Skip to content

Commit 3fe2b79

Browse files
authored
fix bug: getDedicatedTerminal errors on string value for supported terminalKey (#1231)
fixes #1230
1 parent de91d2d commit 3fe2b79

File tree

2 files changed

+127
-21
lines changed

2 files changed

+127
-21
lines changed

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/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)