Skip to content

Improve prompts in case no Python is installed or an invalid interpreter is selected #19397

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jul 11, 2022
8 changes: 6 additions & 2 deletions src/client/application/diagnostics/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { injectable, unmanaged } from 'inversify';
import { DiagnosticSeverity } from 'vscode';
import { IWorkspaceService } from '../../common/application/types';
import { IDisposable, IDisposableRegistry, Resource } from '../../common/types';
import { asyncFilter } from '../../common/utils/arrayUtils';
import { IServiceContainer } from '../../ioc/types';
import { sendTelemetryEvent } from '../../telemetry';
import { EventName } from '../../telemetry/constants';
Expand All @@ -21,8 +22,8 @@ export abstract class BaseDiagnostic implements IDiagnostic {
public readonly severity: DiagnosticSeverity,
public readonly scope: DiagnosticScope,
public readonly resource: Resource,
public readonly invokeHandler: 'always' | 'default' = 'default',
public readonly shouldShowPrompt = true,
public readonly invokeHandler: 'always' | 'default' = 'default',
) {}
}

Expand All @@ -48,7 +49,10 @@ export abstract class BaseDiagnosticsService implements IDiagnosticsService, IDi
if (diagnostics.length === 0) {
return;
}
const diagnosticsToHandle = diagnostics.filter((item) => {
const diagnosticsToHandle = await asyncFilter(diagnostics, async (item) => {
if (!(await this.canHandle(item))) {
return false;
}
if (item.invokeHandler && item.invokeHandler === 'always') {
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ export class InvalidLaunchJsonDebuggerDiagnostic extends BaseDiagnostic {
DiagnosticSeverity.Error,
DiagnosticScope.WorkspaceFolder,
resource,
'always',
shouldShowPrompt,
);
}
Expand Down Expand Up @@ -131,9 +130,6 @@ export class InvalidLaunchJsonDebuggerService extends BaseDiagnosticsService {
}

private async handleDiagnostic(diagnostic: IDiagnostic): Promise<void> {
if (!this.canHandle(diagnostic)) {
return;
}
if (!diagnostic.shouldShowPrompt) {
await this.fixLaunchJson(diagnostic.code);
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,15 @@ class InvalidPythonPathInDebuggerDiagnostic extends BaseDiagnostic {
| DiagnosticCodes.InvalidPythonPathInDebuggerSettingsDiagnostic,
resource: Resource,
) {
super(code, messages[code], DiagnosticSeverity.Error, DiagnosticScope.WorkspaceFolder, resource, 'always');
super(
code,
messages[code],
DiagnosticSeverity.Error,
DiagnosticScope.WorkspaceFolder,
resource,
undefined,
'always',
);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export class PowershellActivationNotAvailableDiagnostic extends BaseDiagnostic {
DiagnosticSeverity.Warning,
DiagnosticScope.Global,
resource,
undefined,
'always',
);
}
Expand Down
97 changes: 54 additions & 43 deletions src/client/application/diagnostics/checks/pythonInterpreter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@ import { inject, injectable } from 'inversify';
import { DiagnosticSeverity } from 'vscode';
import '../../../common/extensions';
import * as nls from 'vscode-nls';
import * as path from 'path';
import { IDisposableRegistry, Resource } from '../../../common/types';
import { IInterpreterService } from '../../../interpreter/contracts';
import { IServiceContainer } from '../../../ioc/types';
import { sendTelemetryEvent } from '../../../telemetry';
import { EventName } from '../../../telemetry/constants';
import { BaseDiagnostic, BaseDiagnosticsService } from '../base';
import { IDiagnosticsCommandFactory } from '../commands/types';
import { DiagnosticCodes } from '../constants';
Expand All @@ -23,28 +22,44 @@ import {
IDiagnosticMessageOnCloseHandler,
} from '../types';
import { Common } from '../../../common/utils/localize';
import { Commands } from '../../../common/constants';
import { IWorkspaceService } from '../../../common/application/types';
import { sendTelemetryEvent } from '../../../telemetry';
import { EventName } from '../../../telemetry/constants';

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

const messages = {
[DiagnosticCodes.NoPythonInterpretersDiagnostic]: localize(
'DiagnosticCodes.NoPythonInterpretersDiagnostic',
'Python is not installed. Please download and install Python before using the extension.',
'No Python interpreter is selected. You need to select a Python interpreter to enable features such as IntelliSense, linting, and debugging.',
),
[DiagnosticCodes.NoCurrentlySelectedPythonInterpreterDiagnostic]: localize(
[DiagnosticCodes.InvalidPythonInterpreterDiagnostic]: localize(
'DiagnosticCodes.NoCurrentlySelectedPythonInterpreterDiagnostic',
'No Python interpreter is selected. You need to select a Python interpreter to enable features such as IntelliSense, linting, and debugging.',
'An Invalid Python interpreter is selected{0}, please try changing it to enable features such as IntelliSense, linting, and debugging.',
),
};

export class InvalidPythonInterpreterDiagnostic extends BaseDiagnostic {
constructor(
code:
| DiagnosticCodes.NoPythonInterpretersDiagnostic
| DiagnosticCodes.NoCurrentlySelectedPythonInterpreterDiagnostic,
code: DiagnosticCodes.NoPythonInterpretersDiagnostic | DiagnosticCodes.InvalidPythonInterpreterDiagnostic,
resource: Resource,
workspaceService: IWorkspaceService,
scope = DiagnosticScope.WorkspaceFolder,
) {
super(code, messages[code], DiagnosticSeverity.Error, DiagnosticScope.WorkspaceFolder, resource);
let formatArg = '';
if (
workspaceService.workspaceFile &&
workspaceService.workspaceFolders &&
workspaceService.workspaceFolders?.length > 1
) {
// Specify folder name in case of multiroot scenarios
const folder = workspaceService.getWorkspaceFolder(resource);
if (folder) {
formatArg = ` ${localize('Common.forWorkspace', 'for workspace')} ${path.basename(folder.uri.fsPath)}`;
}
}
super(code, messages[code].format(formatArg), DiagnosticSeverity.Error, scope, resource, undefined, 'always');
}
}

Expand All @@ -57,37 +72,51 @@ export class InvalidPythonInterpreterService extends BaseDiagnosticsService {
@inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry,
) {
super(
[
DiagnosticCodes.NoPythonInterpretersDiagnostic,
DiagnosticCodes.NoCurrentlySelectedPythonInterpreterDiagnostic,
],
[DiagnosticCodes.NoPythonInterpretersDiagnostic, DiagnosticCodes.InvalidPythonInterpreterDiagnostic],
serviceContainer,
disposableRegistry,
false,
);
}

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

if (!hasInterpreters) {
return [new InvalidPythonInterpreterDiagnostic(DiagnosticCodes.NoPythonInterpretersDiagnostic, resource)];
return [
new InvalidPythonInterpreterDiagnostic(
DiagnosticCodes.NoPythonInterpretersDiagnostic,
resource,
workspaceService,
DiagnosticScope.Global,
),
];
}

const currentInterpreter = await interpreterService.getActiveInterpreter(resource);
if (!currentInterpreter) {
return [
new InvalidPythonInterpreterDiagnostic(
DiagnosticCodes.NoCurrentlySelectedPythonInterpreterDiagnostic,
DiagnosticCodes.InvalidPythonInterpreterDiagnostic,
resource,
workspaceService,
),
];
}

return [];
}

public async validateInterpreterPathInSettings(resource: Resource): Promise<boolean> {
const diagnostics = await this.diagnose(resource);
if (!diagnostics.length) {
return true;
}
this.handle(diagnostics).ignoreErrors();
return false;
}

protected async onHandle(diagnostics: IDiagnostic[]): Promise<void> {
if (diagnostics.length === 0) {
return;
Expand All @@ -110,33 +139,15 @@ export class InvalidPythonInterpreterService extends BaseDiagnosticsService {

private getCommandPrompts(diagnostic: IDiagnostic): { prompt: string; command?: IDiagnosticCommand }[] {
const commandFactory = this.serviceContainer.get<IDiagnosticsCommandFactory>(IDiagnosticsCommandFactory);
switch (diagnostic.code) {
case DiagnosticCodes.NoPythonInterpretersDiagnostic: {
return [
{
prompt: Common.download,
command: commandFactory.createCommand(diagnostic, {
type: 'launch',
options: 'https://www.python.org/downloads',
}),
},
];
}
case DiagnosticCodes.NoCurrentlySelectedPythonInterpreterDiagnostic: {
return [
{
prompt: Common.selectPythonInterpreter,
command: commandFactory.createCommand(diagnostic, {
type: 'executeVSCCommand',
options: 'python.setInterpreter',
}),
},
];
}
default: {
throw new Error("Invalid diagnostic for 'InvalidPythonInterpreterService'");
}
}
return [
{
prompt: Common.selectPythonInterpreter,
command: commandFactory.createCommand(diagnostic, {
type: 'executeVSCCommand',
options: Commands.Set_Interpreter,
}),
},
];
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/client/application/diagnostics/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export enum DiagnosticCodes {
InvalidPythonPathInDebuggerSettingsDiagnostic = 'InvalidPythonPathInDebuggerSettingsDiagnostic',
InvalidPythonPathInDebuggerLaunchDiagnostic = 'InvalidPythonPathInDebuggerLaunchDiagnostic',
EnvironmentActivationInPowerShellWithBatchFilesNotSupportedDiagnostic = 'EnvironmentActivationInPowerShellWithBatchFilesNotSupportedDiagnostic',
NoCurrentlySelectedPythonInterpreterDiagnostic = 'InvalidPythonInterpreterDiagnostic',
InvalidPythonInterpreterDiagnostic = 'InvalidPythonInterpreterDiagnostic',
LSNotSupportedDiagnostic = 'LSNotSupportedDiagnostic',
PythonPathDeprecatedDiagnostic = 'PythonPathDeprecatedDiagnostic',
JustMyCodeDiagnostic = 'JustMyCodeDiagnostic',
Expand Down
6 changes: 6 additions & 0 deletions src/client/application/diagnostics/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ export interface IDiagnosticCommand {

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

export const IInvalidPythonPathInSettings = Symbol('IInvalidPythonPathInSettings');

export interface IInvalidPythonPathInSettings extends IDiagnosticsService {
validateInterpreterPathInSettings(resource: Resource): Promise<boolean>;
}

export const IInvalidPythonPathInDebuggerService = Symbol('IInvalidPythonPathInDebuggerService');

export interface IInvalidPythonPathInDebuggerService extends IDiagnosticsService {
Expand Down
Loading