Skip to content

Commit 4044efb

Browse files
author
Kartik Raj
authored
Improve prompts in case no Python is installed or an invalid interpreter is selected (#19397)
1 parent fffcf9e commit 4044efb

File tree

8 files changed

+114
-102
lines changed

8 files changed

+114
-102
lines changed

src/client/application/diagnostics/base.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { injectable, unmanaged } from 'inversify';
77
import { DiagnosticSeverity } from 'vscode';
88
import { IWorkspaceService } from '../../common/application/types';
99
import { IDisposable, IDisposableRegistry, Resource } from '../../common/types';
10+
import { asyncFilter } from '../../common/utils/arrayUtils';
1011
import { IServiceContainer } from '../../ioc/types';
1112
import { sendTelemetryEvent } from '../../telemetry';
1213
import { EventName } from '../../telemetry/constants';
@@ -21,8 +22,8 @@ export abstract class BaseDiagnostic implements IDiagnostic {
2122
public readonly severity: DiagnosticSeverity,
2223
public readonly scope: DiagnosticScope,
2324
public readonly resource: Resource,
24-
public readonly invokeHandler: 'always' | 'default' = 'default',
2525
public readonly shouldShowPrompt = true,
26+
public readonly invokeHandler: 'always' | 'default' = 'default',
2627
) {}
2728
}
2829

@@ -48,7 +49,10 @@ export abstract class BaseDiagnosticsService implements IDiagnosticsService, IDi
4849
if (diagnostics.length === 0) {
4950
return;
5051
}
51-
const diagnosticsToHandle = diagnostics.filter((item) => {
52+
const diagnosticsToHandle = await asyncFilter(diagnostics, async (item) => {
53+
if (!(await this.canHandle(item))) {
54+
return false;
55+
}
5256
if (item.invokeHandler && item.invokeHandler === 'always') {
5357
return true;
5458
}

src/client/application/diagnostics/checks/invalidLaunchJsonDebugger.ts

-4
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ export class InvalidLaunchJsonDebuggerDiagnostic extends BaseDiagnostic {
3939
DiagnosticSeverity.Error,
4040
DiagnosticScope.WorkspaceFolder,
4141
resource,
42-
'always',
4342
shouldShowPrompt,
4443
);
4544
}
@@ -131,9 +130,6 @@ export class InvalidLaunchJsonDebuggerService extends BaseDiagnosticsService {
131130
}
132131

133132
private async handleDiagnostic(diagnostic: IDiagnostic): Promise<void> {
134-
if (!this.canHandle(diagnostic)) {
135-
return;
136-
}
137133
if (!diagnostic.shouldShowPrompt) {
138134
await this.fixLaunchJson(diagnostic.code);
139135
return;

src/client/application/diagnostics/checks/invalidPythonPathInDebugger.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,15 @@ class InvalidPythonPathInDebuggerDiagnostic extends BaseDiagnostic {
3838
| DiagnosticCodes.InvalidPythonPathInDebuggerSettingsDiagnostic,
3939
resource: Resource,
4040
) {
41-
super(code, messages[code], DiagnosticSeverity.Error, DiagnosticScope.WorkspaceFolder, resource, 'always');
41+
super(
42+
code,
43+
messages[code],
44+
DiagnosticSeverity.Error,
45+
DiagnosticScope.WorkspaceFolder,
46+
resource,
47+
undefined,
48+
'always',
49+
);
4250
}
4351
}
4452

src/client/application/diagnostics/checks/powerShellActivation.ts

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export class PowershellActivationNotAvailableDiagnostic extends BaseDiagnostic {
3434
DiagnosticSeverity.Warning,
3535
DiagnosticScope.Global,
3636
resource,
37+
undefined,
3738
'always',
3839
);
3940
}

src/client/application/diagnostics/checks/pythonInterpreter.ts

+54-43
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,10 @@ import { inject, injectable } from 'inversify';
66
import { DiagnosticSeverity } from 'vscode';
77
import '../../../common/extensions';
88
import * as nls from 'vscode-nls';
9+
import * as path from 'path';
910
import { IDisposableRegistry, Resource } from '../../../common/types';
1011
import { IInterpreterService } from '../../../interpreter/contracts';
1112
import { IServiceContainer } from '../../../ioc/types';
12-
import { sendTelemetryEvent } from '../../../telemetry';
13-
import { EventName } from '../../../telemetry/constants';
1413
import { BaseDiagnostic, BaseDiagnosticsService } from '../base';
1514
import { IDiagnosticsCommandFactory } from '../commands/types';
1615
import { DiagnosticCodes } from '../constants';
@@ -23,28 +22,44 @@ import {
2322
IDiagnosticMessageOnCloseHandler,
2423
} from '../types';
2524
import { Common } from '../../../common/utils/localize';
25+
import { Commands } from '../../../common/constants';
26+
import { IWorkspaceService } from '../../../common/application/types';
27+
import { sendTelemetryEvent } from '../../../telemetry';
28+
import { EventName } from '../../../telemetry/constants';
2629

2730
const localize: nls.LocalizeFunc = nls.loadMessageBundle();
2831

2932
const messages = {
3033
[DiagnosticCodes.NoPythonInterpretersDiagnostic]: localize(
3134
'DiagnosticCodes.NoPythonInterpretersDiagnostic',
32-
'Python is not installed. Please download and install Python before using the extension.',
35+
'No Python interpreter is selected. Please select a Python interpreter to enable features such as IntelliSense, linting, and debugging.',
3336
),
34-
[DiagnosticCodes.NoCurrentlySelectedPythonInterpreterDiagnostic]: localize(
37+
[DiagnosticCodes.InvalidPythonInterpreterDiagnostic]: localize(
3538
'DiagnosticCodes.NoCurrentlySelectedPythonInterpreterDiagnostic',
36-
'No Python interpreter is selected. You need to select a Python interpreter to enable features such as IntelliSense, linting, and debugging.',
39+
'An Invalid Python interpreter is selected{0}, please try changing it to enable features such as IntelliSense, linting, and debugging.',
3740
),
3841
};
3942

4043
export class InvalidPythonInterpreterDiagnostic extends BaseDiagnostic {
4144
constructor(
42-
code:
43-
| DiagnosticCodes.NoPythonInterpretersDiagnostic
44-
| DiagnosticCodes.NoCurrentlySelectedPythonInterpreterDiagnostic,
45+
code: DiagnosticCodes.NoPythonInterpretersDiagnostic | DiagnosticCodes.InvalidPythonInterpreterDiagnostic,
4546
resource: Resource,
47+
workspaceService: IWorkspaceService,
48+
scope = DiagnosticScope.WorkspaceFolder,
4649
) {
47-
super(code, messages[code], DiagnosticSeverity.Error, DiagnosticScope.WorkspaceFolder, resource);
50+
let formatArg = '';
51+
if (
52+
workspaceService.workspaceFile &&
53+
workspaceService.workspaceFolders &&
54+
workspaceService.workspaceFolders?.length > 1
55+
) {
56+
// Specify folder name in case of multiroot scenarios
57+
const folder = workspaceService.getWorkspaceFolder(resource);
58+
if (folder) {
59+
formatArg = ` ${localize('Common.forWorkspace', 'for workspace')} ${path.basename(folder.uri.fsPath)}`;
60+
}
61+
}
62+
super(code, messages[code].format(formatArg), DiagnosticSeverity.Error, scope, resource, undefined, 'always');
4863
}
4964
}
5065

@@ -57,37 +72,51 @@ export class InvalidPythonInterpreterService extends BaseDiagnosticsService {
5772
@inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry,
5873
) {
5974
super(
60-
[
61-
DiagnosticCodes.NoPythonInterpretersDiagnostic,
62-
DiagnosticCodes.NoCurrentlySelectedPythonInterpreterDiagnostic,
63-
],
75+
[DiagnosticCodes.NoPythonInterpretersDiagnostic, DiagnosticCodes.InvalidPythonInterpreterDiagnostic],
6476
serviceContainer,
6577
disposableRegistry,
6678
false,
6779
);
6880
}
6981

7082
public async diagnose(resource: Resource): Promise<IDiagnostic[]> {
83+
const workspaceService = this.serviceContainer.get<IWorkspaceService>(IWorkspaceService);
7184
const interpreterService = this.serviceContainer.get<IInterpreterService>(IInterpreterService);
7285
const hasInterpreters = await interpreterService.hasInterpreters();
7386

7487
if (!hasInterpreters) {
75-
return [new InvalidPythonInterpreterDiagnostic(DiagnosticCodes.NoPythonInterpretersDiagnostic, resource)];
88+
return [
89+
new InvalidPythonInterpreterDiagnostic(
90+
DiagnosticCodes.NoPythonInterpretersDiagnostic,
91+
resource,
92+
workspaceService,
93+
DiagnosticScope.Global,
94+
),
95+
];
7696
}
7797

7898
const currentInterpreter = await interpreterService.getActiveInterpreter(resource);
7999
if (!currentInterpreter) {
80100
return [
81101
new InvalidPythonInterpreterDiagnostic(
82-
DiagnosticCodes.NoCurrentlySelectedPythonInterpreterDiagnostic,
102+
DiagnosticCodes.InvalidPythonInterpreterDiagnostic,
83103
resource,
104+
workspaceService,
84105
),
85106
];
86107
}
87-
88108
return [];
89109
}
90110

111+
public async validateInterpreterPathInSettings(resource: Resource): Promise<boolean> {
112+
const diagnostics = await this.diagnose(resource);
113+
if (!diagnostics.length) {
114+
return true;
115+
}
116+
this.handle(diagnostics).ignoreErrors();
117+
return false;
118+
}
119+
91120
protected async onHandle(diagnostics: IDiagnostic[]): Promise<void> {
92121
if (diagnostics.length === 0) {
93122
return;
@@ -110,33 +139,15 @@ export class InvalidPythonInterpreterService extends BaseDiagnosticsService {
110139

111140
private getCommandPrompts(diagnostic: IDiagnostic): { prompt: string; command?: IDiagnosticCommand }[] {
112141
const commandFactory = this.serviceContainer.get<IDiagnosticsCommandFactory>(IDiagnosticsCommandFactory);
113-
switch (diagnostic.code) {
114-
case DiagnosticCodes.NoPythonInterpretersDiagnostic: {
115-
return [
116-
{
117-
prompt: Common.download,
118-
command: commandFactory.createCommand(diagnostic, {
119-
type: 'launch',
120-
options: 'https://www.python.org/downloads',
121-
}),
122-
},
123-
];
124-
}
125-
case DiagnosticCodes.NoCurrentlySelectedPythonInterpreterDiagnostic: {
126-
return [
127-
{
128-
prompt: Common.selectPythonInterpreter,
129-
command: commandFactory.createCommand(diagnostic, {
130-
type: 'executeVSCCommand',
131-
options: 'python.setInterpreter',
132-
}),
133-
},
134-
];
135-
}
136-
default: {
137-
throw new Error("Invalid diagnostic for 'InvalidPythonInterpreterService'");
138-
}
139-
}
142+
return [
143+
{
144+
prompt: Common.selectPythonInterpreter,
145+
command: commandFactory.createCommand(diagnostic, {
146+
type: 'executeVSCCommand',
147+
options: Commands.Set_Interpreter,
148+
}),
149+
},
150+
];
140151
}
141152
}
142153

src/client/application/diagnostics/constants.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export enum DiagnosticCodes {
1111
InvalidPythonPathInDebuggerSettingsDiagnostic = 'InvalidPythonPathInDebuggerSettingsDiagnostic',
1212
InvalidPythonPathInDebuggerLaunchDiagnostic = 'InvalidPythonPathInDebuggerLaunchDiagnostic',
1313
EnvironmentActivationInPowerShellWithBatchFilesNotSupportedDiagnostic = 'EnvironmentActivationInPowerShellWithBatchFilesNotSupportedDiagnostic',
14-
NoCurrentlySelectedPythonInterpreterDiagnostic = 'InvalidPythonInterpreterDiagnostic',
14+
InvalidPythonInterpreterDiagnostic = 'InvalidPythonInterpreterDiagnostic',
1515
LSNotSupportedDiagnostic = 'LSNotSupportedDiagnostic',
1616
PythonPathDeprecatedDiagnostic = 'PythonPathDeprecatedDiagnostic',
1717
JustMyCodeDiagnostic = 'JustMyCodeDiagnostic',

src/client/application/diagnostics/types.ts

+6
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ export interface IDiagnosticCommand {
5353

5454
export type IDiagnosticMessageOnCloseHandler = (response?: string) => void;
5555

56+
export const IInvalidPythonPathInSettings = Symbol('IInvalidPythonPathInSettings');
57+
58+
export interface IInvalidPythonPathInSettings extends IDiagnosticsService {
59+
validateInterpreterPathInSettings(resource: Resource): Promise<boolean>;
60+
}
61+
5662
export const IInvalidPythonPathInDebuggerService = Symbol('IInvalidPythonPathInDebuggerService');
5763

5864
export interface IInvalidPythonPathInDebuggerService extends IDiagnosticsService {

0 commit comments

Comments
 (0)