Skip to content

Commit b9b2711

Browse files
committed
fix: improve some install selection bugs
1 parent eaa7d58 commit b9b2711

File tree

4 files changed

+68
-57
lines changed

4 files changed

+68
-57
lines changed

src/managers/builtin/pipUtils.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ import { PackageManagement, Pickers, VenvManagerStrings } from '../../common/loc
77
import { PackageManagementOptions, PythonEnvironment, PythonEnvironmentApi, PythonProject } from '../../api';
88
import { findFiles } from '../../common/workspace.apis';
99
import { EXTENSION_ROOT_DIR } from '../../common/constants';
10-
import { Installable, selectFromCommonPackagesToInstall, selectFromInstallableToInstall } from '../common/pickers';
10+
import { selectFromCommonPackagesToInstall, selectFromInstallableToInstall } from '../common/pickers';
1111
import { traceInfo } from '../../common/logging';
12+
import { Installable, mergePackages } from '../common/utils';
1213

1314
async function tomlParse(fsPath: string, log?: LogOutputChannel): Promise<tomljs.JsonMap> {
1415
try {
@@ -145,10 +146,11 @@ export async function getWorkspacePackagesToInstall(
145146
environment?: PythonEnvironment,
146147
): Promise<PipPackages | undefined> {
147148
const installable = (await getProjectInstallable(api, project)) ?? [];
148-
const common = await getCommonPackages();
149+
let common = await getCommonPackages();
149150
let installed: string[] | undefined;
150151
if (environment) {
151152
installed = (await api.getPackages(environment))?.map((pkg) => pkg.name);
153+
common = mergePackages(common, installed ?? []);
152154
}
153155
return selectWorkspaceOrCommon(installable, common, !!options.showSkipOption, installed ?? []);
154156
}

src/managers/common/pickers.ts

Lines changed: 10 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { QuickInputButtons, QuickPickItem, QuickPickItemButtonEvent, QuickPickIt
22
import { Common, PackageManagement } from '../../common/localize';
33
import { launchBrowser } from '../../common/env.apis';
44
import { showInputBoxWithButtons, showQuickPickWithButtons, showTextDocument } from '../../common/window.apis';
5+
import { Installable } from './utils';
56

67
const OPEN_BROWSER_BUTTON = {
78
iconPath: new ThemeIcon('globe'),
@@ -18,50 +19,6 @@ const EDIT_ARGUMENTS_BUTTON = {
1819
tooltip: PackageManagement.editArguments,
1920
};
2021

21-
export interface Installable {
22-
/**
23-
* The name of the package, requirements, lock files, or step name.
24-
*/
25-
readonly name: string;
26-
27-
/**
28-
* The name of the package, requirements, pyproject.toml or any other project file, etc.
29-
*/
30-
readonly displayName: string;
31-
32-
/**
33-
* Arguments passed to the package manager to install the package.
34-
*
35-
* @example
36-
* ['debugpy==1.8.7'] for `pip install debugpy==1.8.7`.
37-
* ['--pre', 'debugpy'] for `pip install --pre debugpy`.
38-
* ['-r', 'requirements.txt'] for `pip install -r requirements.txt`.
39-
*/
40-
readonly args?: string[];
41-
42-
/**
43-
* Installable group name, this will be used to group installable items in the UI.
44-
*
45-
* @example
46-
* `Requirements` for any requirements file.
47-
* `Packages` for any package.
48-
*/
49-
readonly group?: string;
50-
51-
/**
52-
* Description about the installable item. This can also be path to the requirements,
53-
* version of the package, or any other project file path.
54-
*/
55-
readonly description?: string;
56-
57-
/**
58-
* External Uri to the package on pypi or docs.
59-
* @example
60-
* https://pypi.org/project/debugpy/ for `debugpy`.
61-
*/
62-
readonly uri?: Uri;
63-
}
64-
6522
function handleItemButton(uri?: Uri) {
6623
if (uri) {
6724
if (uri.scheme.toLowerCase().startsWith('http')) {
@@ -118,7 +75,7 @@ async function enterPackageManually(filler?: string): Promise<string[] | undefin
11875
}
11976

12077
interface GroupingResult {
121-
items: QuickPickItem[];
78+
items: PackageQuickPickItem[];
12279
installedItems: PackageQuickPickItem[];
12380
}
12481

@@ -132,11 +89,13 @@ function groupByInstalled(items: PackageQuickPickItem[], installed?: string[]):
13289
result.push(i);
13390
}
13491
});
135-
const installedSeparator: QuickPickItem = {
92+
const installedSeparator: PackageQuickPickItem = {
93+
id: 'installed-sep',
13694
label: PackageManagement.installed,
13795
kind: QuickPickItemKind.Separator,
13896
};
139-
const commonPackages: QuickPickItem = {
97+
const commonPackages: PackageQuickPickItem = {
98+
id: 'common-packages-sep',
14099
label: PackageManagement.commonPackages,
141100
kind: QuickPickItemKind.Separator,
142101
};
@@ -171,7 +130,7 @@ export async function selectFromCommonPackagesToInstall(
171130
preSelected?: PackageQuickPickItem[] | undefined,
172131
): Promise<CommonPackagesResult | undefined> {
173132
const { installedItems, items } = groupByInstalled(common.map(installableToQuickPickItem), installed);
174-
const preSelectedItems = [...installedItems, ...(preSelected ?? [])];
133+
const preSelectedItems = items.filter((i) => (preSelected ?? installedItems).some((s) => s.id === i.id));
175134
let selected: PackageQuickPickItem | PackageQuickPickItem[] | undefined;
176135
try {
177136
selected = await showQuickPickWithButtons(
@@ -208,10 +167,8 @@ export async function selectFromCommonPackagesToInstall(
208167

209168
if (selected && Array.isArray(selected)) {
210169
if (selected.find((s) => s.label === PackageManagement.enterPackageNames)) {
211-
const filler = selected
212-
.filter((s) => s.label !== PackageManagement.enterPackageNames)
213-
.map((s) => s.id)
214-
.join(' ');
170+
const filtered = selected.filter((s) => s.label !== PackageManagement.enterPackageNames);
171+
const filler = filtered.map((s) => s.id).join(' ');
215172
try {
216173
const selections = await enterPackageManually(filler);
217174
if (selections) {
@@ -220,7 +177,7 @@ export async function selectFromCommonPackagesToInstall(
220177
return undefined;
221178
} catch (ex) {
222179
if (ex === QuickInputButtons.Back) {
223-
return selectFromCommonPackagesToInstall(common, installed, selected);
180+
return selectFromCommonPackagesToInstall(common, installed, filtered);
224181
}
225182
return undefined;
226183
}

src/managers/common/utils.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,50 @@
11
import * as os from 'os';
22
import { PythonEnvironment } from '../../api';
3+
import { Uri } from 'vscode';
4+
5+
export interface Installable {
6+
/**
7+
* The name of the package, requirements, lock files, or step name.
8+
*/
9+
readonly name: string;
10+
11+
/**
12+
* The name of the package, requirements, pyproject.toml or any other project file, etc.
13+
*/
14+
readonly displayName: string;
15+
16+
/**
17+
* Arguments passed to the package manager to install the package.
18+
*
19+
* @example
20+
* ['debugpy==1.8.7'] for `pip install debugpy==1.8.7`.
21+
* ['--pre', 'debugpy'] for `pip install --pre debugpy`.
22+
* ['-r', 'requirements.txt'] for `pip install -r requirements.txt`.
23+
*/
24+
readonly args?: string[];
25+
26+
/**
27+
* Installable group name, this will be used to group installable items in the UI.
28+
*
29+
* @example
30+
* `Requirements` for any requirements file.
31+
* `Packages` for any package.
32+
*/
33+
readonly group?: string;
34+
35+
/**
36+
* Description about the installable item. This can also be path to the requirements,
37+
* version of the package, or any other project file path.
38+
*/
39+
readonly description?: string;
40+
41+
/**
42+
* External Uri to the package on pypi or docs.
43+
* @example
44+
* https://pypi.org/project/debugpy/ for `debugpy`.
45+
*/
46+
readonly uri?: Uri;
47+
}
348

449
export function isWindows(): boolean {
550
return process.platform === 'win32';
@@ -86,3 +131,10 @@ export function getLatest(collection: PythonEnvironment[]): PythonEnvironment |
86131
}
87132
return latest;
88133
}
134+
135+
export function mergePackages(common: Installable[], installed: string[]): Installable[] {
136+
const notInCommon = installed.filter((pkg) => !common.some((c) => c.name === pkg));
137+
return common
138+
.concat(notInCommon.map((pkg) => ({ name: pkg, displayName: pkg })))
139+
.sort((a, b) => a.name.localeCompare(b.name));
140+
}

src/managers/conda/condaUtils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@ import {
3333
import { getConfiguration } from '../../common/workspace.apis';
3434
import { getGlobalPersistentState, getWorkspacePersistentState } from '../../common/persistentState';
3535
import which from 'which';
36-
import { isWindows, shortVersion, sortEnvironments, untildify } from '../common/utils';
36+
import { Installable, isWindows, shortVersion, sortEnvironments, untildify } from '../common/utils';
3737
import { pickProject } from '../../common/pickers/projects';
3838
import { CondaStrings, PackageManagement, Pickers } from '../../common/localize';
3939
import { showErrorMessage } from '../../common/errors/utils';
4040
import { showInputBox, showQuickPick, showQuickPickWithButtons, withProgress } from '../../common/window.apis';
41-
import { Installable, selectFromCommonPackagesToInstall } from '../common/pickers';
41+
import { selectFromCommonPackagesToInstall } from '../common/pickers';
4242
import { quoteArgs } from '../../features/execution/execUtils';
4343
import { traceInfo } from '../../common/logging';
4444

0 commit comments

Comments
 (0)