Skip to content

Commit d0625be

Browse files
authored
Display a message with options when default Mac interpreter is selected (#2570)
* Display a message with options when default Mac interpreter is selected * Fix test * Fix code review comments
1 parent bcd7dfd commit d0625be

10 files changed

Lines changed: 431 additions & 205 deletions

File tree

news/1 Enhancements/1689.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Display a message with options when user selects the default Mac OS Python Interpreter.
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
'use strict';
5+
6+
import { inject, injectable } from 'inversify';
7+
import { DiagnosticSeverity } from 'vscode';
8+
import '../../../common/extensions';
9+
import { IPlatformService } from '../../../common/platform/types';
10+
import { IConfigurationService } from '../../../common/types';
11+
import { IInterpreterHelper, IInterpreterService, InterpreterType } from '../../../interpreter/contracts';
12+
import { IServiceContainer } from '../../../ioc/types';
13+
import { BaseDiagnostic, BaseDiagnosticsService } from '../base';
14+
import { IDiagnosticsCommandFactory } from '../commands/types';
15+
import { DiagnosticCodes } from '../constants';
16+
import { DiagnosticCommandPromptHandlerServiceId, MessageCommandPrompt } from '../promptHandler';
17+
import { DiagnosticScope, IDiagnostic, IDiagnosticCommand, IDiagnosticHandlerService } from '../types';
18+
19+
const messages = {
20+
[DiagnosticCodes.NoPythonInterpretersDiagnostic]: 'Python is not installed. Please download and install Python before using the extension.',
21+
[DiagnosticCodes.MacInterpreterSelectedAndHaveOtherInterpretersDiagnostic]: 'You have selected the macOS system install of Python, which is not not recommended for use with the Python extension. Some functionality will be limited, please select a different interpreter.',
22+
[DiagnosticCodes.MacInterpreterSelectedAndNoOtherInterpretersDiagnostic]: 'The macOS system install of Python is not recommended, some functionality in the extension will be limited. Install another version of Python for the best experience.'
23+
};
24+
25+
export class InvalidPythonInterpreterDiagnostic extends BaseDiagnostic {
26+
constructor(code: DiagnosticCodes) {
27+
super(code, messages[code], DiagnosticSeverity.Error, DiagnosticScope.WorkspaceFolder);
28+
}
29+
}
30+
31+
export const InvalidPythonInterpreterServiceId = 'InvalidPythonInterpreterServiceId';
32+
33+
@injectable()
34+
export class InvalidPythonInterpreterService extends BaseDiagnosticsService {
35+
constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) {
36+
super([DiagnosticCodes.NoPythonInterpretersDiagnostic,
37+
DiagnosticCodes.MacInterpreterSelectedAndHaveOtherInterpretersDiagnostic,
38+
DiagnosticCodes.MacInterpreterSelectedAndNoOtherInterpretersDiagnostic], serviceContainer);
39+
}
40+
public async diagnose(): Promise<IDiagnostic[]> {
41+
const configurationService = this.serviceContainer.get<IConfigurationService>(IConfigurationService);
42+
const settings = configurationService.getSettings();
43+
if (settings.disableInstallationChecks === true) {
44+
return [];
45+
}
46+
47+
const interpreterService = this.serviceContainer.get<IInterpreterService>(IInterpreterService);
48+
const interpreters = await interpreterService.getInterpreters();
49+
50+
if (interpreters.length === 0) {
51+
return [new InvalidPythonInterpreterDiagnostic(DiagnosticCodes.NoPythonInterpretersDiagnostic)];
52+
}
53+
54+
const platform = this.serviceContainer.get<IPlatformService>(IPlatformService);
55+
if (!platform.isMac) {
56+
return [];
57+
}
58+
59+
const helper = this.serviceContainer.get<IInterpreterHelper>(IInterpreterHelper);
60+
if (!helper.isMacDefaultPythonPath(settings.pythonPath)) {
61+
return [];
62+
}
63+
const interpreter = await interpreterService.getActiveInterpreter();
64+
if (!interpreter || interpreter.type !== InterpreterType.Unknown) {
65+
return [];
66+
}
67+
if (interpreters.filter(i => !helper.isMacDefaultPythonPath(i.path)).length === 0) {
68+
return [new InvalidPythonInterpreterDiagnostic(DiagnosticCodes.MacInterpreterSelectedAndNoOtherInterpretersDiagnostic)];
69+
}
70+
71+
return [new InvalidPythonInterpreterDiagnostic(DiagnosticCodes.MacInterpreterSelectedAndHaveOtherInterpretersDiagnostic)];
72+
}
73+
public async handle(diagnostics: IDiagnostic[]): Promise<void> {
74+
if (diagnostics.length === 0) {
75+
return;
76+
}
77+
const messageService = this.serviceContainer.get<IDiagnosticHandlerService<MessageCommandPrompt>>(IDiagnosticHandlerService, DiagnosticCommandPromptHandlerServiceId);
78+
await Promise.all(diagnostics.map(async diagnostic => {
79+
if (!this.canHandle(diagnostic)) {
80+
return;
81+
}
82+
const commandPrompts = this.getCommandPrompts(diagnostic);
83+
return messageService.handle(diagnostic, { commandPrompts, message: diagnostic.message });
84+
}));
85+
}
86+
private getCommandPrompts(diagnostic: IDiagnostic): { prompt: string; command?: IDiagnosticCommand }[] {
87+
const commandFactory = this.serviceContainer.get<IDiagnosticsCommandFactory>(IDiagnosticsCommandFactory);
88+
switch (diagnostic.code) {
89+
case DiagnosticCodes.NoPythonInterpretersDiagnostic: {
90+
return [{
91+
prompt: 'Download',
92+
command: commandFactory.createCommand(diagnostic, { type: 'launch', options: 'https://www.python.org/downloads' })
93+
}];
94+
}
95+
case DiagnosticCodes.MacInterpreterSelectedAndHaveOtherInterpretersDiagnostic: {
96+
return [{
97+
prompt: 'Select Python Interpreter',
98+
command: commandFactory.createCommand(diagnostic, { type: 'executeVSCCommand', options: 'python.setInterpreter' })
99+
}];
100+
}
101+
case DiagnosticCodes.MacInterpreterSelectedAndNoOtherInterpretersDiagnostic: {
102+
return [{
103+
prompt: 'Learn more',
104+
command: commandFactory.createCommand(diagnostic, { type: 'launch', options: 'https://code.visualstudio.com/docs/python/python-tutorial#_prerequisites' })
105+
},
106+
{
107+
prompt: 'Download',
108+
command: commandFactory.createCommand(diagnostic, { type: 'launch', options: 'https://www.python.org/downloads' })
109+
}];
110+
}
111+
default: {
112+
throw new Error('Invalid diagnostic for \'InvalidPythonInterpreterService\'');
113+
}
114+
}
115+
}
116+
}

src/client/application/diagnostics/constants.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,8 @@
66
export enum DiagnosticCodes {
77
InvalidEnvironmentPathVariableDiagnostic = 'InvalidEnvironmentPathVariableDiagnostic',
88
InvalidDebuggerTypeDiagnostic = 'InvalidDebuggerTypeDiagnostic',
9-
InvalidPythonPathInDebuggerDiagnostic = 'InvalidPythonPathInDebuggerDiagnostic'
9+
InvalidPythonPathInDebuggerDiagnostic = 'InvalidPythonPathInDebuggerDiagnostic',
10+
NoPythonInterpretersDiagnostic = 'NoPythonInterpretersDiagnostic',
11+
MacInterpreterSelectedAndNoOtherInterpretersDiagnostic = 'MacInterpreterSelectedAndNoOtherInterpretersDiagnostic',
12+
MacInterpreterSelectedAndHaveOtherInterpretersDiagnostic = 'MacInterpreterSelectedAndHaveOtherInterpretersDiagnostic'
1013
}

src/client/application/diagnostics/serviceRegistry.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { IServiceManager } from '../../ioc/types';
77
import { EnvironmentPathVariableDiagnosticsService, EnvironmentPathVariableDiagnosticsServiceId } from './checks/envPathVariable';
88
import { InvalidDebuggerTypeDiagnosticsService, InvalidDebuggerTypeDiagnosticsServiceId } from './checks/invalidDebuggerType';
99
import { InvalidPythonPathInDebuggerService, InvalidPythonPathInDebuggerServiceId } from './checks/invalidPythonPathInDebugger';
10+
import { InvalidPythonInterpreterService, InvalidPythonInterpreterServiceId } from './checks/pythonInterpreter';
1011
import { DiagnosticsCommandFactory } from './commands/factory';
1112
import { IDiagnosticsCommandFactory } from './commands/types';
1213
import { DiagnosticFilterService } from './filter';
@@ -19,5 +20,6 @@ export function registerTypes(serviceManager: IServiceManager) {
1920
serviceManager.addSingleton<IDiagnosticsService>(IDiagnosticsService, EnvironmentPathVariableDiagnosticsService, EnvironmentPathVariableDiagnosticsServiceId);
2021
serviceManager.addSingleton<IDiagnosticsService>(IDiagnosticsService, InvalidDebuggerTypeDiagnosticsService, InvalidDebuggerTypeDiagnosticsServiceId);
2122
serviceManager.addSingleton<IDiagnosticsService>(IDiagnosticsService, InvalidPythonPathInDebuggerService, InvalidPythonPathInDebuggerServiceId);
23+
serviceManager.addSingleton<IDiagnosticsService>(IDiagnosticsService, InvalidPythonInterpreterService, InvalidPythonInterpreterServiceId);
2224
serviceManager.addSingleton<IDiagnosticsCommandFactory>(IDiagnosticsCommandFactory, DiagnosticsCommandFactory);
2325
}

src/client/common/installer/pythonInstallation.ts

Lines changed: 0 additions & 48 deletions
This file was deleted.

src/client/extension.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,7 @@ import { IExtensionApi } from './api';
1818
import { registerTypes as appRegisterTypes } from './application/serviceRegistry';
1919
import { IApplicationDiagnostics } from './application/types';
2020
import { IWorkspaceService } from './common/application/types';
21-
import { PythonSettings } from './common/configSettings';
2221
import { isTestExecution, PYTHON, PYTHON_LANGUAGE, STANDARD_OUTPUT_CHANNEL } from './common/constants';
23-
import { PythonInstaller } from './common/installer/pythonInstallation';
2422
import { registerTypes as installerRegisterTypes } from './common/installer/serviceRegistry';
2523
import { registerTypes as platformRegisterTypes } from './common/platform/serviceRegistry';
2624
import { registerTypes as processRegisterTypes } from './common/process/serviceRegistry';
@@ -74,6 +72,7 @@ export async function activate(context: ExtensionContext): Promise<IExtensionApi
7472
const serviceManager = new ServiceManager(cont);
7573
const serviceContainer = new ServiceContainer(cont);
7674
registerServices(context, serviceManager, serviceContainer);
75+
initializeServices(context, serviceManager, serviceContainer);
7776

7877
// When testing, do not perform health checks, as modal dialogs can be displayed.
7978
if (!isTestExecution()) {
@@ -82,8 +81,6 @@ export async function activate(context: ExtensionContext): Promise<IExtensionApi
8281
}
8382

8483
const interpreterManager = serviceContainer.get<IInterpreterService>(IInterpreterService);
85-
// This must be completed before we can continue as language server needs the interpreter path.
86-
interpreterManager.initialize();
8784
await interpreterManager.autoSetInterpreter();
8885

8986
serviceManager.get<ITerminalAutoActivation>(ITerminalAutoActivation).register();
@@ -103,10 +100,6 @@ export async function activate(context: ExtensionContext): Promise<IExtensionApi
103100
serviceManager.get<ICodeExecutionManager>(ICodeExecutionManager).registerCommands();
104101
sendStartupTelemetry(activationDeferred.promise, serviceContainer).ignoreErrors();
105102

106-
const pythonInstaller = new PythonInstaller(serviceContainer);
107-
pythonInstaller.checkPythonInstallation(PythonSettings.getInstance())
108-
.catch(ex => console.error('Python Extension: pythonInstaller.checkPythonInstallation', ex));
109-
110103
interpreterManager.refresh()
111104
.catch(ex => console.error('Python Extension: interpreterManager.refresh', ex));
112105

@@ -152,7 +145,6 @@ export async function activate(context: ExtensionContext): Promise<IExtensionApi
152145
deprecationMgr.initialize();
153146
context.subscriptions.push(deprecationMgr);
154147

155-
context.subscriptions.push(serviceContainer.get<IInterpreterSelector>(IInterpreterSelector));
156148
context.subscriptions.push(activateUpdateSparkLibraryProvider());
157149

158150
context.subscriptions.push(new ReplProvider(serviceContainer));
@@ -201,6 +193,15 @@ function registerServices(context: ExtensionContext, serviceManager: ServiceMana
201193
providersRegisterTypes(serviceManager);
202194
}
203195

196+
function initializeServices(context: ExtensionContext, serviceManager: ServiceManager, serviceContainer: ServiceContainer) {
197+
const selector = serviceContainer.get<IInterpreterSelector>(IInterpreterSelector);
198+
selector.initialize();
199+
context.subscriptions.push(selector);
200+
201+
const interpreterManager = serviceContainer.get<IInterpreterService>(IInterpreterService);
202+
interpreterManager.initialize();
203+
}
204+
204205
async function sendStartupTelemetry(activatedPromise: Promise<void>, serviceContainer: IServiceContainer) {
205206
const logger = serviceContainer.get<ILogger>(ILogger);
206207
try {

src/client/interpreter/configuration/interpreterSelector.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,17 @@ export class InterpreterSelector implements IInterpreterSelector {
2929
this.documentManager = this.serviceContainer.get<IDocumentManager>(IDocumentManager);
3030
this.pathUtils = this.serviceContainer.get<IPathUtils>(IPathUtils);
3131
this.interpreterComparer = this.serviceContainer.get<IInterpreterComparer>(IInterpreterComparer);
32-
33-
const commandManager = serviceContainer.get<ICommandManager>(ICommandManager);
34-
this.disposables.push(commandManager.registerCommand(Commands.Set_Interpreter, this.setInterpreter.bind(this)));
35-
this.disposables.push(commandManager.registerCommand(Commands.Set_ShebangInterpreter, this.setShebangInterpreter.bind(this)));
3632
}
3733
public dispose() {
3834
this.disposables.forEach(disposable => disposable.dispose());
3935
}
4036

37+
public initialize() {
38+
const commandManager = this.serviceContainer.get<ICommandManager>(ICommandManager);
39+
this.disposables.push(commandManager.registerCommand(Commands.Set_Interpreter, this.setInterpreter.bind(this)));
40+
this.disposables.push(commandManager.registerCommand(Commands.Set_ShebangInterpreter, this.setShebangInterpreter.bind(this)));
41+
}
42+
4143
public async getSuggestions(resourceUri?: Uri) {
4244
const interpreters = await this.interpreterManager.getInterpreters(resourceUri);
4345
interpreters.sort(this.interpreterComparer.compare.bind(this.interpreterComparer));

src/client/interpreter/configuration/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export interface IPythonPathUpdaterServiceManager {
1919

2020
export const IInterpreterSelector = Symbol('IInterpreterSelector');
2121
export interface IInterpreterSelector extends Disposable {
22-
22+
initialize(): void;
2323
}
2424

2525
export const IInterpreterComparer = Symbol('IInterpreterComparer');

0 commit comments

Comments
 (0)