Skip to content

Commit 221b769

Browse files
authored
1 parent 9ebc5eb commit 221b769

File tree

4 files changed

+112
-12
lines changed

4 files changed

+112
-12
lines changed

src/client/common/utils/localize.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,7 @@ export namespace CreateEnv {
475475
);
476476
export const deletingEnvironmentProgress = l10n.t('Deleting existing ".venv" environment...');
477477
export const errorDeletingEnvironment = l10n.t('Error while deleting existing ".venv" environment.');
478+
export const openRequirementsFile = l10n.t('Open requirements file');
478479
}
479480

480481
export namespace Conda {

src/client/common/vscodeApis/windowApis.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,15 @@ import {
1616
TextEditor,
1717
window,
1818
Disposable,
19+
QuickPickItemButtonEvent,
20+
Uri,
1921
} from 'vscode';
2022
import { createDeferred, Deferred } from '../utils/async';
2123

24+
export function showTextDocument(uri: Uri): Thenable<TextEditor> {
25+
return window.showTextDocument(uri);
26+
}
27+
2228
export function showQuickPick<T extends QuickPickItem>(
2329
items: readonly T[] | Thenable<readonly T[]>,
2430
options?: QuickPickOptions,
@@ -91,6 +97,7 @@ export async function showQuickPickWithBack<T extends QuickPickItem>(
9197
items: readonly T[],
9298
options?: QuickPickOptions,
9399
token?: CancellationToken,
100+
itemButtonHandler?: (e: QuickPickItemButtonEvent<T>) => void,
94101
): Promise<T | T[] | undefined> {
95102
const quickPick: QuickPick<T> = window.createQuickPick<T>();
96103
const disposables: Disposable[] = [quickPick];
@@ -130,6 +137,11 @@ export async function showQuickPickWithBack<T extends QuickPickItem>(
130137
deferred.resolve(undefined);
131138
}
132139
}),
140+
quickPick.onDidTriggerItemButton((e) => {
141+
if (itemButtonHandler) {
142+
itemButtonHandler(e);
143+
}
144+
}),
133145
);
134146
if (token) {
135147
disposables.push(

src/client/pythonEnvironments/creation/provider/venvUtils.ts

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,22 @@ import * as tomljs from '@iarna/toml';
55
import * as fs from 'fs-extra';
66
import { flatten, isArray } from 'lodash';
77
import * as path from 'path';
8-
import { CancellationToken, ProgressLocation, QuickPickItem, RelativePattern, WorkspaceFolder } from 'vscode';
8+
import {
9+
CancellationToken,
10+
ProgressLocation,
11+
QuickPickItem,
12+
QuickPickItemButtonEvent,
13+
RelativePattern,
14+
ThemeIcon,
15+
Uri,
16+
WorkspaceFolder,
17+
} from 'vscode';
918
import { Common, CreateEnv } from '../../../common/utils/localize';
1019
import {
1120
MultiStepAction,
1221
MultiStepNode,
1322
showQuickPickWithBack,
23+
showTextDocument,
1424
withProgress,
1525
} from '../../../common/vscodeApis/windowApis';
1626
import { findFiles } from '../../../common/vscodeApis/workspaceApis';
@@ -20,6 +30,10 @@ import { isWindows } from '../../../common/platform/platformService';
2030
import { getVenvPath, hasVenv } from '../common/commonUtils';
2131
import { deleteEnvironmentNonWindows, deleteEnvironmentWindows } from './venvDeleteUtils';
2232

33+
export const OPEN_REQUIREMENTS_BUTTON = {
34+
iconPath: new ThemeIcon('go-to-file'),
35+
tooltip: CreateEnv.Venv.openRequirementsFile,
36+
};
2337
const exclude = '**/{.venv*,.git,.nox,.tox,.conda,site-packages,__pypackages__}/**';
2438
async function getPipRequirementsFiles(
2539
workspaceFolder: WorkspaceFolder,
@@ -78,8 +92,13 @@ async function pickTomlExtras(extras: string[], token?: CancellationToken): Prom
7892
return undefined;
7993
}
8094

81-
async function pickRequirementsFiles(files: string[], token?: CancellationToken): Promise<string[] | undefined> {
95+
async function pickRequirementsFiles(
96+
files: string[],
97+
root: string,
98+
token?: CancellationToken,
99+
): Promise<string[] | undefined> {
82100
const items: QuickPickItem[] = files
101+
.map((p) => path.relative(root, p))
83102
.sort((a, b) => {
84103
const al: number = a.split(/[\\\/]/).length;
85104
const bl: number = b.split(/[\\\/]/).length;
@@ -91,7 +110,10 @@ async function pickRequirementsFiles(files: string[], token?: CancellationToken)
91110
}
92111
return al - bl;
93112
})
94-
.map((e) => ({ label: e }));
113+
.map((e) => ({
114+
label: e,
115+
buttons: [OPEN_REQUIREMENTS_BUTTON],
116+
}));
95117

96118
const selection = await showQuickPickWithBack(
97119
items,
@@ -101,6 +123,11 @@ async function pickRequirementsFiles(files: string[], token?: CancellationToken)
101123
canPickMany: true,
102124
},
103125
token,
126+
async (e: QuickPickItemButtonEvent<QuickPickItem>) => {
127+
if (e.item.label) {
128+
await showTextDocument(Uri.file(path.join(root, e.item.label)));
129+
}
130+
},
104131
);
105132

106133
if (selection && isArray(selection)) {
@@ -195,14 +222,11 @@ export async function pickPackagesToInstall(
195222
tomlStep,
196223
async (context?: MultiStepAction) => {
197224
traceVerbose('Looking for pip requirements.');
198-
const requirementFiles = (await getPipRequirementsFiles(workspaceFolder, token))?.map((p) =>
199-
path.relative(workspaceFolder.uri.fsPath, p),
200-
);
201-
225+
const requirementFiles = await getPipRequirementsFiles(workspaceFolder, token);
202226
if (requirementFiles && requirementFiles.length > 0) {
203227
traceVerbose('Found pip requirements.');
204228
try {
205-
const result = await pickRequirementsFiles(requirementFiles, token);
229+
const result = await pickRequirementsFiles(requirementFiles, workspaceFolder.uri.fsPath, token);
206230
const installList = result?.map((p) => path.join(workspaceFolder.uri.fsPath, p));
207231
if (installList) {
208232
installList.forEach((i) => {

src/test/pythonEnvironments/creation/provider/venvUtils.unit.test.ts

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@ import * as windowApis from '../../../../client/common/vscodeApis/windowApis';
1010
import * as workspaceApis from '../../../../client/common/vscodeApis/workspaceApis';
1111
import {
1212
ExistingVenvAction,
13+
OPEN_REQUIREMENTS_BUTTON,
1314
pickExistingVenvAction,
1415
pickPackagesToInstall,
1516
} from '../../../../client/pythonEnvironments/creation/provider/venvUtils';
1617
import { EXTENSION_ROOT_DIR_FOR_TESTS } from '../../../constants';
1718
import { CreateEnv } from '../../../../client/common/utils/localize';
19+
import { createDeferred } from '../../../../client/common/utils/async';
1820

1921
chaiUse(chaiAsPromised);
2022

@@ -23,6 +25,7 @@ suite('Venv Utils test', () => {
2325
let showQuickPickWithBackStub: sinon.SinonStub;
2426
let pathExistsStub: sinon.SinonStub;
2527
let readFileStub: sinon.SinonStub;
28+
let showTextDocumentStub: sinon.SinonStub;
2629

2730
const workspace1 = {
2831
uri: Uri.file(path.join(EXTENSION_ROOT_DIR_FOR_TESTS, 'src', 'testMultiRootWkspc', 'workspace1')),
@@ -35,6 +38,7 @@ suite('Venv Utils test', () => {
3538
showQuickPickWithBackStub = sinon.stub(windowApis, 'showQuickPickWithBack');
3639
pathExistsStub = sinon.stub(fs, 'pathExists');
3740
readFileStub = sinon.stub(fs, 'readFile');
41+
showTextDocumentStub = sinon.stub(windowApis, 'showTextDocument');
3842
});
3943

4044
teardown(() => {
@@ -224,13 +228,18 @@ suite('Venv Utils test', () => {
224228
await assert.isRejected(pickPackagesToInstall(workspace1));
225229
assert.isTrue(
226230
showQuickPickWithBackStub.calledWithExactly(
227-
[{ label: 'requirements.txt' }, { label: 'dev-requirements.txt' }, { label: 'test-requirements.txt' }],
231+
[
232+
{ label: 'requirements.txt', buttons: [OPEN_REQUIREMENTS_BUTTON] },
233+
{ label: 'dev-requirements.txt', buttons: [OPEN_REQUIREMENTS_BUTTON] },
234+
{ label: 'test-requirements.txt', buttons: [OPEN_REQUIREMENTS_BUTTON] },
235+
],
228236
{
229237
placeHolder: CreateEnv.Venv.requirementsQuickPickTitle,
230238
ignoreFocusOut: true,
231239
canPickMany: true,
232240
},
233241
undefined,
242+
sinon.match.func,
234243
),
235244
);
236245
assert.isTrue(readFileStub.calledOnce);
@@ -257,13 +266,18 @@ suite('Venv Utils test', () => {
257266
const actual = await pickPackagesToInstall(workspace1);
258267
assert.isTrue(
259268
showQuickPickWithBackStub.calledWithExactly(
260-
[{ label: 'requirements.txt' }, { label: 'dev-requirements.txt' }, { label: 'test-requirements.txt' }],
269+
[
270+
{ label: 'requirements.txt', buttons: [OPEN_REQUIREMENTS_BUTTON] },
271+
{ label: 'dev-requirements.txt', buttons: [OPEN_REQUIREMENTS_BUTTON] },
272+
{ label: 'test-requirements.txt', buttons: [OPEN_REQUIREMENTS_BUTTON] },
273+
],
261274
{
262275
placeHolder: CreateEnv.Venv.requirementsQuickPickTitle,
263276
ignoreFocusOut: true,
264277
canPickMany: true,
265278
},
266279
undefined,
280+
sinon.match.func,
267281
),
268282
);
269283
assert.deepStrictEqual(actual, []);
@@ -290,13 +304,18 @@ suite('Venv Utils test', () => {
290304
const actual = await pickPackagesToInstall(workspace1);
291305
assert.isTrue(
292306
showQuickPickWithBackStub.calledWithExactly(
293-
[{ label: 'requirements.txt' }, { label: 'dev-requirements.txt' }, { label: 'test-requirements.txt' }],
307+
[
308+
{ label: 'requirements.txt', buttons: [OPEN_REQUIREMENTS_BUTTON] },
309+
{ label: 'dev-requirements.txt', buttons: [OPEN_REQUIREMENTS_BUTTON] },
310+
{ label: 'test-requirements.txt', buttons: [OPEN_REQUIREMENTS_BUTTON] },
311+
],
294312
{
295313
placeHolder: CreateEnv.Venv.requirementsQuickPickTitle,
296314
ignoreFocusOut: true,
297315
canPickMany: true,
298316
},
299317
undefined,
318+
sinon.match.func,
300319
),
301320
);
302321
assert.deepStrictEqual(actual, [
@@ -328,13 +347,18 @@ suite('Venv Utils test', () => {
328347
const actual = await pickPackagesToInstall(workspace1);
329348
assert.isTrue(
330349
showQuickPickWithBackStub.calledWithExactly(
331-
[{ label: 'requirements.txt' }, { label: 'dev-requirements.txt' }, { label: 'test-requirements.txt' }],
350+
[
351+
{ label: 'requirements.txt', buttons: [OPEN_REQUIREMENTS_BUTTON] },
352+
{ label: 'dev-requirements.txt', buttons: [OPEN_REQUIREMENTS_BUTTON] },
353+
{ label: 'test-requirements.txt', buttons: [OPEN_REQUIREMENTS_BUTTON] },
354+
],
332355
{
333356
placeHolder: CreateEnv.Venv.requirementsQuickPickTitle,
334357
ignoreFocusOut: true,
335358
canPickMany: true,
336359
},
337360
undefined,
361+
sinon.match.func,
338362
),
339363
);
340364
assert.deepStrictEqual(actual, [
@@ -349,6 +373,45 @@ suite('Venv Utils test', () => {
349373
]);
350374
assert.isTrue(readFileStub.notCalled);
351375
});
376+
377+
test('User clicks button to open requirements.txt', async () => {
378+
let allow = true;
379+
findFilesStub.callsFake(() => {
380+
if (allow) {
381+
allow = false;
382+
return Promise.resolve([
383+
Uri.file(path.join(workspace1.uri.fsPath, 'requirements.txt')),
384+
Uri.file(path.join(workspace1.uri.fsPath, 'dev-requirements.txt')),
385+
Uri.file(path.join(workspace1.uri.fsPath, 'test-requirements.txt')),
386+
]);
387+
}
388+
return Promise.resolve([]);
389+
});
390+
pathExistsStub.resolves(false);
391+
392+
const deferred = createDeferred();
393+
showQuickPickWithBackStub.callsFake(async (_items, _options, _token, callback) => {
394+
callback({
395+
button: OPEN_REQUIREMENTS_BUTTON,
396+
item: { label: 'requirements.txt' },
397+
});
398+
await deferred.promise;
399+
return [{ label: 'requirements.txt' }];
400+
});
401+
402+
let uri: Uri | undefined;
403+
showTextDocumentStub.callsFake((arg: Uri) => {
404+
uri = arg;
405+
deferred.resolve();
406+
return Promise.resolve();
407+
});
408+
409+
await pickPackagesToInstall(workspace1);
410+
assert.deepStrictEqual(
411+
uri?.toString(),
412+
Uri.file(path.join(workspace1.uri.fsPath, 'requirements.txt')).toString(),
413+
);
414+
});
352415
});
353416

354417
suite('Test pick existing venv action', () => {

0 commit comments

Comments
 (0)