Skip to content

Commit 7d38442

Browse files
committed
feat: add command to reveal environment in manager view
1 parent 7b701b3 commit 7d38442

File tree

7 files changed

+355
-9
lines changed

7 files changed

+355
-9
lines changed

package.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,12 @@
313313
"category": "Python Envs",
314314
"icon": "$(folder-opened)"
315315
},
316+
{
317+
"command": "python-envs.revealEnvInManagerView",
318+
"title": "%python-envs.revealEnvInManagerView.title%",
319+
"category": "Python Envs",
320+
"icon": "$(eye)"
321+
},
316322
{
317323
"command": "python-envs.runPetInTerminal",
318324
"title": "%python-envs.runPetInTerminal.title%",
@@ -423,6 +429,10 @@
423429
"command": "python-envs.revealProjectInExplorer",
424430
"when": "false"
425431
},
432+
{
433+
"command": "python-envs.revealEnvInManagerView",
434+
"when": "false"
435+
},
426436
{
427437
"command": "python-envs.createNewProjectFromTemplate",
428438
"when": "config.python.useEnvironmentsExtension != false"
@@ -531,6 +541,11 @@
531541
"command": "python-envs.uninstallPackage",
532542
"group": "inline",
533543
"when": "view == python-projects && viewItem == python-package"
544+
},
545+
{
546+
"command": "python-envs.revealEnvInManagerView",
547+
"group": "inline",
548+
"when": "view == python-projects && viewItem == python-env"
534549
}
535550
],
536551
"view/title": [

package.nls.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"python-envs.terminal.deactivate.title": "Deactivate Environment in Current Terminal",
4343
"python-envs.uninstallPackage.title": "Uninstall Package",
4444
"python-envs.revealProjectInExplorer.title": "Reveal Project in Explorer",
45+
"python-envs.revealEnvInManagerView.title": "Reveal in Environment Managers View",
4546
"python-envs.runPetInTerminal.title": "Run Python Environment Tool (PET) in Terminal...",
4647
"python-envs.alwaysUseUv.description": "When set to true, uv will be used to manage all virtual environments if available. When set to false, uv will only manage virtual environments explicitly created by uv."
4748
}

src/extension.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {
3535
refreshPackagesCommand,
3636
removeEnvironmentCommand,
3737
removePythonProject,
38+
revealEnvInManagerView,
3839
revealProjectInExplorer,
3940
runAsTaskCommand,
4041
runInDedicatedTerminalCommand,
@@ -312,6 +313,9 @@ export async function activate(context: ExtensionContext): Promise<PythonEnviron
312313
commands.registerCommand('python-envs.revealProjectInExplorer', async (item) => {
313314
await revealProjectInExplorer(item);
314315
}),
316+
commands.registerCommand('python-envs.revealEnvInManagerView', async (item) => {
317+
await revealEnvInManagerView(item, managerView);
318+
}),
315319
commands.registerCommand('python-envs.terminal.activate', async () => {
316320
const terminal = activeTerminal();
317321
if (terminal) {

src/features/envCommands.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import {
5252
import { runAsTask } from './execution/runAsTask';
5353
import { runInTerminal } from './terminal/runInTerminal';
5454
import { TerminalManager } from './terminal/terminalManager';
55+
import { EnvManagerView } from './views/envManagersView';
5556
import {
5657
EnvManagerTreeItem,
5758
EnvTreeItemKind,
@@ -751,3 +752,16 @@ export async function revealProjectInExplorer(item: unknown): Promise<void> {
751752
traceVerbose(`Invalid context for reveal project in explorer: ${item}`);
752753
}
753754
}
755+
756+
/**
757+
* Focuses the Environment Managers view and reveals the given project environment.
758+
*/
759+
export async function revealEnvInManagerView(item: unknown, managerView: EnvManagerView): Promise<void> {
760+
if (item instanceof ProjectEnvironment) {
761+
await commands.executeCommand('env-managers.focus');
762+
await managerView.reveal(item.environment);
763+
return;
764+
}
765+
766+
traceVerbose(`Invalid context for reveal environment in manager view: ${item}`);
767+
}

src/features/views/envManagersView.ts

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,14 @@ export class EnvManagerView implements TreeDataProvider<EnvTreeItem>, Disposable
3434
>();
3535
private revealMap = new Map<string, PythonEnvTreeItem>();
3636
private managerViews = new Map<string, EnvManagerTreeItem>();
37+
private groupViews = new Map<string, PythonGroupEnvTreeItem>();
3738
private selected: Map<string, string> = new Map();
3839
private disposables: Disposable[] = [];
3940

40-
public constructor(public providers: EnvironmentManagers, private stateManager: ITemporaryStateManager) {
41+
public constructor(
42+
public providers: EnvironmentManagers,
43+
private stateManager: ITemporaryStateManager,
44+
) {
4145
this.treeView = window.createTreeView<EnvTreeItem>('env-managers', {
4246
treeDataProvider: this,
4347
});
@@ -46,6 +50,7 @@ export class EnvManagerView implements TreeDataProvider<EnvTreeItem>, Disposable
4650
new Disposable(() => {
4751
this.revealMap.clear();
4852
this.managerViews.clear();
53+
this.groupViews.clear();
4954
this.selected.clear();
5055
}),
5156
this.treeView,
@@ -107,6 +112,7 @@ export class EnvManagerView implements TreeDataProvider<EnvTreeItem>, Disposable
107112
if (!element) {
108113
const views: EnvTreeItem[] = [];
109114
this.managerViews.clear();
115+
this.groupViews.clear();
110116
this.providers.managers.forEach((m) => {
111117
const view = new EnvManagerTreeItem(m);
112118
views.push(view);
@@ -137,7 +143,10 @@ export class EnvManagerView implements TreeDataProvider<EnvTreeItem>, Disposable
137143
});
138144

139145
groupObjects.forEach((group) => {
140-
views.push(new PythonGroupEnvTreeItem(element as EnvManagerTreeItem, group));
146+
const groupView = new PythonGroupEnvTreeItem(element as EnvManagerTreeItem, group);
147+
const groupName = typeof group === 'string' ? group : group.name;
148+
this.groupViews.set(`${manager.id}:${groupName}`, groupView);
149+
views.push(groupView);
141150
});
142151

143152
if (views.length === 0) {
@@ -202,12 +211,47 @@ export class EnvManagerView implements TreeDataProvider<EnvTreeItem>, Disposable
202211
return element.parent;
203212
}
204213

205-
reveal(environment?: PythonEnvironment) {
206-
const view = environment ? this.revealMap.get(environment.envId.id) : undefined;
214+
/**
215+
* Reveals and focuses on the given environment in the Environment Managers view.
216+
*
217+
* @param environment - The Python environment to reveal
218+
*/
219+
async reveal(environment?: PythonEnvironment): Promise<void> {
220+
if (!environment) {
221+
return;
222+
}
223+
224+
const manager = this.providers.getEnvironmentManager(environment);
225+
if (!manager) {
226+
return;
227+
}
228+
229+
if (!this.managerViews.has(manager.id)) {
230+
await this.getChildren(undefined);
231+
}
232+
233+
const managerView = this.managerViews.get(manager.id);
234+
if (!managerView) {
235+
return;
236+
}
237+
238+
const groupName = typeof environment.group === 'string' ? environment.group : environment.group?.name;
239+
if (groupName) {
240+
if (!this.groupViews.has(`${manager.id}:${groupName}`)) {
241+
await this.getChildren(managerView);
242+
}
243+
244+
const groupView = this.groupViews.get(`${manager.id}:${groupName}`);
245+
if (groupView) {
246+
await this.getChildren(groupView);
247+
}
248+
} else {
249+
await this.getChildren(managerView);
250+
}
251+
252+
const view = this.revealMap.get(environment.envId.id);
207253
if (view && this.treeView.visible) {
208-
setImmediate(async () => {
209-
await this.treeView.reveal(view);
210-
});
254+
await this.treeView.reveal(view, { expand: false, focus: true, select: true });
211255
}
212256
}
213257

src/test/features/envCommands.unit.test.ts

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import * as assert from 'assert';
22
import * as sinon from 'sinon';
33
import * as typeMoq from 'typemoq';
4-
import { Uri } from 'vscode';
4+
import { commands, Uri } from 'vscode';
55
import { PythonEnvironment, PythonProject } from '../../api';
66
import * as managerApi from '../../common/pickers/managers';
77
import * as projectApi from '../../common/pickers/projects';
8-
import { createAnyEnvironmentCommand } from '../../features/envCommands';
8+
import { createAnyEnvironmentCommand, revealEnvInManagerView } from '../../features/envCommands';
9+
import { EnvManagerView } from '../../features/views/envManagersView';
10+
import { ProjectEnvironment, ProjectItem } from '../../features/views/treeViewItems';
911
import { EnvironmentManagers, InternalEnvironmentManager, PythonProjectManager } from '../../internal.api';
1012
import { setupNonThenable } from '../mocks/helper';
1113

@@ -175,3 +177,49 @@ suite('Create Any Environment Command Tests', () => {
175177
em.verifyAll();
176178
});
177179
});
180+
181+
suite('Reveal Env In Manager View Command Tests', () => {
182+
let managerView: typeMoq.IMock<EnvManagerView>;
183+
let executeCommandStub: sinon.SinonStub;
184+
185+
setup(() => {
186+
managerView = typeMoq.Mock.ofType<EnvManagerView>();
187+
setupNonThenable(managerView);
188+
executeCommandStub = sinon.stub(commands, 'executeCommand');
189+
});
190+
191+
teardown(() => {
192+
sinon.restore();
193+
});
194+
195+
test('Focuses env-managers view and reveals environment when given a ProjectEnvironment', async () => {
196+
// Mock
197+
const project: PythonProject = {
198+
uri: Uri.file('/test/project'),
199+
name: 'test-project',
200+
};
201+
const projectItem = new ProjectItem(project);
202+
203+
const environment: PythonEnvironment = {
204+
envId: { id: 'test-env-id', managerId: 'test-manager' },
205+
name: 'test-env',
206+
displayName: 'Test Environment',
207+
displayPath: '/path/to/env',
208+
version: '3.10.0',
209+
environmentPath: Uri.file('/path/to/env'),
210+
execInfo: { run: { executable: '/path/to/python' }, activatedRun: { executable: '/path/to/python' } },
211+
sysPrefix: '/path/to/env',
212+
};
213+
const projectEnv = new ProjectEnvironment(projectItem, environment);
214+
215+
executeCommandStub.resolves();
216+
managerView.setup((m) => m.reveal(environment)).returns(() => Promise.resolve());
217+
218+
// Run
219+
await revealEnvInManagerView(projectEnv, managerView.object);
220+
221+
// Assert
222+
assert.ok(executeCommandStub.calledOnceWith('env-managers.focus'), 'Should focus the env-managers view');
223+
managerView.verify((m) => m.reveal(environment), typeMoq.Times.once());
224+
});
225+
});

0 commit comments

Comments
 (0)