Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions src/features/terminal/terminalManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,11 +289,13 @@ export class TerminalManagerImpl implements TerminalManager {
});
}

// Uncomment the code line below after the issue is resolved:
// https://github.com/microsoft/vscode-python-environments/issues/172
// const name = options.name ?? `Python: ${environment.displayName}`;
// Follow Python extension's terminal naming convention:
// - Default: 'Python'
// - Dedicated: 'Python: {filename}' (set by getDedicatedTerminal)
const name = options.name ?? 'Python';
const newTerminal = createTerminal({
...options,
name,
env: envVars,
});

Expand Down Expand Up @@ -342,7 +344,10 @@ export class TerminalManagerImpl implements TerminalManager {
const uriDir = uriStat.isDirectory() ? terminalKey.fsPath : path.dirname(terminalKey.fsPath);
const cwd = config.get<boolean>('terminal.executeInFileDir', false) ? uriDir : projectDir;

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

const disable = onDidCloseTerminal((terminal) => {
Expand Down
136 changes: 135 additions & 1 deletion src/test/features/terminal/terminalManager.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
// Licensed under the MIT License.

import * as assert from 'assert';
import * as fsapi from 'fs-extra';
import * as os from 'os';
import * as path from 'path';
import * as sinon from 'sinon';
import { Disposable, Event, EventEmitter, Progress, Terminal, TerminalOptions, Uri } from 'vscode';
import { Disposable, Event, EventEmitter, Progress, Terminal, TerminalOptions, Uri, WorkspaceConfiguration } from 'vscode';
import { PythonEnvironment } from '../../../api';
import * as windowApis from '../../../common/window.apis';
import * as workspaceApis from '../../../common/workspace.apis';
Expand Down Expand Up @@ -185,3 +188,134 @@ suite('TerminalManager - create()', () => {
assert.strictEqual(terminalActivation.activateCalls, 0, 'No activate() when disableActivation is true');
});
});

suite('TerminalManager - terminal naming', () => {
let terminalActivation: TestTerminalActivation;
let mockGetAutoActivationType: sinon.SinonStub;
let terminalManager: TerminalManagerImpl;
let mockTerminal: Partial<Terminal> & { show: sinon.SinonStub };
let createTerminalStub: sinon.SinonStub;

const createMockEnvironment = (): PythonEnvironment => ({
envId: { id: 'test-env-id', managerId: 'test-manager' },
name: 'Test Environment',
displayName: 'Test Environment',
shortDisplayName: 'TestEnv',
displayPath: '/path/to/env',
version: '3.9.0',
environmentPath: Uri.file('/path/to/python'),
sysPrefix: '/path/to/env',
execInfo: {
run: { executable: '/path/to/python' },
activation: [{ executable: '/path/to/activate' }],
},
});

setup(() => {
terminalActivation = new TestTerminalActivation();

mockTerminal = {
name: 'Test Terminal',
creationOptions: {} as TerminalOptions,
shellIntegration: undefined,
show: sinon.stub(),
sendText: sinon.stub(),
};

mockGetAutoActivationType = sinon.stub(terminalUtils, 'getAutoActivationType');
sinon.stub(terminalUtils, 'waitForShellIntegration').resolves(false);
sinon.stub(activationUtils, 'isActivatableEnvironment').returns(true);
sinon.stub(shellDetector, 'identifyTerminalShell').returns('bash');

createTerminalStub = sinon.stub(windowApis, 'createTerminal').returns(mockTerminal as Terminal);
sinon.stub(windowApis, 'onDidOpenTerminal').returns(new Disposable(() => {}));
sinon.stub(windowApis, 'onDidCloseTerminal').returns(new Disposable(() => {}));
sinon.stub(windowApis, 'onDidChangeWindowState').returns(new Disposable(() => {}));
sinon.stub(windowApis, 'terminals').returns([]);
sinon.stub(windowApis, 'withProgress').callsFake(async (_options, task) => {
const mockProgress: Progress<{ message?: string; increment?: number }> = { report: () => {} };
const mockCancellationToken = {
isCancellationRequested: false,
onCancellationRequested: () => new Disposable(() => {}),
};
return task(mockProgress, mockCancellationToken as never);
});

sinon.stub(workspaceApis, 'onDidChangeConfiguration').returns(new Disposable(() => {}));
});

teardown(() => {
sinon.restore();
terminalActivation.dispose();
});

function createTerminalManager(): TerminalManagerImpl {
const emptyEnvProviders: ShellEnvsProvider[] = [];
const emptyScriptProviders: ShellStartupScriptProvider[] = [];
return new TerminalManagerImpl(terminalActivation, emptyEnvProviders, emptyScriptProviders);
}

test('getDedicatedTerminal sets Python file name as terminal name', async () => {
mockGetAutoActivationType.returns(terminalUtils.ACT_TYPE_OFF);
terminalManager = createTerminalManager();
const env = createMockEnvironment();

const optionsList: TerminalOptions[] = [];
createTerminalStub.callsFake((options) => {
optionsList.push(options);
return mockTerminal as Terminal;
});

const tempRoot = await fsapi.mkdtemp(path.join(os.tmpdir(), 'py-envs-'));
const projectPath = path.join(tempRoot, 'project');
const filePath = path.join(projectPath, 'main.py');
await fsapi.ensureDir(projectPath);
await fsapi.writeFile(filePath, 'print("hello")');
const projectUri = Uri.file(projectPath);
const fileUri = Uri.file(filePath);

const config = { get: sinon.stub().returns(false) } as unknown as WorkspaceConfiguration;
sinon.stub(workspaceApis, 'getConfiguration').returns(config);

try {
await terminalManager.getDedicatedTerminal(fileUri, projectUri, env);

assert.strictEqual(
optionsList[0]?.name,
'Python: main',
'Dedicated terminal should use the file name in the title',
);
} finally {
await fsapi.remove(tempRoot);
}
});

test('getProjectTerminal sets Python as terminal name', async () => {
mockGetAutoActivationType.returns(terminalUtils.ACT_TYPE_OFF);
terminalManager = createTerminalManager();
const env = createMockEnvironment();

const optionsList: TerminalOptions[] = [];
createTerminalStub.callsFake((options) => {
optionsList.push(options);
return mockTerminal as Terminal;
});

const tempRoot = await fsapi.mkdtemp(path.join(os.tmpdir(), 'py-envs-'));
const projectPath = path.join(tempRoot, 'project');
await fsapi.ensureDir(projectPath);
const projectUri = Uri.file(projectPath);

try {
await terminalManager.getProjectTerminal(projectUri, env);

assert.strictEqual(
optionsList[0]?.name,
'Python',
'Project terminal should use the Python title',
);
} finally {
await fsapi.remove(tempRoot);
}
});
});