Skip to content

Commit 860f142

Browse files
Copiloteleanorjboyd
andcommitted
Implement multiroot support for createTerminal command
Co-authored-by: eleanorjboyd <26030610+eleanorjboyd@users.noreply.github.com>
1 parent efc2791 commit 860f142

2 files changed

Lines changed: 134 additions & 13 deletions

File tree

src/features/envCommands.ts

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -514,23 +514,17 @@ export async function createTerminalCommand(
514514
api: PythonEnvironmentApi,
515515
tm: TerminalManager,
516516
): Promise<Terminal | undefined> {
517-
if (context === undefined) {
518-
const pw = await pickProject(api.getPythonProjects());
517+
if (context === undefined || context instanceof Uri) {
518+
// For undefined context or Uri context, check for multiroot and prompt if needed
519+
const projects = api.getPythonProjects();
520+
const pw = await pickProject(projects);
519521
if (pw) {
520522
const env = await api.getEnvironment(pw.uri);
521523
const cwd = await findParentIfFile(pw.uri.fsPath);
522524
if (env) {
523525
return await tm.create(env, { cwd });
524526
}
525527
}
526-
} else if (context instanceof Uri) {
527-
const uri = context as Uri;
528-
const env = await api.getEnvironment(uri);
529-
const pw = api.getPythonProject(uri);
530-
if (env && pw) {
531-
const cwd = await findParentIfFile(pw.uri.fsPath);
532-
return await tm.create(env, { cwd });
533-
}
534528
} else if (context instanceof ProjectItem) {
535529
const view = context as ProjectItem;
536530
const env = await api.getEnvironment(view.project.uri);

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

Lines changed: 130 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ import * as sinon from 'sinon';
44
import { EnvironmentManagers, InternalEnvironmentManager, PythonProjectManager } from '../../internal.api';
55
import * as projectApi from '../../common/pickers/projects';
66
import * as managerApi from '../../common/pickers/managers';
7-
import { PythonEnvironment, PythonProject } from '../../api';
8-
import { createAnyEnvironmentCommand } from '../../features/envCommands';
9-
import { Uri } from 'vscode';
7+
import { PythonEnvironment, PythonEnvironmentApi, PythonProject } from '../../api';
8+
import { createAnyEnvironmentCommand, createTerminalCommand } from '../../features/envCommands';
9+
import { Terminal, Uri } from 'vscode';
10+
import { TerminalManager } from '../../features/terminal/terminalManager';
11+
import * as fs from 'fs-extra';
1012

1113
suite('Create Any Environment Command Tests', () => {
1214
let em: typeMoq.IMock<EnvironmentManagers>;
@@ -175,3 +177,128 @@ suite('Create Any Environment Command Tests', () => {
175177
em.verifyAll();
176178
});
177179
});
180+
181+
suite('Create Terminal Command Tests', () => {
182+
let api: typeMoq.IMock<PythonEnvironmentApi>;
183+
let tm: typeMoq.IMock<TerminalManager>;
184+
let env: typeMoq.IMock<PythonEnvironment>;
185+
let terminal: typeMoq.IMock<Terminal>;
186+
let pickProjectStub: sinon.SinonStub;
187+
let fsStatStub: sinon.SinonStub;
188+
let project1: PythonProject = {
189+
uri: Uri.file('/workspace/folder1'),
190+
name: 'folder1',
191+
};
192+
let project2: PythonProject = {
193+
uri: Uri.file('/workspace/folder2'),
194+
name: 'folder2',
195+
};
196+
197+
setup(() => {
198+
env = typeMoq.Mock.ofType<PythonEnvironment>();
199+
env.setup((e) => e.envId).returns(() => ({ id: 'env1', managerId: 'test' }));
200+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
201+
env.setup((e: any) => e.then).returns(() => undefined);
202+
203+
terminal = typeMoq.Mock.ofType<Terminal>();
204+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
205+
terminal.setup((t: any) => t.then).returns(() => undefined);
206+
207+
api = typeMoq.Mock.ofType<PythonEnvironmentApi>();
208+
tm = typeMoq.Mock.ofType<TerminalManager>();
209+
210+
pickProjectStub = sinon.stub(projectApi, 'pickProject');
211+
// Stub fs.stat to return a directory stats object to avoid file system access
212+
fsStatStub = sinon.stub(fs, 'stat');
213+
fsStatStub.resolves({ isFile: () => false, isDirectory: () => true } as any);
214+
});
215+
216+
teardown(() => {
217+
sinon.restore();
218+
});
219+
220+
test('Single project: should create terminal without prompting', async () => {
221+
// Setup: single project
222+
api.setup((a) => a.getPythonProjects()).returns(() => [project1]);
223+
api.setup((a) => a.getEnvironment(project1.uri)).returns(() => Promise.resolve(env.object));
224+
tm.setup((t) => t.create(env.object, typeMoq.It.isAny())).returns(() => Promise.resolve(terminal.object));
225+
226+
// pickProject should return the single project without prompting
227+
pickProjectStub.resolves(project1);
228+
229+
const result = await createTerminalCommand(undefined, api.object, tm.object);
230+
231+
assert.strictEqual(result, terminal.object, 'Expected terminal to be created');
232+
assert.strictEqual(pickProjectStub.callCount, 1, 'pickProject should be called once');
233+
});
234+
235+
test('Multiple projects: should prompt user to select project', async () => {
236+
// Setup: multiple projects
237+
api.setup((a) => a.getPythonProjects()).returns(() => [project1, project2]);
238+
api.setup((a) => a.getEnvironment(project2.uri)).returns(() => Promise.resolve(env.object));
239+
tm.setup((t) => t.create(env.object, typeMoq.It.isAny())).returns(() => Promise.resolve(terminal.object));
240+
241+
// User selects project2
242+
pickProjectStub.resolves(project2);
243+
244+
const result = await createTerminalCommand(undefined, api.object, tm.object);
245+
246+
assert.strictEqual(result, terminal.object, 'Expected terminal to be created');
247+
assert.strictEqual(pickProjectStub.callCount, 1, 'pickProject should be called once');
248+
// Verify pickProject was called with both projects
249+
assert.deepStrictEqual(
250+
pickProjectStub.firstCall.args[0],
251+
[project1, project2],
252+
'pickProject should be called with all projects',
253+
);
254+
});
255+
256+
test('Uri context with single project: should create terminal without prompting', async () => {
257+
// Setup: single project
258+
api.setup((a) => a.getPythonProjects()).returns(() => [project1]);
259+
api.setup((a) => a.getEnvironment(project1.uri)).returns(() => Promise.resolve(env.object));
260+
tm.setup((t) => t.create(env.object, typeMoq.It.isAny())).returns(() => Promise.resolve(terminal.object));
261+
262+
// pickProject should return the single project without prompting
263+
pickProjectStub.resolves(project1);
264+
265+
const result = await createTerminalCommand(project1.uri, api.object, tm.object);
266+
267+
assert.strictEqual(result, terminal.object, 'Expected terminal to be created');
268+
assert.strictEqual(pickProjectStub.callCount, 1, 'pickProject should be called once');
269+
});
270+
271+
test('Uri context with multiple projects: should prompt user to select project', async () => {
272+
// Setup: multiple projects, context is project1.uri but user should still be prompted
273+
api.setup((a) => a.getPythonProjects()).returns(() => [project1, project2]);
274+
api.setup((a) => a.getEnvironment(project2.uri)).returns(() => Promise.resolve(env.object));
275+
tm.setup((t) => t.create(env.object, typeMoq.It.isAny())).returns(() => Promise.resolve(terminal.object));
276+
277+
// User selects project2 (different from context)
278+
pickProjectStub.resolves(project2);
279+
280+
const result = await createTerminalCommand(project1.uri, api.object, tm.object);
281+
282+
assert.strictEqual(result, terminal.object, 'Expected terminal to be created');
283+
assert.strictEqual(pickProjectStub.callCount, 1, 'pickProject should be called once');
284+
// Verify pickProject was called with all projects, not just the context
285+
assert.deepStrictEqual(
286+
pickProjectStub.firstCall.args[0],
287+
[project1, project2],
288+
'pickProject should be called with all projects',
289+
);
290+
});
291+
292+
test('User cancels project selection: should not create terminal', async () => {
293+
// Setup: multiple projects
294+
api.setup((a) => a.getPythonProjects()).returns(() => [project1, project2]);
295+
296+
// User cancels selection
297+
pickProjectStub.resolves(undefined);
298+
299+
const result = await createTerminalCommand(undefined, api.object, tm.object);
300+
301+
assert.strictEqual(result, undefined, 'Expected no terminal to be created when user cancels');
302+
assert.strictEqual(pickProjectStub.callCount, 1, 'pickProject should be called once');
303+
});
304+
});

0 commit comments

Comments
 (0)