@@ -28,6 +28,10 @@ import { EventName } from '../../../telemetry/constants';
28
28
import { IExtensionSingleActivationService } from '../../../activation/types' ;
29
29
import { cache } from '../../../common/utils/decorators' ;
30
30
import { noop } from '../../../common/utils/misc' ;
31
+ import { IPythonExecutionFactory } from '../../../common/process/types' ;
32
+ import { getOSType , OSType } from '../../../common/utils/platform' ;
33
+ import { IFileSystem } from '../../../common/platform/types' ;
34
+ import { traceError } from '../../../logging' ;
31
35
32
36
const messages = {
33
37
[ DiagnosticCodes . NoPythonInterpretersDiagnostic ] : l10n . t (
@@ -36,6 +40,9 @@ const messages = {
36
40
[ DiagnosticCodes . InvalidPythonInterpreterDiagnostic ] : l10n . t (
37
41
'An Invalid Python interpreter is selected{0}, please try changing it to enable features such as IntelliSense, linting, and debugging. See output for more details regarding why the interpreter is invalid.' ,
38
42
) ,
43
+ [ DiagnosticCodes . InvalidComspecDiagnostic ] : l10n . t (
44
+ "The environment variable 'Comspec' seems to be set to an invalid value. Please correct it to carry valid path to Command Prompt to enable features such as IntelliSense, linting, and debugging. See instructions which might help." ,
45
+ ) ,
39
46
} ;
40
47
41
48
export class InvalidPythonInterpreterDiagnostic extends BaseDiagnostic {
@@ -61,6 +68,12 @@ export class InvalidPythonInterpreterDiagnostic extends BaseDiagnostic {
61
68
}
62
69
}
63
70
71
+ export class DefaultShellDiagnostic extends BaseDiagnostic {
72
+ constructor ( code : DiagnosticCodes . InvalidComspecDiagnostic , resource : Resource , scope = DiagnosticScope . Global ) {
73
+ super ( code , messages [ code ] , DiagnosticSeverity . Error , scope , resource , undefined , 'always' ) ;
74
+ }
75
+ }
76
+
64
77
export const InvalidPythonInterpreterServiceId = 'InvalidPythonInterpreterServiceId' ;
65
78
66
79
@injectable ( )
@@ -73,7 +86,11 @@ export class InvalidPythonInterpreterService extends BaseDiagnosticsService
73
86
@inject ( IDisposableRegistry ) disposableRegistry : IDisposableRegistry ,
74
87
) {
75
88
super (
76
- [ DiagnosticCodes . NoPythonInterpretersDiagnostic , DiagnosticCodes . InvalidPythonInterpreterDiagnostic ] ,
89
+ [
90
+ DiagnosticCodes . NoPythonInterpretersDiagnostic ,
91
+ DiagnosticCodes . InvalidPythonInterpreterDiagnostic ,
92
+ DiagnosticCodes . InvalidComspecDiagnostic ,
93
+ ] ,
77
94
serviceContainer ,
78
95
disposableRegistry ,
79
96
false ,
@@ -103,6 +120,13 @@ export class InvalidPythonInterpreterService extends BaseDiagnosticsService
103
120
public async _manualDiagnose ( resource : Resource ) : Promise < IDiagnostic [ ] > {
104
121
const workspaceService = this . serviceContainer . get < IWorkspaceService > ( IWorkspaceService ) ;
105
122
const interpreterService = this . serviceContainer . get < IInterpreterService > ( IInterpreterService ) ;
123
+ const currentInterpreter = await interpreterService . getActiveInterpreter ( resource ) ;
124
+ if ( ! currentInterpreter ) {
125
+ const diagnostics = await this . diagnoseDefaultShell ( resource ) ;
126
+ if ( diagnostics . length ) {
127
+ return diagnostics ;
128
+ }
129
+ }
106
130
const hasInterpreters = await interpreterService . hasInterpreters ( ) ;
107
131
const interpreterPathService = this . serviceContainer . get < IInterpreterPathService > ( IInterpreterPathService ) ;
108
132
const isInterpreterSetToDefault = interpreterPathService . get ( resource ) === 'python' ;
@@ -118,7 +142,6 @@ export class InvalidPythonInterpreterService extends BaseDiagnosticsService
118
142
] ;
119
143
}
120
144
121
- const currentInterpreter = await interpreterService . getActiveInterpreter ( resource ) ;
122
145
if ( ! currentInterpreter ) {
123
146
return [
124
147
new InvalidPythonInterpreterDiagnostic (
@@ -163,6 +186,17 @@ export class InvalidPythonInterpreterService extends BaseDiagnosticsService
163
186
164
187
private getCommandPrompts ( diagnostic : IDiagnostic ) : { prompt : string ; command ?: IDiagnosticCommand } [ ] {
165
188
const commandFactory = this . serviceContainer . get < IDiagnosticsCommandFactory > ( IDiagnosticsCommandFactory ) ;
189
+ if ( diagnostic . code === DiagnosticCodes . InvalidComspecDiagnostic ) {
190
+ return [
191
+ {
192
+ prompt : Common . instructions ,
193
+ command : commandFactory . createCommand ( diagnostic , {
194
+ type : 'launch' ,
195
+ options : 'https://aka.ms/AAk3djo' ,
196
+ } ) ,
197
+ } ,
198
+ ] ;
199
+ }
166
200
const prompts = [
167
201
{
168
202
prompt : Common . selectPythonInterpreter ,
@@ -183,6 +217,32 @@ export class InvalidPythonInterpreterService extends BaseDiagnosticsService
183
217
}
184
218
return prompts ;
185
219
}
220
+
221
+ private async diagnoseDefaultShell ( resource : Resource ) : Promise < IDiagnostic [ ] > {
222
+ if ( getOSType ( ) !== OSType . Windows ) {
223
+ return [ ] ;
224
+ }
225
+ const executionFactory = this . serviceContainer . get < IPythonExecutionFactory > ( IPythonExecutionFactory ) ;
226
+ const executionService = await executionFactory . create ( { resource } ) ;
227
+ try {
228
+ await executionService . getExecutablePath ( { throwOnError : true } ) ;
229
+ } catch ( ex ) {
230
+ if ( ( ex as Error ) . message ?. includes ( '4058' ) ) {
231
+ // ENOENT (-4058) error is thrown by Node when the default shell is invalid.
232
+ if ( await this . isComspecInvalid ( ) ) {
233
+ traceError ( 'ComSpec is set to an invalid value' , process . env . ComSpec ) ;
234
+ return [ new DefaultShellDiagnostic ( DiagnosticCodes . InvalidComspecDiagnostic , resource ) ] ;
235
+ }
236
+ }
237
+ }
238
+ return [ ] ;
239
+ }
240
+
241
+ private async isComspecInvalid ( ) {
242
+ const comSpec = process . env . ComSpec ?? '' ;
243
+ const fs = this . serviceContainer . get < IFileSystem > ( IFileSystem ) ;
244
+ return fs . fileExists ( comSpec ) ;
245
+ }
186
246
}
187
247
188
248
function getOnCloseHandler ( diagnostic : IDiagnostic ) : IDiagnosticMessageOnCloseHandler | undefined {
0 commit comments