Skip to content

Commit bf8ea32

Browse files
authored
Merge branch 'main' into energetic-hummingbird
2 parents a044c35 + b546ac4 commit bf8ea32

File tree

8 files changed

+151
-27
lines changed

8 files changed

+151
-27
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "vscode-python-envs",
33
"displayName": "Python Environments",
44
"description": "Provides a unified python environment experience",
5-
"version": "1.20.0",
5+
"version": "1.21.0",
66
"publisher": "ms-python",
77
"preview": true,
88
"engines": {

src/common/localize.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,9 @@ export namespace CondaStrings {
168168
export const condaMissingPythonNoFix = l10n.t(
169169
'No Python found in the selected conda environment. Please select another environment or install Python manually.',
170170
);
171+
export const condaCondaPrefixActive = l10n.t(
172+
'CONDA_PREFIX is set for this VS Code session. Selection saved for new terminals only.',
173+
);
171174
}
172175

173176
export namespace PyenvStrings {

src/extension.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -494,8 +494,9 @@ export async function activate(context: ExtensionContext): Promise<PythonEnviron
494494
}
495495
const envVar = shellEnv.value;
496496
if (envVar) {
497-
if (envVar['VIRTUAL_ENV']) {
498-
const envPath = normalizeShellPath(envVar['VIRTUAL_ENV'], e.terminal.state.shell);
497+
const envVarPath = envVar['VIRTUAL_ENV'] || envVar['CONDA_PREFIX'];
498+
if (envVarPath) {
499+
const envPath = normalizeShellPath(envVarPath, e.terminal.state.shell);
499500
const env = await api.resolveEnvironment(Uri.file(envPath));
500501
if (env) {
501502
monitoredTerminals.set(e.terminal, env);

src/features/terminal/terminalManager.ts

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,7 @@ export interface TerminalInit {
5959
}
6060

6161
export interface TerminalManager
62-
extends TerminalEnvironment,
63-
TerminalInit,
64-
TerminalActivation,
65-
TerminalCreation,
66-
TerminalGetters,
67-
Disposable {}
62+
extends TerminalEnvironment, TerminalInit, TerminalActivation, TerminalCreation, TerminalGetters, Disposable {}
6863

6964
export class TerminalManagerImpl implements TerminalManager {
7065
private disposables: Disposable[] = [];
@@ -321,7 +316,7 @@ export class TerminalManagerImpl implements TerminalManager {
321316

322317
private dedicatedTerminals = new Map<string, Terminal>();
323318
async getDedicatedTerminal(
324-
terminalKey: Uri,
319+
terminalKey: Uri | string,
325320
project: Uri | PythonProject,
326321
environment: PythonEnvironment,
327322
createNew: boolean = false,
@@ -336,17 +331,26 @@ export class TerminalManagerImpl implements TerminalManager {
336331
}
337332

338333
const puri = project instanceof Uri ? project : project.uri;
339-
const config = getConfiguration('python', terminalKey);
334+
const config = getConfiguration('python', terminalKey instanceof Uri ? terminalKey : puri);
340335
const projectStat = await fsapi.stat(puri.fsPath);
341336
const projectDir = projectStat.isDirectory() ? puri.fsPath : path.dirname(puri.fsPath);
342337

343-
const uriStat = await fsapi.stat(terminalKey.fsPath);
344-
const uriDir = uriStat.isDirectory() ? terminalKey.fsPath : path.dirname(terminalKey.fsPath);
345-
const cwd = config.get<boolean>('terminal.executeInFileDir', false) ? uriDir : projectDir;
338+
let cwd: string;
339+
let name: string;
340+
341+
if (terminalKey instanceof Uri) {
342+
const uriStat = await fsapi.stat(terminalKey.fsPath);
343+
const uriDir = uriStat.isDirectory() ? terminalKey.fsPath : path.dirname(terminalKey.fsPath);
344+
cwd = config.get<boolean>('terminal.executeInFileDir', false) ? uriDir : projectDir;
345+
// Follow Python extension's naming: 'Python: {filename}' for dedicated terminals
346+
const fileName = path.basename(terminalKey.fsPath).replace('.py', '');
347+
name = `Python: ${fileName}`;
348+
} else {
349+
// When terminalKey is a string, use project directory and the string as name
350+
cwd = projectDir;
351+
name = `Python: ${terminalKey}`;
352+
}
346353

347-
// Follow Python extension's naming: 'Python: {filename}' for dedicated terminals
348-
const fileName = path.basename(terminalKey.fsPath).replace('.py', '');
349-
const name = `Python: ${fileName}`;
350354
const newTerminal = await this.create(environment, { cwd, name });
351355
this.dedicatedTerminals.set(key, newTerminal);
352356

src/managers/conda/condaEnvManager.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import { CondaStrings } from '../../common/localize';
2323
import { traceError } from '../../common/logging';
2424
import { createDeferred, Deferred } from '../../common/utils/deferred';
2525
import { normalizePath } from '../../common/utils/pathUtils';
26-
import { showErrorMessage, withProgress } from '../../common/window.apis';
26+
import { showErrorMessage, showInformationMessage, withProgress } from '../../common/window.apis';
2727
import { NativePythonFinder } from '../common/nativePythonFinder';
2828
import { CondaSourcingStatus } from './condaSourcingUtils';
2929
import {
@@ -289,6 +289,16 @@ export class CondaEnvManager implements EnvironmentManager, Disposable {
289289
const folder = this.api.getPythonProject(scope);
290290
const fsPath = folder?.uri?.fsPath ?? scope.fsPath;
291291
if (fsPath) {
292+
// Notify user if CONDA_PREFIX is set and they're trying to select a different environment
293+
if (process.env.CONDA_PREFIX && checkedEnv) {
294+
const condaPrefixPath = process.env.CONDA_PREFIX;
295+
const selectedPath = checkedEnv.environmentPath.fsPath;
296+
// Only show notification if they selected a different environment
297+
if (condaPrefixPath !== selectedPath) {
298+
showInformationMessage(CondaStrings.condaCondaPrefixActive);
299+
}
300+
}
301+
292302
const normalizedFsPath = normalizePath(fsPath);
293303
if (checkedEnv) {
294304
this.fsPathToEnv.set(normalizedFsPath, checkedEnv);

src/managers/conda/condaUtils.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ export function getCondaPathSetting(): string | undefined {
8282
}
8383

8484
export async function getCondaForWorkspace(fsPath: string): Promise<string | undefined> {
85+
if (process.env.CONDA_PREFIX) {
86+
return process.env.CONDA_PREFIX;
87+
}
88+
8589
const state = await getWorkspacePersistentState();
8690
const data: { [key: string]: string } | undefined = await state.get(CONDA_WORKSPACE_KEY);
8791
if (data) {

src/test/features/terminal/terminalManager.unit.test.ts

Lines changed: 109 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,22 @@ import * as fsapi from 'fs-extra';
66
import * as os from 'os';
77
import * as path from 'path';
88
import * as sinon from 'sinon';
9-
import { Disposable, Event, EventEmitter, Progress, Terminal, TerminalOptions, Uri, WorkspaceConfiguration } from 'vscode';
9+
import {
10+
Disposable,
11+
Event,
12+
EventEmitter,
13+
Progress,
14+
Terminal,
15+
TerminalOptions,
16+
Uri,
17+
WorkspaceConfiguration,
18+
} from 'vscode';
1019
import { PythonEnvironment } from '../../../api';
1120
import * as windowApis from '../../../common/window.apis';
1221
import * as workspaceApis from '../../../common/workspace.apis';
1322
import * as activationUtils from '../../../features/common/activation';
1423
import * as shellDetector from '../../../features/common/shellDetector';
15-
import {
16-
ShellEnvsProvider,
17-
ShellStartupScriptProvider,
18-
} from '../../../features/terminal/shells/startupProvider';
24+
import { ShellEnvsProvider, ShellStartupScriptProvider } from '../../../features/terminal/shells/startupProvider';
1925
import {
2026
DidChangeTerminalActivationStateEvent,
2127
TerminalActivationInternal,
@@ -309,13 +315,109 @@ suite('TerminalManager - terminal naming', () => {
309315
try {
310316
await terminalManager.getProjectTerminal(projectUri, env);
311317

318+
assert.strictEqual(optionsList[0]?.name, 'Python', 'Project terminal should use the Python title');
319+
} finally {
320+
await fsapi.remove(tempRoot);
321+
}
322+
});
323+
324+
// Regression test for https://github.com/microsoft/vscode-python-environments/issues/1230
325+
test('getDedicatedTerminal with string key uses string as terminal name', async () => {
326+
mockGetAutoActivationType.returns(terminalUtils.ACT_TYPE_OFF);
327+
terminalManager = createTerminalManager();
328+
const env = createMockEnvironment();
329+
330+
const optionsList: TerminalOptions[] = [];
331+
createTerminalStub.callsFake((options) => {
332+
optionsList.push(options);
333+
return mockTerminal as Terminal;
334+
});
335+
336+
const tempRoot = await fsapi.mkdtemp(path.join(os.tmpdir(), 'py-envs-'));
337+
const projectPath = path.join(tempRoot, 'project');
338+
await fsapi.ensureDir(projectPath);
339+
const projectUri = Uri.file(projectPath);
340+
341+
const config = { get: sinon.stub().returns(false) } as unknown as WorkspaceConfiguration;
342+
sinon.stub(workspaceApis, 'getConfiguration').returns(config);
343+
344+
try {
345+
await terminalManager.getDedicatedTerminal('my-terminal-key', projectUri, env);
346+
312347
assert.strictEqual(
313348
optionsList[0]?.name,
314-
'Python',
315-
'Project terminal should use the Python title',
349+
'Python: my-terminal-key',
350+
'Dedicated terminal with string key should use the string in the title',
351+
);
352+
assert.strictEqual(
353+
Uri.file(optionsList[0]?.cwd as string).fsPath,
354+
Uri.file(projectPath).fsPath,
355+
'Dedicated terminal with string key should use project directory as cwd',
316356
);
317357
} finally {
318358
await fsapi.remove(tempRoot);
319359
}
320360
});
361+
362+
// Regression test for https://github.com/microsoft/vscode-python-environments/issues/1230
363+
test('getDedicatedTerminal with string key reuses terminal for same key', async () => {
364+
mockGetAutoActivationType.returns(terminalUtils.ACT_TYPE_OFF);
365+
terminalManager = createTerminalManager();
366+
const env = createMockEnvironment();
367+
368+
let createCount = 0;
369+
createTerminalStub.callsFake((options) => {
370+
createCount++;
371+
return { ...mockTerminal, name: options.name } as Terminal;
372+
});
373+
374+
const tempRoot = await fsapi.mkdtemp(path.join(os.tmpdir(), 'py-envs-'));
375+
const projectPath = path.join(tempRoot, 'project');
376+
await fsapi.ensureDir(projectPath);
377+
const projectUri = Uri.file(projectPath);
378+
379+
const config = { get: sinon.stub().returns(false) } as unknown as WorkspaceConfiguration;
380+
sinon.stub(workspaceApis, 'getConfiguration').returns(config);
381+
382+
try {
383+
const terminal1 = await terminalManager.getDedicatedTerminal('my-key', projectUri, env);
384+
const terminal2 = await terminalManager.getDedicatedTerminal('my-key', projectUri, env);
385+
386+
assert.strictEqual(terminal1, terminal2, 'Same string key should return the same terminal');
387+
assert.strictEqual(createCount, 1, 'Terminal should be created only once');
388+
} finally {
389+
await fsapi.remove(tempRoot);
390+
}
391+
});
392+
393+
// Regression test for https://github.com/microsoft/vscode-python-environments/issues/1230
394+
test('getDedicatedTerminal with string key uses different terminals for different keys', async () => {
395+
mockGetAutoActivationType.returns(terminalUtils.ACT_TYPE_OFF);
396+
terminalManager = createTerminalManager();
397+
const env = createMockEnvironment();
398+
399+
let createCount = 0;
400+
createTerminalStub.callsFake((options) => {
401+
createCount++;
402+
return { ...mockTerminal, name: options.name, id: createCount } as unknown as Terminal;
403+
});
404+
405+
const tempRoot = await fsapi.mkdtemp(path.join(os.tmpdir(), 'py-envs-'));
406+
const projectPath = path.join(tempRoot, 'project');
407+
await fsapi.ensureDir(projectPath);
408+
const projectUri = Uri.file(projectPath);
409+
410+
const config = { get: sinon.stub().returns(false) } as unknown as WorkspaceConfiguration;
411+
sinon.stub(workspaceApis, 'getConfiguration').returns(config);
412+
413+
try {
414+
const terminal1 = await terminalManager.getDedicatedTerminal('key-1', projectUri, env);
415+
const terminal2 = await terminalManager.getDedicatedTerminal('key-2', projectUri, env);
416+
417+
assert.notStrictEqual(terminal1, terminal2, 'Different string keys should return different terminals');
418+
assert.strictEqual(createCount, 2, 'Two terminals should be created');
419+
} finally {
420+
await fsapi.remove(tempRoot);
421+
}
422+
});
321423
});

0 commit comments

Comments
 (0)