Skip to content

Commit ebaba5c

Browse files
Cherry-pick pipenv changes and pythonpath prompt changes to release (#11700)
* Show the prompt again if user clicks on more info (#11664) * Show the prompt again if user clicks on more info * Review feedback * Use Learn more as text for the link. * Leave pipenv in a corner until the user decides to select an interpreter (#11654) * add onSuggestion option * Swap onActivation with onSuggestion * Update unit tests * Remove registration of IPipenvService * Move didTriggerInterpreterSuggestions logic inside pipenv locator * Fix existing unit tests * Add new unit tests * Replace typemoq any param with object * Shorten the tests * Fix warning * Duplicate teardown Co-authored-by: Kim-Adeline Miguel <51720070+kimadeline@users.noreply.github.com>
1 parent 11c0cbf commit ebaba5c

File tree

28 files changed

+315
-239
lines changed

28 files changed

+315
-239
lines changed

package.nls.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -240,9 +240,9 @@
240240
"DataScience.liveShareServiceFailure": "Failure starting '{0}' service during live share connection.",
241241
"DataScience.documentMismatch": "Cannot run cells, duplicate documents for {0} found.",
242242
"DataScience.pythonInteractiveCreateFailed": "Failure to create a 'Python Interactive' window. Try reinstalling the Python extension.",
243-
"diagnostics.removePythonPathSettingsJson": "The setting \"python.pythonPath\" defined in your workspace settings is now deprecated. Do you want us to delete it? This will only remove the \"python.pythonPath\" entry from your settings.json.",
244-
"diagnostics.removePythonPathCodeWorkspace": "The setting \"python.pythonPath\" defined in your workspace settings is now deprecated. Do you want us to delete it? This will only remove the \"python.pythonPath\" entry from your .code-workspace file.",
245-
"diagnostics.removePythonPathCodeWorkspaceAndSettingsJson": "The setting \"python.pythonPath\" defined in your workspace settings is now deprecated. Do you want us to delete it? This will only remove the \"python.pythonPath\" entry from your settings.json and .code-workspace file.",
243+
"diagnostics.removePythonPathSettingsJson": "The setting \"python.pythonPath\" defined in your settings.json is now deprecated. Do you want us to delete it from your settings.json only? [Learn more](https://aka.ms/AA7jfor).",
244+
"diagnostics.removePythonPathCodeWorkspace": "The setting \"python.pythonPath\" defined in your workspace settings is now deprecated. Do you want us to delete it from your .code-workspace file only? [Learn more](https://aka.ms/AA7jfor).",
245+
"diagnostics.removePythonPathCodeWorkspaceAndSettingsJson": "The setting \"python.pythonPath\" defined in your workspace settings is now deprecated. Do you want us to delete it from your .code-workspace file and settings.json? [Learn more](https://aka.ms/AA7jfor).",
246246
"diagnostics.warnSourceMaps": "Source map support is enabled in the Python Extension, this will adversely impact performance of the extension.",
247247
"diagnostics.disableSourceMaps": "Disable Source Map Support",
248248
"diagnostics.warnBeforeEnablingSourceMaps": "Enabling source map support in the Python Extension will adversely impact performance of the extension.",

src/client/activation/activationManager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export class ExtensionActivationManager implements IExtensionActivationManager {
7373
this.experiments.sendTelemetryIfInExperiment(DeprecatePythonPath.control);
7474

7575
// Get latest interpreter list in the background.
76-
this.interpreterService.getInterpreters(resource, { onActivation: true }).ignoreErrors();
76+
this.interpreterService.getInterpreters(resource).ignoreErrors();
7777

7878
await sendActivationTelemetry(this.fileSystem, this.workspaceService, resource);
7979

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ export class InvalidMacPythonInterpreterService extends BaseDiagnosticsService {
101101
return [];
102102
}
103103

104-
const interpreters = await this.interpreterService.getInterpreters(resource, { onActivation: true });
104+
const interpreters = await this.interpreterService.getInterpreters(resource);
105105
if (interpreters.filter((i) => !this.helper.isMacDefaultPythonPath(i.path)).length === 0) {
106106
return [
107107
new InvalidMacPythonInterpreterDiagnostic(

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

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import { IWorkspaceService } from '../../../common/application/types';
99
import { DeprecatePythonPath } from '../../../common/experimentGroups';
1010
import { IDisposableRegistry, IExperimentsManager, Resource } from '../../../common/types';
1111
import { Common, Diagnostics } from '../../../common/utils/localize';
12-
import { learnMoreOnInterpreterSecurityURI } from '../../../interpreter/autoSelection/constants';
1312
import { IServiceContainer } from '../../../ioc/types';
1413
import { BaseDiagnostic, BaseDiagnosticsService } from '../base';
1514
import { IDiagnosticsCommandFactory } from '../commands/types';
@@ -97,13 +96,6 @@ export class PythonPathDeprecatedDiagnosticService extends BaseDiagnosticsServic
9796
{
9897
prompt: Common.noIWillDoItLater()
9998
},
100-
{
101-
prompt: Common.moreInfo(),
102-
command: commandFactory.createCommand(diagnostic, {
103-
type: 'launch',
104-
options: learnMoreOnInterpreterSecurityURI
105-
})
106-
},
10799
{
108100
prompt: Common.doNotShowAgain(),
109101
command: commandFactory.createCommand(diagnostic, { type: 'ignore', options: DiagnosticScope.Global })

src/client/common/terminal/environmentActivationProviders/pipEnvActivationProvider.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,16 @@
33

44
'use strict';
55

6-
import { inject, injectable } from 'inversify';
6+
import { inject, injectable, named } from 'inversify';
77
import { Uri } from 'vscode';
88
import '../../../common/extensions';
9-
import { IInterpreterService, InterpreterType, IPipEnvService } from '../../../interpreter/contracts';
9+
import {
10+
IInterpreterLocatorService,
11+
IInterpreterService,
12+
InterpreterType,
13+
IPipEnvService,
14+
PIPENV_SERVICE
15+
} from '../../../interpreter/contracts';
1016
import { IWorkspaceService } from '../../application/types';
1117
import { IFileSystem } from '../../platform/types';
1218
import { ITerminalActivationCommandProvider, TerminalShellType } from '../types';
@@ -15,7 +21,9 @@ import { ITerminalActivationCommandProvider, TerminalShellType } from '../types'
1521
export class PipEnvActivationCommandProvider implements ITerminalActivationCommandProvider {
1622
constructor(
1723
@inject(IInterpreterService) private readonly interpreterService: IInterpreterService,
18-
@inject(IPipEnvService) private readonly pipenvService: IPipEnvService,
24+
@inject(IInterpreterLocatorService)
25+
@named(PIPENV_SERVICE)
26+
private readonly pipenvService: IPipEnvService,
1927
@inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService,
2028
@inject(IFileSystem) private readonly fs: IFileSystem
2129
) {}

src/client/common/utils/localize.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,15 @@ export namespace Diagnostics {
3232
);
3333
export const removePythonPathSettingsJson = localize(
3434
'diagnostics.removePythonPathSettingsJson',
35-
'The setting "python.pythonPath" defined in your workspace settings is now deprecated. Do you want us to delete it? This will only remove the "python.pythonPath" entry from your settings.json.'
35+
'The setting "python.pythonPath" defined in your settings.json is now deprecated. Do you want us to delete it from your settings.json only? [Learn more](https://aka.ms/AA7jfor).'
3636
);
3737
export const removePythonPathCodeWorkspace = localize(
3838
'diagnostics.removePythonPathCodeWorkspace',
39-
'The setting "python.pythonPath" defined in your workspace settings is now deprecated. Do you want us to delete it? This will only remove the "python.pythonPath" entry from your .code-workspace file.'
39+
'The setting "python.pythonPath" defined in your workspace settings is now deprecated. Do you want us to delete it from your .code-workspace file only? [Learn more](https://aka.ms/AA7jfor).'
4040
);
4141
export const removePythonPathCodeWorkspaceAndSettingsJson = localize(
4242
'diagnostics.removePythonPathCodeWorkspaceAndSettingsJson',
43-
'The setting "python.pythonPath" defined in your workspace settings is now deprecated. Do you want us to delete it? This will only remove the "python.pythonPath" entry from your settings.json and .code-workspace file.'
43+
'The setting "python.pythonPath" defined in your workspace settings is now deprecated. Do you want us to delete it from your .code-workspace file and settings.json? [Learn more](https://aka.ms/AA7jfor).'
4444
);
4545
export const invalidPythonPathInDebuggerSettings = localize(
4646
'diagnostics.invalidPythonPathInDebuggerSettings',

src/client/interpreter/autoSelection/rules/system.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export class SystemWideInterpretersAutoSelectionRule extends BaseRuleService {
2525
resource: Resource,
2626
manager?: IInterpreterAutoSelectionService
2727
): Promise<NextAction> {
28-
const interpreters = await this.interpreterService.getInterpreters(resource, { onActivation: true });
28+
const interpreters = await this.interpreterService.getInterpreters(resource);
2929
// Exclude non-local interpreters.
3030
const filteredInterpreters = interpreters.filter(
3131
(int) =>

src/client/interpreter/configuration/interpreterSelector/interpreterSelector.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export class InterpreterSelector implements IInterpreterSelector {
2727
}
2828

2929
public async getSuggestions(resource: Resource) {
30-
let interpreters = await this.interpreterManager.getInterpreters(resource);
30+
let interpreters = await this.interpreterManager.getInterpreters(resource, { onSuggestion: true });
3131
if (this.experimentsManager.inExperiment(DeprecatePythonPath.experiment)) {
3232
interpreters = interpreters.filter((item) => this.interpreterSecurityService.isSafe(item) !== false);
3333
}

src/client/interpreter/contracts.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export interface IVirtualEnvironmentsSearchPathProvider {
2828
}
2929

3030
export type GetInterpreterOptions = {
31-
onActivation?: boolean;
31+
onSuggestion?: boolean;
3232
};
3333

3434
export type GetInterpreterLocatorOptions = GetInterpreterOptions & { ignoreCache?: boolean };
@@ -38,6 +38,7 @@ export const IInterpreterLocatorService = Symbol('IInterpreterLocatorService');
3838
export interface IInterpreterLocatorService extends Disposable {
3939
readonly onLocating: Event<Promise<PythonInterpreter[]>>;
4040
readonly hasInterpreters: Promise<boolean>;
41+
didTriggerInterpreterSuggestions?: boolean;
4142
getInterpreters(resource?: Uri, options?: GetInterpreterLocatorOptions): Promise<PythonInterpreter[]>;
4243
}
4344

@@ -126,7 +127,7 @@ export interface IInterpreterHelper {
126127
}
127128

128129
export const IPipEnvService = Symbol('IPipEnvService');
129-
export interface IPipEnvService {
130+
export interface IPipEnvService extends IInterpreterLocatorService {
130131
executable: string;
131132
isRelatedPipEnvironment(dir: string, pythonPath: string): Promise<boolean>;
132133
}

src/client/interpreter/locators/index.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,13 @@ const flatten = require('lodash/flatten') as typeof import('lodash/flatten');
3030
*/
3131
@injectable()
3232
export class PythonInterpreterLocatorService implements IInterpreterLocatorService {
33+
public didTriggerInterpreterSuggestions: boolean;
34+
3335
private readonly disposables: Disposable[] = [];
3436
private readonly platform: IPlatformService;
3537
private readonly interpreterLocatorHelper: IInterpreterLocatorHelper;
3638
private readonly _hasInterpreters: Deferred<boolean>;
39+
3740
constructor(
3841
@inject(IServiceContainer) private serviceContainer: IServiceContainer,
3942
@inject(InterpreterFilter) private readonly interpreterFilter: IInterpreterFilter
@@ -42,6 +45,7 @@ export class PythonInterpreterLocatorService implements IInterpreterLocatorServi
4245
serviceContainer.get<Disposable[]>(IDisposableRegistry).push(this);
4346
this.platform = serviceContainer.get<IPlatformService>(IPlatformService);
4447
this.interpreterLocatorHelper = serviceContainer.get<IInterpreterLocatorHelper>(IInterpreterLocatorHelper);
48+
this.didTriggerInterpreterSuggestions = false;
4549
}
4650
/**
4751
* This class should never emit events when we're locating.
@@ -106,18 +110,27 @@ export class PythonInterpreterLocatorService implements IInterpreterLocatorServi
106110
// The order is important because the data sources at the bottom of the list do not contain all,
107111
// the information about the interpreters (e.g. type, environment name, etc).
108112
// This way, the items returned from the top of the list will win, when we combine the items returned.
109-
const keys: [string | undefined, OSType | undefined][] = [
113+
const keys: [string, OSType | undefined][] = [
110114
[WINDOWS_REGISTRY_SERVICE, OSType.Windows],
111115
[CONDA_ENV_SERVICE, undefined],
112116
[CONDA_ENV_FILE_SERVICE, undefined],
113-
options?.onActivation ? [undefined, undefined] : [PIPENV_SERVICE, undefined],
117+
[PIPENV_SERVICE, undefined],
114118
[GLOBAL_VIRTUAL_ENV_SERVICE, undefined],
115119
[WORKSPACE_VIRTUAL_ENV_SERVICE, undefined],
116120
[KNOWN_PATH_SERVICE, undefined],
117121
[CURRENT_PATH_SERVICE, undefined]
118122
];
119-
return keys
120-
.filter((item) => item[0] !== undefined && (item[1] === undefined || item[1] === this.platform.osType))
123+
124+
const locators = keys
125+
.filter((item) => item[1] === undefined || item[1] === this.platform.osType)
121126
.map((item) => this.serviceContainer.get<IInterpreterLocatorService>(IInterpreterLocatorService, item[0]));
127+
128+
// Set it to true the first time the user selects an interpreter
129+
if (!this.didTriggerInterpreterSuggestions && options?.onSuggestion === true) {
130+
this.didTriggerInterpreterSuggestions = true;
131+
locators.forEach((locator) => (locator.didTriggerInterpreterSuggestions = true));
132+
}
133+
134+
return locators;
122135
}
123136
}

src/client/interpreter/locators/services/cacheableLocatorService.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,24 @@ export abstract class CacheableLocatorService implements IInterpreterLocatorServ
7070
private readonly handlersAddedToResource = new Set<string>();
7171
private readonly cacheKeyPrefix: string;
7272
private readonly locating = new EventEmitter<Promise<PythonInterpreter[]>>();
73+
private _didTriggerInterpreterSuggestions: boolean;
74+
7375
constructor(
7476
@unmanaged() private readonly name: string,
7577
@unmanaged() protected readonly serviceContainer: IServiceContainer,
7678
@unmanaged() private cachePerWorkspace: boolean = false
7779
) {
7880
this._hasInterpreters = createDeferred<boolean>();
7981
this.cacheKeyPrefix = `INTERPRETERS_CACHE_v3_${name}`;
82+
this._didTriggerInterpreterSuggestions = false;
83+
}
84+
85+
public get didTriggerInterpreterSuggestions(): boolean {
86+
return this._didTriggerInterpreterSuggestions;
87+
}
88+
89+
public set didTriggerInterpreterSuggestions(value: boolean) {
90+
this._didTriggerInterpreterSuggestions = value;
8091
}
8192

8293
public get onLocating(): Event<Promise<PythonInterpreter[]>> {

src/client/interpreter/locators/services/pipEnvService.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,15 @@ export class PipEnvService extends CacheableLocatorService implements IPipEnvSer
4343
this.configService = this.serviceContainer.get<IConfigurationService>(IConfigurationService);
4444
this.pipEnvServiceHelper = this.serviceContainer.get<IPipEnvServiceHelper>(IPipEnvServiceHelper);
4545
}
46+
4647
// tslint:disable-next-line:no-empty
4748
public dispose() {}
49+
4850
public async isRelatedPipEnvironment(dir: string, pythonPath: string): Promise<boolean> {
51+
if (!this.didTriggerInterpreterSuggestions) {
52+
return false;
53+
}
54+
4955
// In PipEnv, the name of the cwd is used as a prefix in the virtual env.
5056
if (pythonPath.indexOf(`${path.sep}${path.basename(dir)}-`) === -1) {
5157
return false;
@@ -55,10 +61,14 @@ export class PipEnvService extends CacheableLocatorService implements IPipEnvSer
5561
}
5662

5763
public get executable(): string {
58-
return this.configService.getSettings().pipenvPath;
64+
return this.didTriggerInterpreterSuggestions ? this.configService.getSettings().pipenvPath : '';
5965
}
6066

6167
public async getInterpreters(resource?: Uri, options?: GetInterpreterLocatorOptions): Promise<PythonInterpreter[]> {
68+
if (!this.didTriggerInterpreterSuggestions) {
69+
return [];
70+
}
71+
6272
const stopwatch = new StopWatch();
6373
const startDiscoveryTime = stopwatch.elapsedTime;
6474

@@ -71,6 +81,10 @@ export class PipEnvService extends CacheableLocatorService implements IPipEnvSer
7181
}
7282

7383
protected getInterpretersImplementation(resource?: Uri): Promise<PythonInterpreter[]> {
84+
if (!this.didTriggerInterpreterSuggestions) {
85+
return Promise.resolve([]);
86+
}
87+
7488
const pipenvCwd = this.getPipenvWorkingDirectory(resource);
7589
if (!pipenvCwd) {
7690
return Promise.resolve([]);
@@ -146,6 +160,7 @@ export class PipEnvService extends CacheableLocatorService implements IPipEnvSer
146160
}
147161
}
148162
}
163+
149164
private async checkIfPipFileExists(cwd: string): Promise<boolean> {
150165
const currentProcess = this.serviceContainer.get<ICurrentProcess>(ICurrentProcess);
151166
const pipFileName = currentProcess.env[pipEnvFileNameVariable];

src/client/interpreter/serviceRegistry.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ import {
6060
IInterpreterWatcherBuilder,
6161
IKnownSearchPathsForInterpreters,
6262
INTERPRETER_LOCATOR_SERVICE,
63-
IPipEnvService,
6463
IShebangCodeLensProvider,
6564
IVirtualEnvironmentsSearchPathProvider,
6665
KNOWN_PATH_SERVICE,
@@ -200,7 +199,6 @@ export function registerInterpreterTypes(serviceManager: IServiceManager) {
200199
WORKSPACE_VIRTUAL_ENV_SERVICE
201200
);
202201
serviceManager.addSingleton<IInterpreterLocatorService>(IInterpreterLocatorService, PipEnvService, PIPENV_SERVICE);
203-
serviceManager.addSingleton<IInterpreterLocatorService>(IPipEnvService, PipEnvService);
204202

205203
serviceManager.addSingleton<IInterpreterLocatorService>(
206204
IInterpreterLocatorService,

src/client/interpreter/virtualEnvs/index.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { ICurrentProcess, IPathUtils } from '../../common/types';
1212
import { getNamesAndValues } from '../../common/utils/enum';
1313
import { noop } from '../../common/utils/misc';
1414
import { IServiceContainer } from '../../ioc/types';
15-
import { InterpreterType, IPipEnvService } from '../contracts';
15+
import { IInterpreterLocatorService, InterpreterType, IPipEnvService, PIPENV_SERVICE } from '../contracts';
1616
import { IVirtualEnvironmentManager } from './types';
1717

1818
const PYENVFILES = ['pyvenv.cfg', path.join('..', 'pyvenv.cfg')];
@@ -27,7 +27,10 @@ export class VirtualEnvironmentManager implements IVirtualEnvironmentManager {
2727
constructor(@inject(IServiceContainer) private readonly serviceContainer: IServiceContainer) {
2828
this.processServiceFactory = serviceContainer.get<IProcessServiceFactory>(IProcessServiceFactory);
2929
this.fs = serviceContainer.get<IFileSystem>(IFileSystem);
30-
this.pipEnvService = serviceContainer.get<IPipEnvService>(IPipEnvService);
30+
this.pipEnvService = serviceContainer.get<IInterpreterLocatorService>(
31+
IInterpreterLocatorService,
32+
PIPENV_SERVICE
33+
) as IPipEnvService;
3134
this.workspaceService = serviceContainer.get<IWorkspaceService>(IWorkspaceService);
3235
}
3336
public async getEnvironmentName(pythonPath: string, resource?: Uri): Promise<string> {
@@ -37,6 +40,7 @@ export class VirtualEnvironmentManager implements IVirtualEnvironmentManager {
3740
const workspaceFolder = resource ? this.workspaceService.getWorkspaceFolder(resource) : undefined;
3841
const workspaceUri = workspaceFolder ? workspaceFolder.uri : defaultWorkspaceUri;
3942
const grandParentDirName = path.basename(path.dirname(path.dirname(pythonPath)));
43+
4044
if (workspaceUri && (await this.pipEnvService.isRelatedPipEnvironment(workspaceUri.fsPath, pythonPath))) {
4145
// In pipenv, return the folder name of the workspace.
4246
return path.basename(workspaceUri.fsPath);

src/client/startupTelemetry.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,7 @@ async function getActivationTelemetryProps(serviceContainer: IServiceContainer):
129129
.then((ver) => (ver ? ver.raw : ''))
130130
.catch<string>(() => ''),
131131
interpreterService.getActiveInterpreter().catch<PythonInterpreter | undefined>(() => undefined),
132-
interpreterService
133-
.getInterpreters(mainWorkspaceUri, { onActivation: true })
134-
.catch<PythonInterpreter[]>(() => [])
132+
interpreterService.getInterpreters(mainWorkspaceUri).catch<PythonInterpreter[]>(() => [])
135133
]);
136134
const workspaceFolderCount = workspaceService.hasWorkspaceFolders ? workspaceService.workspaceFolders!.length : 0;
137135
const pythonVersion = interpreter && interpreter.version ? interpreter.version.raw : undefined;

0 commit comments

Comments
 (0)