@@ -6,7 +6,7 @@ import { inject, injectable } from 'inversify';
6
6
import { DiagnosticSeverity , l10n } from 'vscode' ;
7
7
import '../../../common/extensions' ;
8
8
import * as path from 'path' ;
9
- import { IDisposableRegistry , IInterpreterPathService , Resource } from '../../../common/types' ;
9
+ import { IConfigurationService , IDisposableRegistry , IInterpreterPathService , Resource } from '../../../common/types' ;
10
10
import { IInterpreterService } from '../../../interpreter/contracts' ;
11
11
import { IServiceContainer } from '../../../ioc/types' ;
12
12
import { BaseDiagnostic , BaseDiagnosticsService } from '../base' ;
@@ -28,6 +28,12 @@ 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 { getEnvironmentVariable , getOSType , OSType } from '../../../common/utils/platform' ;
32
+ import { IFileSystem } from '../../../common/platform/types' ;
33
+ import { traceError } from '../../../logging' ;
34
+ import { getExecutable } from '../../../common/process/internal/python' ;
35
+ import { getSearchPathEnvVarNames } from '../../../common/utils/exec' ;
36
+ import { IProcessServiceFactory } from '../../../common/process/types' ;
31
37
32
38
const messages = {
33
39
[ DiagnosticCodes . NoPythonInterpretersDiagnostic ] : l10n . t (
@@ -36,6 +42,15 @@ const messages = {
36
42
[ DiagnosticCodes . InvalidPythonInterpreterDiagnostic ] : l10n . t (
37
43
'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
44
) ,
45
+ [ DiagnosticCodes . InvalidComspecDiagnostic ] : l10n . t (
46
+ 'We detected an issue with one of your environment variables that breaks features such as IntelliSense, linting and debugging. Try setting the "ComSpec" variable to a valid Command Prompt path in your system to fix it.' ,
47
+ ) ,
48
+ [ DiagnosticCodes . IncompletePathVarDiagnostic ] : l10n . t (
49
+ 'We detected an issue with "Path" environment variable that breaks features such as IntelliSense, linting and debugging. Please edit it to make sure it contains the "SystemRoot" subdirectories.' ,
50
+ ) ,
51
+ [ DiagnosticCodes . DefaultShellErrorDiagnostic ] : l10n . t (
52
+ 'We detected an issue with your default shell that breaks features such as IntelliSense, linting and debugging. Try resetting "ComSpec" and "Path" environment variables to fix it.' ,
53
+ ) ,
39
54
} ;
40
55
41
56
export class InvalidPythonInterpreterDiagnostic extends BaseDiagnostic {
@@ -61,6 +76,17 @@ export class InvalidPythonInterpreterDiagnostic extends BaseDiagnostic {
61
76
}
62
77
}
63
78
79
+ type DefaultShellDiagnostics =
80
+ | DiagnosticCodes . InvalidComspecDiagnostic
81
+ | DiagnosticCodes . IncompletePathVarDiagnostic
82
+ | DiagnosticCodes . DefaultShellErrorDiagnostic ;
83
+
84
+ export class DefaultShellDiagnostic extends BaseDiagnostic {
85
+ constructor ( code : DefaultShellDiagnostics , resource : Resource , scope = DiagnosticScope . Global ) {
86
+ super ( code , messages [ code ] , DiagnosticSeverity . Error , scope , resource , undefined , 'always' ) ;
87
+ }
88
+ }
89
+
64
90
export const InvalidPythonInterpreterServiceId = 'InvalidPythonInterpreterServiceId' ;
65
91
66
92
@injectable ( )
@@ -73,7 +99,13 @@ export class InvalidPythonInterpreterService extends BaseDiagnosticsService
73
99
@inject ( IDisposableRegistry ) disposableRegistry : IDisposableRegistry ,
74
100
) {
75
101
super (
76
- [ DiagnosticCodes . NoPythonInterpretersDiagnostic , DiagnosticCodes . InvalidPythonInterpreterDiagnostic ] ,
102
+ [
103
+ DiagnosticCodes . NoPythonInterpretersDiagnostic ,
104
+ DiagnosticCodes . InvalidPythonInterpreterDiagnostic ,
105
+ DiagnosticCodes . InvalidComspecDiagnostic ,
106
+ DiagnosticCodes . IncompletePathVarDiagnostic ,
107
+ DiagnosticCodes . DefaultShellErrorDiagnostic ,
108
+ ] ,
77
109
serviceContainer ,
78
110
disposableRegistry ,
79
111
false ,
@@ -95,14 +127,17 @@ export class InvalidPythonInterpreterService extends BaseDiagnosticsService
95
127
) ;
96
128
}
97
129
98
- // eslint-disable-next-line class-methods-use-this
99
- public async diagnose ( _resource : Resource ) : Promise < IDiagnostic [ ] > {
100
- return [ ] ;
130
+ public async diagnose ( resource : Resource ) : Promise < IDiagnostic [ ] > {
131
+ return this . diagnoseDefaultShell ( resource ) ;
101
132
}
102
133
103
134
public async _manualDiagnose ( resource : Resource ) : Promise < IDiagnostic [ ] > {
104
135
const workspaceService = this . serviceContainer . get < IWorkspaceService > ( IWorkspaceService ) ;
105
136
const interpreterService = this . serviceContainer . get < IInterpreterService > ( IInterpreterService ) ;
137
+ const diagnostics = await this . diagnoseDefaultShell ( resource ) ;
138
+ if ( diagnostics . length > 0 ) {
139
+ return diagnostics ;
140
+ }
106
141
const hasInterpreters = await interpreterService . hasInterpreters ( ) ;
107
142
const interpreterPathService = this . serviceContainer . get < IInterpreterPathService > ( IInterpreterPathService ) ;
108
143
const isInterpreterSetToDefault = interpreterPathService . get ( resource ) === 'python' ;
@@ -140,6 +175,72 @@ export class InvalidPythonInterpreterService extends BaseDiagnosticsService
140
175
return false ;
141
176
}
142
177
178
+ private async diagnoseDefaultShell ( resource : Resource ) : Promise < IDiagnostic [ ] > {
179
+ await this . isPathVarIncomplete ( ) ;
180
+ if ( getOSType ( ) !== OSType . Windows ) {
181
+ return [ ] ;
182
+ }
183
+ const interpreterService = this . serviceContainer . get < IInterpreterService > ( IInterpreterService ) ;
184
+ const currentInterpreter = await interpreterService . getActiveInterpreter ( resource ) ;
185
+ if ( currentInterpreter ) {
186
+ return [ ] ;
187
+ }
188
+ try {
189
+ await this . shellExecPython ( ) ;
190
+ } catch ( ex ) {
191
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
192
+ if ( ( ex as any ) . errno === - 4058 ) {
193
+ // ENOENT (-4058) error is thrown by Node when the default shell is invalid.
194
+ traceError ( 'ComSpec is likely set to an invalid value' , getEnvironmentVariable ( 'ComSpec' ) ) ;
195
+ if ( await this . isComspecInvalid ( ) ) {
196
+ return [ new DefaultShellDiagnostic ( DiagnosticCodes . InvalidComspecDiagnostic , resource ) ] ;
197
+ }
198
+ if ( this . isPathVarIncomplete ( ) ) {
199
+ traceError ( 'PATH env var appears to be incomplete' , process . env . Path , process . env . PATH ) ;
200
+ return [ new DefaultShellDiagnostic ( DiagnosticCodes . IncompletePathVarDiagnostic , resource ) ] ;
201
+ }
202
+ return [ new DefaultShellDiagnostic ( DiagnosticCodes . DefaultShellErrorDiagnostic , resource ) ] ;
203
+ }
204
+ }
205
+ return [ ] ;
206
+ }
207
+
208
+ private async isComspecInvalid ( ) {
209
+ const comSpec = getEnvironmentVariable ( 'ComSpec' ) ?? '' ;
210
+ const fs = this . serviceContainer . get < IFileSystem > ( IFileSystem ) ;
211
+ return fs . fileExists ( comSpec ) . then ( ( exists ) => ! exists ) ;
212
+ }
213
+
214
+ // eslint-disable-next-line class-methods-use-this
215
+ private isPathVarIncomplete ( ) {
216
+ const envVars = getSearchPathEnvVarNames ( ) ;
217
+ const systemRoot = getEnvironmentVariable ( 'SystemRoot' ) ?? 'C:\\WINDOWS' ;
218
+ for ( const envVar of envVars ) {
219
+ const value = getEnvironmentVariable ( envVar ) ;
220
+ if ( value ?. includes ( systemRoot ) ) {
221
+ return false ;
222
+ }
223
+ }
224
+ return true ;
225
+ }
226
+
227
+ @cache ( - 1 , true )
228
+ // eslint-disable-next-line class-methods-use-this
229
+ private async shellExecPython ( ) {
230
+ const configurationService = this . serviceContainer . get < IConfigurationService > ( IConfigurationService ) ;
231
+ const { pythonPath } = configurationService . getSettings ( ) ;
232
+ const [ args ] = getExecutable ( ) ;
233
+ const argv = [ pythonPath , ...args ] ;
234
+ // Concat these together to make a set of quoted strings
235
+ const quoted = argv . reduce (
236
+ ( p , c ) => ( p ? `${ p } ${ c . toCommandArgumentForPythonExt ( ) } ` : `${ c . toCommandArgumentForPythonExt ( ) } ` ) ,
237
+ '' ,
238
+ ) ;
239
+ const processServiceFactory = this . serviceContainer . get < IProcessServiceFactory > ( IProcessServiceFactory ) ;
240
+ const service = await processServiceFactory . create ( ) ;
241
+ return service . shellExec ( quoted , { timeout : 15000 } ) ;
242
+ }
243
+
143
244
@cache ( 1000 , true ) // This is to handle throttling of multiple events.
144
245
protected async onHandle ( diagnostics : IDiagnostic [ ] ) : Promise < void > {
145
246
if ( diagnostics . length === 0 ) {
@@ -163,6 +264,26 @@ export class InvalidPythonInterpreterService extends BaseDiagnosticsService
163
264
164
265
private getCommandPrompts ( diagnostic : IDiagnostic ) : { prompt : string ; command ?: IDiagnosticCommand } [ ] {
165
266
const commandFactory = this . serviceContainer . get < IDiagnosticsCommandFactory > ( IDiagnosticsCommandFactory ) ;
267
+ if (
268
+ diagnostic . code === DiagnosticCodes . InvalidComspecDiagnostic ||
269
+ diagnostic . code === DiagnosticCodes . IncompletePathVarDiagnostic ||
270
+ diagnostic . code === DiagnosticCodes . DefaultShellErrorDiagnostic
271
+ ) {
272
+ const links : Record < DefaultShellDiagnostics , string > = {
273
+ InvalidComspecDiagnostic : 'https://aka.ms/AAk3djo' ,
274
+ IncompletePathVarDiagnostic : 'https://aka.ms/AAk744c' ,
275
+ DefaultShellErrorDiagnostic : 'https://aka.ms/AAk7qix' ,
276
+ } ;
277
+ return [
278
+ {
279
+ prompt : Common . seeInstructions ,
280
+ command : commandFactory . createCommand ( diagnostic , {
281
+ type : 'launch' ,
282
+ options : links [ diagnostic . code ] ,
283
+ } ) ,
284
+ } ,
285
+ ] ;
286
+ }
166
287
const prompts = [
167
288
{
168
289
prompt : Common . selectPythonInterpreter ,
0 commit comments