Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
2 changes: 2 additions & 0 deletions src/client/common/terminal/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,13 +219,15 @@ export class TerminalService implements ITerminalService, Disposable {
this.terminal = await ensureTerminalLegacy(this.options?.resource, {
name: this.options?.title || 'Python',
hideFromUser: this.options?.hideFromUser,
iconPath: this.options?.iconPath,
});
return;
} else {
this.terminalShellType = this.terminalHelper.identifyTerminalShell(this.terminal);
this.terminal = this.terminalManager.createTerminal({
name: this.options?.title || 'Python',
hideFromUser: this.options?.hideFromUser,
iconPath: this.options?.iconPath,
});
this.terminalAutoActivator.disableAutoActivation(this.terminal);

Expand Down
6 changes: 5 additions & 1 deletion src/client/common/terminal/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

'use strict';

import { CancellationToken, Event, Terminal, Uri, TerminalShellExecution } from 'vscode';
import { CancellationToken, Event, Terminal, TerminalOptions, Uri, TerminalShellExecution } from 'vscode';
import { PythonEnvironment } from '../../pythonEnvironments/info';
import { IEventNamePropertyMapping } from '../../telemetry/index';
import { IDisposable, Resource } from '../types';
Expand Down Expand Up @@ -65,6 +65,10 @@ export type TerminalCreationOptions = {
* Object with environment variables that will be added to the Terminal.
*/
env?: { [key: string]: string | null };
/**
* Icon displayed for the terminal.
*/
iconPath?: TerminalOptions['iconPath'];
/**
* Resource identifier. E.g. used to determine python interpreter that needs to be used or environment variables or the like.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ export class DjangoShellCodeExecutionProvider extends TerminalCodeExecutionProvi
applicationShell,
);
this.terminalTitle = 'Django Shell';
// The snake icon is reserved for the Python REPL terminal.
this.terminalIcon = undefined;
disposableRegistry.push(new DjangoContextInitializer(documentManager, workspace, fileSystem, commandManager));
}

Expand Down
4 changes: 3 additions & 1 deletion src/client/terminals/codeExecution/terminalCodeExecution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import { inject, injectable } from 'inversify';
import * as path from 'path';
import { Disposable, Uri } from 'vscode';
import { Disposable, TerminalOptions, ThemeIcon, Uri } from 'vscode';
import { IApplicationShell, ICommandManager, IWorkspaceService } from '../../common/application/types';
import '../../common/extensions';
import { IPlatformService } from '../../common/platform/types';
Expand All @@ -24,6 +24,7 @@ import { sendTelemetryEvent } from '../../telemetry';
export class TerminalCodeExecutionProvider implements ICodeExecutionService {
private hasRanOutsideCurrentDrive = false;
protected terminalTitle!: string;
protected terminalIcon?: TerminalOptions['iconPath'] = new ThemeIcon('snake');
Comment thread
edvilme marked this conversation as resolved.
Resolved
private replActive?: Promise<boolean>;
Comment thread
edvilme marked this conversation as resolved.
Resolved

constructor(
Expand Down Expand Up @@ -128,6 +129,7 @@ export class TerminalCodeExecutionProvider implements ICodeExecutionService {
return this.terminalServiceFactory.getTerminalService({
resource,
title: this.terminalTitle,
iconPath: this.terminalIcon,
newTerminalPerFile: options?.newTerminalPerFile,
});
}
Expand Down
37 changes: 36 additions & 1 deletion src/test/common/terminals/service.unit.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import { expect } from 'chai';
import { assert, expect } from 'chai';
import * as path from 'path';
import * as sinon from 'sinon';
import * as TypeMoq from 'typemoq';
Expand All @@ -11,6 +11,7 @@ import {
TerminalShellExecution,
TerminalShellExecutionEndEvent,
TerminalShellIntegration,
ThemeIcon,
Uri,
Terminal as VSCodeTerminal,
WorkspaceConfiguration,
Expand All @@ -29,10 +30,12 @@ import {
import { IDisposableRegistry } from '../../../client/common/types';
import { IServiceContainer } from '../../../client/ioc/types';
import { ITerminalAutoActivation } from '../../../client/terminals/types';
import { noop } from '../../../client/common/utils/misc';
import { createPythonInterpreter } from '../../utils/interpreters';
import * as workspaceApis from '../../../client/common/vscodeApis/workspaceApis';
import * as platform from '../../../client/common/utils/platform';
import * as extapi from '../../../client/envExt/api.internal';
import * as extapiLegacy from '../../../client/envExt/api.legacy';
import { IInterpreterService } from '../../../client/interpreter/contracts';
import { PythonEnvironment } from '../../../client/pythonEnvironments/info';

Expand All @@ -55,6 +58,7 @@ suite('Terminal Service', () => {
let editorConfig: TypeMoq.IMock<WorkspaceConfiguration>;
let isWindowsStub: sinon.SinonStub;
let useEnvExtensionStub: sinon.SinonStub;
let ensureTerminalLegacyStub: sinon.SinonStub;
let interpreterService: TypeMoq.IMock<IInterpreterService>;
let options: TypeMoq.IMock<TerminalCreationOptions>;
let applicationShell: TypeMoq.IMock<IApplicationShell>;
Expand All @@ -64,6 +68,7 @@ suite('Terminal Service', () => {
setup(() => {
useEnvExtensionStub = sinon.stub(extapi, 'useEnvExtension');
useEnvExtensionStub.returns(false);
ensureTerminalLegacyStub = sinon.stub(extapiLegacy, 'ensureTerminalLegacy');

terminal = TypeMoq.Mock.ofType<VSCodeTerminal>();
terminalShellIntegration = TypeMoq.Mock.ofType<TerminalShellIntegration>();
Expand Down Expand Up @@ -394,6 +399,36 @@ suite('Terminal Service', () => {
terminal.verify((t) => t.show(TypeMoq.It.isValue(true)), TypeMoq.Times.never());
});

test('Ensure `iconPath` option is forwarded to the created terminal', async () => {
terminalHelper
.setup((helper) => helper.getEnvironmentActivationCommands(TypeMoq.It.isAny(), TypeMoq.It.isAny()))
.returns(() => Promise.resolve(undefined));
const iconPath = new ThemeIcon('snake');
service = new TerminalService(mockServiceContainer.object, { iconPath });
terminalHelper.setup((h) => h.identifyTerminalShell(TypeMoq.It.isAny())).returns(() => TerminalShellType.bash);
terminalManager.setup((t) => t.createTerminal(TypeMoq.It.isAny())).returns(() => terminal.object);

await service.show();

terminalManager.verify(
(t) => t.createTerminal(TypeMoq.It.isObjectWith({ iconPath })),
TypeMoq.Times.atLeastOnce(),
);
});

test('Ensure `iconPath` option is forwarded when the environments extension is used', async () => {
useEnvExtensionStub.returns(true);
const iconPath = new ThemeIcon('snake');
const createdTerminal = ({ show: noop, dispose: noop } as unknown) as VSCodeTerminal;
ensureTerminalLegacyStub.resolves(createdTerminal);
service = new TerminalService(mockServiceContainer.object, { iconPath });

await service.show();

assert.ok(ensureTerminalLegacyStub.calledOnce);
assert.strictEqual(ensureTerminalLegacyStub.firstCall.args[1].iconPath, iconPath);
});

test('Ensure terminal shown otherwise', async () => {
terminalHelper
.setup((helper) => helper.getEnvironmentActivationCommands(TypeMoq.It.isAny(), TypeMoq.It.isAny()))
Expand Down
12 changes: 10 additions & 2 deletions src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { expect } from 'chai';
import * as path from 'path';
import { SemVer } from 'semver';
import * as TypeMoq from 'typemoq';
import { Disposable, Uri, WorkspaceFolder } from 'vscode';
import { Disposable, ThemeIcon, Uri, WorkspaceFolder } from 'vscode';
import {
IApplicationShell,
ICommandManager,
Expand Down Expand Up @@ -45,6 +45,7 @@ suite('Terminal - Code Execution', () => {
let disposables: Disposable[] = [];
let executor: ICodeExecutionService;
let expectedTerminalTitle: string | undefined;
let expectedTerminalIconId: string | undefined;
let terminalFactory: TypeMoq.IMock<ITerminalServiceFactory>;
let documentManager: TypeMoq.IMock<IDocumentManager>;
let commandManager: TypeMoq.IMock<ICommandManager>;
Expand Down Expand Up @@ -81,6 +82,7 @@ suite('Terminal - Code Execution', () => {
settings = TypeMoq.Mock.ofType<IPythonSettings>();
settings.setup((s) => s.terminal).returns(() => terminalSettings.object);
configService.setup((c) => c.getSettings(TypeMoq.It.isAny())).returns(() => settings.object);
expectedTerminalIconId = 'snake';
Comment thread
edvilme marked this conversation as resolved.
Resolved

switch (testSuiteName) {
case 'Terminal Execution': {
Expand Down Expand Up @@ -132,6 +134,7 @@ suite('Terminal - Code Execution', () => {
applicationShell.object,
);
expectedTerminalTitle = 'Django Shell';
expectedTerminalIconId = undefined;
break;
}
default: {
Expand All @@ -145,7 +148,12 @@ suite('Terminal - Code Execution', () => {
terminalFactory
.setup((f) =>
f.getTerminalService(
TypeMoq.It.is<TerminalCreationOptions>((a) => a.title === expectedTerminalTitle),
TypeMoq.It.is<TerminalCreationOptions>(
(a) =>
a.title === expectedTerminalTitle &&
(a.iconPath instanceof ThemeIcon ? a.iconPath.id : undefined) ===
expectedTerminalIconId,
),
),
)
.returns(() => terminalService.object);
Expand Down
Loading