Skip to content

Commit b929726

Browse files
author
Kartik Raj
authored
Group interpreters in interpreter quick picker using separators (#18171)
* Group interpreters in interpreter quick picker using separators * Do not group while the list is loading * Cleanup * Ensure full display name does not have arch for non-global type envs * tes * Recommend the same interpreter we autoselect * Fix env resikver tst * Fix display name testS * Fix status bar display * Fix autoselection tests * Fix interpreter selector tests * Fix sorting/do not prioritize conda envs * Recommend Pipenv & Poetry envs * Do not autoselect poetry/pipenv envs * Fix interpreter selector tests * Fix set interpreter tests * News * Add test for grouping * Events to add an environment are handled appropriately if a grouped list is present * Code reviews * Add a new group for recommended interpreter * Localize group names * Fix unit tests * Change quickpick list placeholder
1 parent b13087a commit b929726

File tree

27 files changed

+580
-168
lines changed

27 files changed

+580
-168
lines changed

Diff for: news/1 Enhancements/17944.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Group interpreters in interpreter quick picker using separators.

Diff for: package.nls.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@
5353
"Interpreters.DiscoveringInterpreters": "Discovering Python Interpreters",
5454
"Interpreters.condaInheritEnvMessage": "We noticed you're using a conda environment. If you are experiencing issues with this environment in the integrated terminal, we recommend that you let the Python extension change \"terminal.integrated.inheritEnv\" to false in your user settings.",
5555
"Logging.CurrentWorkingDirectory": "cwd:",
56-
"InterpreterQuickPickList.quickPickListPlaceholder": "Current: {0}",
56+
"InterpreterQuickPickList.workspaceGroupName": "Workspace",
57+
"InterpreterQuickPickList.globalGroupName": "Global",
58+
"InterpreterQuickPickList.quickPickListPlaceholder": "Selected Interpreter: {0}",
5759
"InterpreterQuickPickList.enterPath.label": "Enter interpreter path...",
5860
"InterpreterQuickPickList.enterPath.placeholder": "Enter path to a Python interpreter.",
5961
"InterpreterQuickPickList.refreshInterpreterList": "Refresh Interpreter list",

Diff for: package.nls.zh-cn.json

-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@
4949
"Interpreters.pythonInterpreterPath": "Python 解释器路径: {0}",
5050
"Interpreters.condaInheritEnvMessage": "您正在使用 conda 环境,如果您在集成终端中遇到相关问题,建议您允许 Python 扩展将用户设置中的 \"terminal.integrated.inheritEnv\" 改为 false。",
5151
"Logging.CurrentWorkingDirectory": "cwd:",
52-
"InterpreterQuickPickList.quickPickListPlaceholder": "当前: {0}",
5352
"InterpreterQuickPickList.enterPath.label": "输入解释器路径...",
5453
"InterpreterQuickPickList.enterPath.placeholder": "请输入 Python 解释器的路径。",
5554
"InterpreterQuickPickList.refreshInterpreterList": "刷新解释器列表",

Diff for: package.nls.zh-tw.json

-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@
5151
"Interpreters.entireWorkspace": "完整工作區",
5252
"Interpreters.pythonInterpreterPath": "Python 直譯器路徑: {0}",
5353
"Logging.CurrentWorkingDirectory": "cwd:",
54-
"InterpreterQuickPickList.quickPickListPlaceholder": "目前: {0}",
5554
"InterpreterQuickPickList.enterPath.label": "輸入直譯器路徑...",
5655
"InterpreterQuickPickList.enterPath.placeholder": "輸入 Python 直譯器的路徑。",
5756
"InterpreterQuickPickList.refreshInterpreterList": "重新整理直譯器清單",

Diff for: src/client/common/utils/localize.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -292,9 +292,11 @@ export namespace Interpreters {
292292
}
293293

294294
export namespace InterpreterQuickPickList {
295+
export const globalGroupName = localize('InterpreterQuickPickList.globalGroupName', 'Global');
296+
export const workspaceGroupName = localize('InterpreterQuickPickList.workspaceGroupName', 'Workspace');
295297
export const quickPickListPlaceholder = localize(
296298
'InterpreterQuickPickList.quickPickListPlaceholder',
297-
'Current: {0}',
299+
'Selected Interpreter: {0}',
298300
);
299301
export const enterPath = {
300302
label: localize('InterpreterQuickPickList.enterPath.label', 'Enter interpreter path...'),

Diff for: src/client/interpreter/autoSelection/index.ts

+6-12
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import { compareSemVerLikeVersions } from '../../pythonEnvironments/base/info/py
1414
import { PythonEnvironment } from '../../pythonEnvironments/info';
1515
import { sendTelemetryEvent } from '../../telemetry';
1616
import { EventName } from '../../telemetry/constants';
17-
import { EnvTypeHeuristic, getEnvTypeHeuristic } from '../configuration/environmentTypeComparer';
1817
import { IInterpreterComparer } from '../configuration/types';
1918
import { IInterpreterHelper, IInterpreterService } from '../contracts';
2019
import { IInterpreterAutoSelectionService, IInterpreterAutoSelectionProxyService } from './types';
@@ -204,19 +203,14 @@ export class InterpreterAutoSelectionService implements IInterpreterAutoSelectio
204203
const interpreters = await this.interpreterService.getAllInterpreters(resource);
205204
const workspaceUri = this.interpreterHelper.getActiveWorkspaceUri(resource);
206205

207-
// When auto-selecting an intepreter for a workspace, we either want to return a local one
208-
// or fallback on a globally-installed interpreter, and we don't want want to suggest a global environment
209-
// because we would have to add a way to match environments to a workspace.
210-
const filteredInterpreters = interpreters.filter(
211-
(i) => getEnvTypeHeuristic(i, workspaceUri?.folderUri.fsPath || '') !== EnvTypeHeuristic.Global,
212-
);
213-
214-
filteredInterpreters.sort(this.envTypeComparer.compare.bind(this.envTypeComparer));
215-
206+
const recommendedInterpreter = this.envTypeComparer.getRecommended(interpreters, workspaceUri?.folderUri);
207+
if (!recommendedInterpreter) {
208+
return;
209+
}
216210
if (workspaceUri) {
217-
this.setWorkspaceInterpreter(workspaceUri.folderUri, filteredInterpreters[0]);
211+
this.setWorkspaceInterpreter(workspaceUri.folderUri, recommendedInterpreter);
218212
} else {
219-
this.setGlobalInterpreter(filteredInterpreters[0]);
213+
this.setGlobalInterpreter(recommendedInterpreter);
220214
}
221215

222216
queriedState.updateValue(true);

Diff for: src/client/interpreter/configuration/environmentTypeComparer.ts

+73-44
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,22 @@
33

44
import { injectable, inject } from 'inversify';
55
import { getArchitectureDisplayName } from '../../common/platform/registry';
6+
import { Resource } from '../../common/types';
67
import { isParentPath } from '../../pythonEnvironments/common/externalDependencies';
7-
import { EnvironmentType, PythonEnvironment } from '../../pythonEnvironments/info';
8+
import { EnvironmentType, PythonEnvironment, virtualEnvTypes } from '../../pythonEnvironments/info';
89
import { PythonVersion } from '../../pythonEnvironments/info/pythonVersion';
910
import { IInterpreterHelper } from '../contracts';
1011
import { IInterpreterComparer } from './types';
1112

12-
/*
13-
* Enum description:
14-
* - Local environments (.venv);
15-
* - Global environments (pipenv, conda);
16-
* - Globally-installed interpreters (/usr/bin/python3, Windows Store).
17-
*/
18-
export enum EnvTypeHeuristic {
13+
export enum EnvLocationHeuristic {
14+
/**
15+
* Environments inside the workspace.
16+
*/
1917
Local = 1,
18+
/**
19+
* Environments outside the workspace.
20+
*/
2021
Global = 2,
21-
GlobalInterpreters = 3,
2222
}
2323

2424
@injectable()
@@ -41,8 +41,14 @@ export class EnvironmentTypeComparer implements IInterpreterComparer {
4141
* Always sort with newest version of Python first within each subgroup.
4242
*/
4343
public compare(a: PythonEnvironment, b: PythonEnvironment): number {
44+
// Check environment location.
45+
const envLocationComparison = compareEnvironmentLocation(a, b, this.workspaceFolderPath);
46+
if (envLocationComparison !== 0) {
47+
return envLocationComparison;
48+
}
49+
4450
// Check environment type.
45-
const envTypeComparison = compareEnvironmentType(a, b, this.workspaceFolderPath);
51+
const envTypeComparison = compareEnvironmentType(a, b);
4652
if (envTypeComparison !== 0) {
4753
return envTypeComparison;
4854
}
@@ -53,20 +59,15 @@ export class EnvironmentTypeComparer implements IInterpreterComparer {
5359
return versionComparison;
5460
}
5561

56-
// Prioritize non-Conda environments.
57-
if (isCondaEnvironment(a) && !isCondaEnvironment(b)) {
62+
// If we have the "base" Conda env, put it last in its Python version subgroup.
63+
if (isBaseCondaEnvironment(a)) {
5864
return 1;
5965
}
6066

61-
if (!isCondaEnvironment(a) && isCondaEnvironment(b)) {
67+
if (isBaseCondaEnvironment(b)) {
6268
return -1;
6369
}
6470

65-
// If we have the "base" Conda env, put it last in its Python version subgroup.
66-
if (isBaseCondaEnvironment(a)) {
67-
return 1;
68-
}
69-
7071
// Check alphabetical order.
7172
const nameA = getSortName(a, this.interpreterHelper);
7273
const nameB = getSortName(b, this.interpreterHelper);
@@ -76,6 +77,25 @@ export class EnvironmentTypeComparer implements IInterpreterComparer {
7677

7778
return nameA > nameB ? 1 : -1;
7879
}
80+
81+
public getRecommended(interpreters: PythonEnvironment[], resource: Resource): PythonEnvironment | undefined {
82+
// When recommending an intepreter for a workspace, we either want to return a local one
83+
// or fallback on a globally-installed interpreter, and we don't want want to suggest a global environment
84+
// because we would have to add a way to match environments to a workspace.
85+
const workspaceUri = this.interpreterHelper.getActiveWorkspaceUri(resource);
86+
const filteredInterpreters = interpreters.filter((i) => {
87+
if (getEnvLocationHeuristic(i, workspaceUri?.folderUri.fsPath || '') === EnvLocationHeuristic.Local) {
88+
return true;
89+
}
90+
if (virtualEnvTypes.includes(i.envType)) {
91+
// We're not sure if these envs were created for the workspace, so do not recommend them.
92+
return false;
93+
}
94+
return true;
95+
});
96+
filteredInterpreters.sort(this.compare.bind(this));
97+
return filteredInterpreters.length ? filteredInterpreters[0] : undefined;
98+
}
7999
}
80100

81101
function getSortName(info: PythonEnvironment, interpreterHelper: IInterpreterHelper): string {
@@ -113,12 +133,11 @@ function getSortName(info: PythonEnvironment, interpreterHelper: IInterpreterHel
113133
return `${sortNameParts.join(' ')} ${envSuffix}`.trim();
114134
}
115135

116-
function isCondaEnvironment(environment: PythonEnvironment): boolean {
117-
return environment.envType === EnvironmentType.Conda;
118-
}
119-
120136
function isBaseCondaEnvironment(environment: PythonEnvironment): boolean {
121-
return isCondaEnvironment(environment) && (environment.envName === 'base' || environment.envName === 'miniconda');
137+
return (
138+
environment.envType === EnvironmentType.Conda &&
139+
(environment.envName === 'base' || environment.envName === 'miniconda')
140+
);
122141
}
123142

124143
/**
@@ -151,40 +170,50 @@ function comparePythonVersionDescending(a: PythonVersion | undefined, b: PythonV
151170
}
152171

153172
/**
154-
* Compare 2 environment types: return 0 if they are the same, -1 if a comes before b, 1 otherwise.
173+
* Compare 2 environment locations: return 0 if they are the same, -1 if a comes before b, 1 otherwise.
155174
*/
156-
function compareEnvironmentType(a: PythonEnvironment, b: PythonEnvironment, workspacePath: string): number {
157-
const aHeuristic = getEnvTypeHeuristic(a, workspacePath);
158-
const bHeuristic = getEnvTypeHeuristic(b, workspacePath);
175+
function compareEnvironmentLocation(a: PythonEnvironment, b: PythonEnvironment, workspacePath: string): number {
176+
const aHeuristic = getEnvLocationHeuristic(a, workspacePath);
177+
const bHeuristic = getEnvLocationHeuristic(b, workspacePath);
159178

160179
return Math.sign(aHeuristic - bHeuristic);
161180
}
162181

163182
/**
164183
* Return a heuristic value depending on the environment type.
165184
*/
166-
export function getEnvTypeHeuristic(environment: PythonEnvironment, workspacePath: string): EnvTypeHeuristic {
167-
const { envType } = environment;
168-
185+
export function getEnvLocationHeuristic(environment: PythonEnvironment, workspacePath: string): EnvLocationHeuristic {
169186
if (
170187
workspacePath.length > 0 &&
171188
((environment.envPath && isParentPath(environment.envPath, workspacePath)) ||
172189
(environment.path && isParentPath(environment.path, workspacePath)))
173190
) {
174-
return EnvTypeHeuristic.Local;
191+
return EnvLocationHeuristic.Local;
175192
}
193+
return EnvLocationHeuristic.Global;
194+
}
176195

177-
switch (envType) {
178-
case EnvironmentType.Venv:
179-
case EnvironmentType.Conda:
180-
case EnvironmentType.VirtualEnv:
181-
case EnvironmentType.VirtualEnvWrapper:
182-
case EnvironmentType.Pipenv:
183-
case EnvironmentType.Poetry:
184-
return EnvTypeHeuristic.Global;
185-
// The default case covers global environments.
186-
// For now this includes: pyenv, Windows Store, Global, System and Unknown environment types.
187-
default:
188-
return EnvTypeHeuristic.GlobalInterpreters;
189-
}
196+
/**
197+
* Compare 2 environment types: return 0 if they are the same, -1 if a comes before b, 1 otherwise.
198+
*/
199+
function compareEnvironmentType(a: PythonEnvironment, b: PythonEnvironment): number {
200+
const envTypeByPriority = getPrioritizedEnvironmentType();
201+
return Math.sign(envTypeByPriority.indexOf(a.envType) - envTypeByPriority.indexOf(b.envType));
202+
}
203+
204+
function getPrioritizedEnvironmentType(): EnvironmentType[] {
205+
return [
206+
// Prioritize non-Conda environments.
207+
EnvironmentType.Poetry,
208+
EnvironmentType.Pipenv,
209+
EnvironmentType.VirtualEnvWrapper,
210+
EnvironmentType.Venv,
211+
EnvironmentType.VirtualEnv,
212+
EnvironmentType.Conda,
213+
EnvironmentType.Pyenv,
214+
EnvironmentType.WindowsStore,
215+
EnvironmentType.Global,
216+
EnvironmentType.System,
217+
EnvironmentType.Unknown,
218+
];
190219
}

0 commit comments

Comments
 (0)