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