@@ -8,6 +8,7 @@ import { Event, EventEmitter } from 'vscode';
8
8
import { CancellationToken } from 'vscode-jsonrpc' ;
9
9
import { createPromiseFromCancellation } from '../../../common/cancellation' ;
10
10
import '../../../common/extensions' ;
11
+ import { noop } from '../../../common/utils/misc' ;
11
12
import { IInterpreterService , PythonInterpreter } from '../../../interpreter/contracts' ;
12
13
import { sendTelemetryEvent } from '../../../telemetry' ;
13
14
import { Telemetry } from '../../constants' ;
@@ -19,8 +20,8 @@ import { JupyterInterpreterStateStore } from './jupyterInterpreterStateStore';
19
20
@injectable ( )
20
21
export class JupyterInterpreterService {
21
22
private _selectedInterpreter ?: PythonInterpreter ;
22
- private _selectedInterpreterPath ?: string ;
23
23
private _onDidChangeInterpreter = new EventEmitter < PythonInterpreter > ( ) ;
24
+ private getInitialInterpreterPromise : Promise < PythonInterpreter | undefined > | undefined ;
24
25
public get onDidChangeInterpreter ( ) : Event < PythonInterpreter > {
25
26
return this . _onDidChangeInterpreter . event ;
26
27
}
@@ -40,40 +41,30 @@ export class JupyterInterpreterService {
40
41
* @memberof JupyterInterpreterService
41
42
*/
42
43
public async getSelectedInterpreter ( token ?: CancellationToken ) : Promise < PythonInterpreter | undefined > {
43
- if ( this . _selectedInterpreter ) {
44
- return this . _selectedInterpreter ;
45
- }
44
+ // Before we return _selected interpreter make sure that we have run our initial set interpreter once
45
+ // because _selectedInterpreter can be changed by other function and at other times, this promise
46
+ // is cached to only run once
47
+ await this . setInitialInterpreter ( token ) ;
46
48
47
- const resolveToUndefinedWhenCancelled = createPromiseFromCancellation ( { cancelAction : 'resolve' , defaultValue : undefined , token } ) ;
48
- // For backwards compatiblity check if we have a cached interpreter (older version of extension).
49
- // If that interpreter has everything we need then use that.
50
- let interpreter = await Promise . race ( [ this . getInterpreterFromChangeOfOlderVersionOfExtension ( ) , resolveToUndefinedWhenCancelled ] ) ;
51
- if ( interpreter ) {
52
- return interpreter ;
53
- }
49
+ return this . _selectedInterpreter ;
50
+ }
54
51
55
- const pythonPath = this . _selectedInterpreterPath || this . interpreterSelectionState . selectedPythonPath ;
56
- if ( ! pythonPath ) {
57
- // Check if current interpreter has all of the required dependencies.
58
- // If yes, then use that.
59
- interpreter = await this . interpreterService . getActiveInterpreter ( undefined ) ;
60
- if ( ! interpreter ) {
61
- return ;
62
- }
63
- // Use this interpreter going forward.
64
- if ( await this . interpreterConfiguration . areDependenciesInstalled ( interpreter ) ) {
65
- this . setAsSelectedInterpreter ( interpreter ) ;
66
- return interpreter ;
67
- }
68
- return ;
52
+ // To be run one initial time. Check our saved locations and then current interpreter to try to start off
53
+ // with a valid jupyter interpreter
54
+ public async setInitialInterpreter ( token ?: CancellationToken ) : Promise < PythonInterpreter | undefined > {
55
+ if ( ! this . getInitialInterpreterPromise ) {
56
+ this . getInitialInterpreterPromise = this . getInitialInterpreterImpl ( token ) . then ( result => {
57
+ // Set ourselves as a valid interpreter if we found something
58
+ if ( result ) {
59
+ this . changeSelectedInterpreterProperty ( result ) ;
60
+ }
61
+ return result ;
62
+ } ) ;
69
63
}
70
64
71
- const interpreterDetails = await Promise . race ( [ this . interpreterService . getInterpreterDetails ( pythonPath , undefined ) , resolveToUndefinedWhenCancelled ] ) ;
72
- if ( interpreterDetails ) {
73
- this . _selectedInterpreter = interpreterDetails ;
74
- }
75
- return interpreterDetails ;
65
+ return this . getInitialInterpreterPromise ;
76
66
}
67
+
77
68
/**
78
69
* Selects and interpreter to run jupyter server.
79
70
* Validates and configures the interpreter.
@@ -94,7 +85,7 @@ export class JupyterInterpreterService {
94
85
const result = await this . interpreterConfiguration . installMissingDependencies ( interpreter , undefined , token ) ;
95
86
switch ( result ) {
96
87
case JupyterInterpreterDependencyResponse . ok : {
97
- this . setAsSelectedInterpreter ( interpreter ) ;
88
+ await this . setAsSelectedInterpreter ( interpreter ) ;
98
89
return interpreter ;
99
90
}
100
91
case JupyterInterpreterDependencyResponse . cancel :
@@ -104,30 +95,91 @@ export class JupyterInterpreterService {
104
95
return this . selectInterpreter ( token ) ;
105
96
}
106
97
}
107
- private async getInterpreterFromChangeOfOlderVersionOfExtension ( ) : Promise < PythonInterpreter | undefined > {
98
+
99
+ // Check the location that we stored jupyter launch path in the old version
100
+ // if it's there, return it and clear the location
101
+ private getInterpreterFromChangeOfOlderVersionOfExtension ( ) : string | undefined {
108
102
const pythonPath = this . oldVersionCacheStateStore . getCachedInterpreterPath ( ) ;
109
103
if ( ! pythonPath ) {
110
104
return ;
111
105
}
106
+
107
+ // Clear the cache to not check again
108
+ this . oldVersionCacheStateStore . clearCache ( ) . ignoreErrors ( ) ;
109
+ return pythonPath ;
110
+ }
111
+
112
+ // Set the specified interpreter as our current selected interpreter
113
+ private async setAsSelectedInterpreter ( interpreter : PythonInterpreter ) : Promise < void > {
114
+ // Make sure that our initial set has happened before we allow a set so that
115
+ // calculation of the initial interpreter doesn't clobber the existing one
116
+ await this . setInitialInterpreter ( ) ;
117
+ this . changeSelectedInterpreterProperty ( interpreter ) ;
118
+ }
119
+
120
+ private changeSelectedInterpreterProperty ( interpreter : PythonInterpreter ) {
121
+ this . _selectedInterpreter = interpreter ;
122
+ this . _onDidChangeInterpreter . fire ( interpreter ) ;
123
+ this . interpreterSelectionState . updateSelectedPythonPath ( interpreter . path ) ;
124
+ sendTelemetryEvent ( Telemetry . SelectJupyterInterpreter , undefined , { result : 'selected' } ) ;
125
+ }
126
+
127
+ // For a given python path check if it can run jupyter for us
128
+ // if so, return the interpreter
129
+ private async validateInterpreterPath ( pythonPath : string , token ?: CancellationToken ) : Promise < PythonInterpreter | undefined > {
112
130
try {
113
- const interpreter = await this . interpreterService . getInterpreterDetails ( pythonPath , undefined ) ;
131
+ const resolveToUndefinedWhenCancelled = createPromiseFromCancellation ( {
132
+ cancelAction : 'resolve' ,
133
+ defaultValue : undefined ,
134
+ token
135
+ } ) ;
136
+
137
+ // First see if we can get interpreter details
138
+ const interpreter = await Promise . race ( [ this . interpreterService . getInterpreterDetails ( pythonPath , undefined ) , resolveToUndefinedWhenCancelled ] ) ;
139
+ if ( interpreter ) {
140
+ // Then check that dependencies are installed
141
+ if ( await this . interpreterConfiguration . areDependenciesInstalled ( interpreter , token ) ) {
142
+ return interpreter ;
143
+ }
144
+ }
145
+ } catch ( _err ) {
146
+ // For any errors we are ok with just returning undefined for an invalid interpreter
147
+ noop ( ) ;
148
+ }
149
+ return undefined ;
150
+ }
151
+
152
+ private async getInitialInterpreterImpl ( token ?: CancellationToken ) : Promise < PythonInterpreter | undefined > {
153
+ let interpreter : PythonInterpreter | undefined ;
154
+
155
+ // Check the old version location first, we will clear it if we find it here
156
+ const oldVersionPythonPath = this . getInterpreterFromChangeOfOlderVersionOfExtension ( ) ;
157
+ if ( oldVersionPythonPath ) {
158
+ interpreter = await this . validateInterpreterPath ( oldVersionPythonPath , token ) ;
159
+ }
160
+
161
+ // Next check the saved global path
162
+ if ( ! interpreter && this . interpreterSelectionState . selectedPythonPath ) {
163
+ interpreter = await this . validateInterpreterPath ( this . interpreterSelectionState . selectedPythonPath , token ) ;
164
+
165
+ // If we had a global path, but it's not valid, trash it
114
166
if ( ! interpreter ) {
115
- return ;
167
+ this . interpreterSelectionState . updateSelectedPythonPath ( undefined ) ;
116
168
}
117
- if ( await this . interpreterConfiguration . areDependenciesInstalled ( interpreter ) ) {
118
- this . setAsSelectedInterpreter ( interpreter ) ;
119
- return interpreter ;
169
+ }
170
+
171
+ // Nothing saved found, so check our current interpreter
172
+ if ( ! interpreter ) {
173
+ const currentInterpreter = await this . interpreterService . getActiveInterpreter ( undefined ) ;
174
+
175
+ if ( currentInterpreter ) {
176
+ // Ask and give a chance to install dependencies in current interpreter
177
+ if ( await this . interpreterConfiguration . areDependenciesInstalled ( currentInterpreter , token ) ) {
178
+ interpreter = currentInterpreter ;
179
+ }
120
180
}
121
- // If dependencies are not installed, then ignore it. lets continue with the current logic.
122
- } finally {
123
- // Don't perform this check again, just clear the cache.
124
- this . oldVersionCacheStateStore . clearCache ( ) . ignoreErrors ( ) ;
125
181
}
126
- }
127
- private setAsSelectedInterpreter ( interpreter : PythonInterpreter ) : void {
128
- this . _selectedInterpreter = interpreter ;
129
- this . _onDidChangeInterpreter . fire ( interpreter ) ;
130
- this . interpreterSelectionState . updateSelectedPythonPath ( ( this . _selectedInterpreterPath = interpreter . path ) ) ;
131
- sendTelemetryEvent ( Telemetry . SelectJupyterInterpreter , undefined , { result : 'selected' } ) ;
182
+
183
+ return interpreter ;
132
184
}
133
185
}
0 commit comments