Skip to content

Commit f862978

Browse files
authored
Use options object in terminal methods (#9739)
* Use options object in terminal methods * Fix code review comments * Update VSC * Address review comments * Fix types * Revert "Update VSC" This reverts commit 1c38572.
1 parent adb7be2 commit f862978

File tree

18 files changed

+192
-91
lines changed

18 files changed

+192
-91
lines changed

src/client/common/terminal/activator/base.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,26 @@
33

44
'use strict';
55

6-
import { Terminal, Uri } from 'vscode';
6+
import { Terminal } from 'vscode';
77
import { createDeferred, sleep } from '../../utils/async';
8-
import { ITerminalActivator, ITerminalHelper, TerminalShellType } from '../types';
8+
import { ITerminalActivator, ITerminalHelper, TerminalActivationOptions, TerminalShellType } from '../types';
99

1010
export class BaseTerminalActivator implements ITerminalActivator {
1111
private readonly activatedTerminals: Map<Terminal, Promise<boolean>> = new Map<Terminal, Promise<boolean>>();
1212
constructor(private readonly helper: ITerminalHelper) {}
13-
public async activateEnvironmentInTerminal(terminal: Terminal, resource: Uri | undefined, preserveFocus: boolean = true) {
13+
public async activateEnvironmentInTerminal(terminal: Terminal, options?: TerminalActivationOptions): Promise<boolean> {
1414
if (this.activatedTerminals.has(terminal)) {
1515
return this.activatedTerminals.get(terminal)!;
1616
}
1717
const deferred = createDeferred<boolean>();
1818
this.activatedTerminals.set(terminal, deferred.promise);
1919
const terminalShellType = this.helper.identifyTerminalShell(terminal);
2020

21-
const activationCommamnds = await this.helper.getEnvironmentActivationCommands(terminalShellType, resource);
21+
const activationCommands = await this.helper.getEnvironmentActivationCommands(terminalShellType, options?.resource, options?.interpreter);
2222
let activated = false;
23-
if (activationCommamnds) {
24-
for (const command of activationCommamnds!) {
25-
terminal.show(preserveFocus);
23+
if (activationCommands) {
24+
for (const command of activationCommands!) {
25+
terminal.show(options?.preserveFocus);
2626
terminal.sendText(command);
2727
await this.waitForCommandToProcess(terminalShellType);
2828
activated = true;

src/client/common/terminal/activator/index.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
'use strict';
55

66
import { inject, injectable, multiInject } from 'inversify';
7-
import { Terminal, Uri } from 'vscode';
8-
import { ITerminalActivationHandler, ITerminalActivator, ITerminalHelper } from '../types';
7+
import { Terminal } from 'vscode';
8+
import { ITerminalActivationHandler, ITerminalActivator, ITerminalHelper, TerminalActivationOptions } from '../types';
99
import { BaseTerminalActivator } from './base';
1010

1111
@injectable()
@@ -14,9 +14,9 @@ export class TerminalActivator implements ITerminalActivator {
1414
constructor(@inject(ITerminalHelper) readonly helper: ITerminalHelper, @multiInject(ITerminalActivationHandler) private readonly handlers: ITerminalActivationHandler[]) {
1515
this.initialize();
1616
}
17-
public async activateEnvironmentInTerminal(terminal: Terminal, resource: Uri | undefined, preserveFocus: boolean = true) {
18-
const activated = await this.baseActivator.activateEnvironmentInTerminal(terminal, resource, preserveFocus);
19-
this.handlers.forEach(handler => handler.handleActivation(terminal, resource, preserveFocus, activated).ignoreErrors());
17+
public async activateEnvironmentInTerminal(terminal: Terminal, options?: TerminalActivationOptions): Promise<boolean> {
18+
const activated = await this.baseActivator.activateEnvironmentInTerminal(terminal, options);
19+
this.handlers.forEach(handler => handler.handleActivation(terminal, options?.resource, options?.preserveFocus === true, activated).ignoreErrors());
2020
return activated;
2121
}
2222
protected initialize() {

src/client/common/terminal/factory.ts

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@
33

44
import { inject, injectable } from 'inversify';
55
import { Uri } from 'vscode';
6-
import { IInterpreterService } from '../../interpreter/contracts';
6+
import { IInterpreterService, PythonInterpreter } from '../../interpreter/contracts';
77
import { IServiceContainer } from '../../ioc/types';
88
import { IWorkspaceService } from '../application/types';
99
import { IFileSystem } from '../platform/types';
10+
import { isUri } from '../utils/misc';
1011
import { TerminalService } from './service';
1112
import { SynchronousTerminalService } from './syncTerminalService';
12-
import { ITerminalService, ITerminalServiceFactory } from './types';
13+
import { ITerminalService, ITerminalServiceFactory, TerminalCreationOptions } from './types';
1314

1415
@injectable()
1516
export class TerminalServiceFactory implements ITerminalServiceFactory {
@@ -22,26 +23,39 @@ export class TerminalServiceFactory implements ITerminalServiceFactory {
2223
) {
2324
this.terminalServices = new Map<string, TerminalService>();
2425
}
25-
public getTerminalService(resource?: Uri, title?: string): ITerminalService {
26+
public getTerminalService(options?: TerminalCreationOptions): ITerminalService;
27+
public getTerminalService(resource?: Uri, title?: string): ITerminalService;
28+
public getTerminalService(arg1?: Uri | TerminalCreationOptions, arg2?: string): ITerminalService {
29+
const resource = isUri(arg1) ? arg1 : undefined;
30+
const title = isUri(arg1) ? undefined : arg1?.title || arg2;
2631
const terminalTitle = typeof title === 'string' && title.trim().length > 0 ? title.trim() : 'Python';
27-
const id = this.getTerminalId(terminalTitle, resource);
32+
const interpreter = isUri(arg1) ? undefined : arg1?.interpreter;
33+
const env = isUri(arg1) ? undefined : arg1?.env;
34+
35+
const options: TerminalCreationOptions = {
36+
env,
37+
interpreter,
38+
resource,
39+
title
40+
};
41+
const id = this.getTerminalId(terminalTitle, resource, interpreter);
2842
if (!this.terminalServices.has(id)) {
29-
const terminalService = new TerminalService(this.serviceContainer, resource, terminalTitle);
43+
const terminalService = new TerminalService(this.serviceContainer, options);
3044
this.terminalServices.set(id, terminalService);
3145
}
3246

3347
// Decorate terminal service with the synchronous service.
34-
return new SynchronousTerminalService(this.fs, this.interpreterService, this.terminalServices.get(id)!);
48+
return new SynchronousTerminalService(this.fs, this.interpreterService, this.terminalServices.get(id)!, interpreter);
3549
}
3650
public createTerminalService(resource?: Uri, title?: string): ITerminalService {
37-
const terminalTitle = typeof title === 'string' && title.trim().length > 0 ? title.trim() : 'Python';
38-
return new TerminalService(this.serviceContainer, resource, terminalTitle);
51+
title = typeof title === 'string' && title.trim().length > 0 ? title.trim() : 'Python';
52+
return new TerminalService(this.serviceContainer, { resource, title });
3953
}
40-
private getTerminalId(title: string, resource?: Uri): string {
41-
if (!resource) {
54+
private getTerminalId(title: string, resource?: Uri, interpreter?: PythonInterpreter): string {
55+
if (!resource && !interpreter) {
4256
return title;
4357
}
44-
const workspaceFolder = this.serviceContainer.get<IWorkspaceService>(IWorkspaceService).getWorkspaceFolder(resource!);
45-
return workspaceFolder ? `${title}:${workspaceFolder.uri.fsPath}` : title;
58+
const workspaceFolder = this.serviceContainer.get<IWorkspaceService>(IWorkspaceService).getWorkspaceFolder(resource || undefined);
59+
return `${title}:${workspaceFolder?.uri.fsPath || ''}:${interpreter?.path}`;
4660
}
4761
}

src/client/common/terminal/helper.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ export class TerminalHelper implements ITerminalHelper {
4747
const commandPrefix = isPowershell ? '& ' : '';
4848
return `${commandPrefix}${command.fileToCommandArgument()} ${args.join(' ')}`.trim();
4949
}
50-
public async getEnvironmentActivationCommands(terminalShellType: TerminalShellType, resource?: Uri): Promise<string[] | undefined> {
50+
public async getEnvironmentActivationCommands(terminalShellType: TerminalShellType, resource?: Uri, interpreter?: PythonInterpreter): Promise<string[] | undefined> {
5151
const providers = [this.pipenv, this.pyenv, this.bashCShellFish, this.commandPromptAndPowerShell];
52-
const promise = this.getActivationCommands(resource || undefined, undefined, terminalShellType, providers);
52+
const promise = this.getActivationCommands(resource || undefined, interpreter, terminalShellType, providers);
5353
this.sendTelemetry(resource, terminalShellType, EventName.PYTHON_INTERPRETER_ACTIVATION_FOR_TERMINAL, promise).ignoreErrors();
5454
return promise;
5555
}

src/client/common/terminal/service.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@
22
// Licensed under the MIT License.
33

44
import { inject, injectable } from 'inversify';
5-
import { CancellationToken, Disposable, Event, EventEmitter, Terminal, Uri } from 'vscode';
5+
import { CancellationToken, Disposable, Event, EventEmitter, Terminal } from 'vscode';
66
import '../../common/extensions';
77
import { IInterpreterService } from '../../interpreter/contracts';
88
import { IServiceContainer } from '../../ioc/types';
99
import { captureTelemetry } from '../../telemetry';
1010
import { EventName } from '../../telemetry/constants';
1111
import { ITerminalManager } from '../application/types';
1212
import { IConfigurationService, IDisposableRegistry } from '../types';
13-
import { ITerminalActivator, ITerminalHelper, ITerminalService, TerminalShellType } from './types';
13+
import { ITerminalActivator, ITerminalHelper, ITerminalService, TerminalCreationOptions, TerminalShellType } from './types';
1414

1515
@injectable()
1616
export class TerminalService implements ITerminalService, Disposable {
@@ -23,7 +23,7 @@ export class TerminalService implements ITerminalService, Disposable {
2323
public get onDidCloseTerminal(): Event<void> {
2424
return this.terminalClosed.event.bind(this.terminalClosed);
2525
}
26-
constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer, private resource?: Uri, private title: string = 'Python') {
26+
constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer, private readonly options?: TerminalCreationOptions) {
2727
const disposableRegistry = this.serviceContainer.get<Disposable[]>(IDisposableRegistry);
2828
disposableRegistry.push(this);
2929
this.terminalHelper = this.serviceContainer.get<ITerminalHelper>(ITerminalHelper);
@@ -56,12 +56,16 @@ export class TerminalService implements ITerminalService, Disposable {
5656
return;
5757
}
5858
this.terminalShellType = this.terminalHelper.identifyTerminalShell(this.terminal);
59-
this.terminal = this.terminalManager.createTerminal({ name: this.title });
59+
this.terminal = this.terminalManager.createTerminal({ name: this.options?.title || 'Python', env: this.options?.env });
6060

6161
// Sometimes the terminal takes some time to start up before it can start accepting input.
6262
await new Promise(resolve => setTimeout(resolve, 100));
6363

64-
await this.terminalActivator.activateEnvironmentInTerminal(this.terminal!, this.resource, preserveFocus);
64+
await this.terminalActivator.activateEnvironmentInTerminal(this.terminal!, {
65+
resource: this.options?.resource,
66+
preserveFocus,
67+
interpreter: this.options?.interpreter
68+
});
6569

6670
this.terminal!.show(preserveFocus);
6771

@@ -75,8 +79,8 @@ export class TerminalService implements ITerminalService, Disposable {
7579
}
7680

7781
private async sendTelemetry() {
78-
const pythonPath = this.serviceContainer.get<IConfigurationService>(IConfigurationService).getSettings(this.resource).pythonPath;
79-
const interpreterInfo = await this.serviceContainer.get<IInterpreterService>(IInterpreterService).getInterpreterDetails(pythonPath);
82+
const pythonPath = this.serviceContainer.get<IConfigurationService>(IConfigurationService).getSettings(this.options?.resource).pythonPath;
83+
const interpreterInfo = this.options?.interpreter || (await this.serviceContainer.get<IInterpreterService>(IInterpreterService).getInterpreterDetails(pythonPath));
8084
const pythonVersion = interpreterInfo && interpreterInfo.version ? interpreterInfo.version.raw : undefined;
8185
const interpreterType = interpreterInfo ? interpreterInfo.type : undefined;
8286
captureTelemetry(EventName.TERMINAL_CREATE, { terminal: this.terminalShellType, pythonVersion, interpreterType });

src/client/common/terminal/syncTerminalService.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { inject } from 'inversify';
77
import * as path from 'path';
88
import { CancellationToken, Disposable, Event } from 'vscode';
99
import { EXTENSION_ROOT_DIR } from '../../constants';
10-
import { IInterpreterService } from '../../interpreter/contracts';
10+
import { IInterpreterService, PythonInterpreter } from '../../interpreter/contracts';
1111
import { Cancellation } from '../cancellation';
1212
import { traceVerbose } from '../logger';
1313
import { IFileSystem, TemporaryFile } from '../platform/types';
@@ -97,7 +97,8 @@ export class SynchronousTerminalService implements ITerminalService, Disposable
9797
constructor(
9898
@inject(IFileSystem) private readonly fs: IFileSystem,
9999
@inject(IInterpreterService) private readonly interpreter: IInterpreterService,
100-
public readonly terminalService: TerminalService
100+
public readonly terminalService: TerminalService,
101+
private readonly pythonInterpreter?: PythonInterpreter
101102
) {}
102103
public dispose() {
103104
this.terminalService.dispose();
@@ -121,7 +122,7 @@ export class SynchronousTerminalService implements ITerminalService, Disposable
121122
const lockFile = await this.createLockFile();
122123
const state = new ExecutionState(lockFile.filePath, this.fs, [command, ...args]);
123124
try {
124-
const pythonExec = await this.interpreter.getActiveInterpreter(undefined);
125+
const pythonExec = this.pythonInterpreter || (await this.interpreter.getActiveInterpreter(undefined));
125126
await this.terminalService.sendCommand(pythonExec?.path || 'python', [
126127
shellExecFile.fileToCommandArgument(),
127128
command.fileToCommandArgument(),

src/client/common/terminal/types.ts

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import { CancellationToken, Event, Terminal, Uri } from 'vscode';
77
import { PythonInterpreter } from '../../interpreter/contracts';
88
import { IEventNamePropertyMapping } from '../../telemetry/index';
9-
import { Resource } from '../types';
9+
import { IDisposable, Resource } from '../types';
1010

1111
export enum TerminalActivationProviders {
1212
bashCShellFish = 'bashCShellFish',
@@ -31,7 +31,7 @@ export enum TerminalShellType {
3131
other = 'other'
3232
}
3333

34-
export interface ITerminalService {
34+
export interface ITerminalService extends IDisposable {
3535
readonly onDidCloseTerminal: Event<void>;
3636
sendCommand(command: string, args: string[], cancel?: CancellationToken): Promise<void>;
3737
sendText(text: string): Promise<void>;
@@ -40,6 +40,31 @@ export interface ITerminalService {
4040

4141
export const ITerminalServiceFactory = Symbol('ITerminalServiceFactory');
4242

43+
export type TerminalCreationOptions = {
44+
/**
45+
* Object with environment variables that will be added to the Terminal.
46+
*/
47+
env?: { [key: string]: string | null };
48+
/**
49+
* Resource identifier. E.g. used to determine python interpreter that needs to be used or environment variables or the like.
50+
*
51+
* @type {Uri}
52+
*/
53+
resource?: Uri;
54+
/**
55+
* Title.
56+
*
57+
* @type {string}
58+
*/
59+
title?: string;
60+
/**
61+
* Associated Python Interpreter.
62+
*
63+
* @type {PythonInterpreter}
64+
*/
65+
interpreter?: PythonInterpreter;
66+
};
67+
4368
export interface ITerminalServiceFactory {
4469
/**
4570
* Gets a terminal service with a specific title.
@@ -50,6 +75,15 @@ export interface ITerminalServiceFactory {
5075
* @memberof ITerminalServiceFactory
5176
*/
5277
getTerminalService(resource?: Uri, title?: string): ITerminalService;
78+
/**
79+
* Gets a terminal service.
80+
* If one exists with the same information, that is returned else a new one is created.
81+
*
82+
* @param {TerminalCreationOptions} [options]
83+
* @returns {ITerminalService}
84+
* @memberof ITerminalServiceFactory
85+
*/
86+
getTerminalService(options?: TerminalCreationOptions): ITerminalService;
5387
createTerminalService(resource?: Uri, title?: string): ITerminalService;
5488
}
5589

@@ -59,13 +93,18 @@ export interface ITerminalHelper {
5993
createTerminal(title?: string): Terminal;
6094
identifyTerminalShell(terminal?: Terminal): TerminalShellType;
6195
buildCommandForTerminal(terminalShellType: TerminalShellType, command: string, args: string[]): string;
62-
getEnvironmentActivationCommands(terminalShellType: TerminalShellType, resource?: Uri): Promise<string[] | undefined>;
96+
getEnvironmentActivationCommands(terminalShellType: TerminalShellType, resource?: Uri, interpreter?: PythonInterpreter): Promise<string[] | undefined>;
6397
getEnvironmentActivationShellCommands(resource: Resource, shell: TerminalShellType, interpreter?: PythonInterpreter): Promise<string[] | undefined>;
6498
}
6599

66100
export const ITerminalActivator = Symbol('ITerminalActivator');
101+
export type TerminalActivationOptions = {
102+
resource?: Resource;
103+
preserveFocus?: boolean;
104+
interpreter?: PythonInterpreter;
105+
};
67106
export interface ITerminalActivator {
68-
activateEnvironmentInTerminal(terminal: Terminal, resource: Uri | undefined, preserveFocus?: boolean): Promise<boolean>;
107+
activateEnvironmentInTerminal(terminal: Terminal, options?: TerminalActivationOptions): Promise<boolean>;
69108
}
70109

71110
export const ITerminalActivationCommandProvider = Symbol('ITerminalActivationCommandProvider');

src/client/common/utils/misc.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,21 @@ export function isResource(resource?: InterpreterUri): resource is Resource {
4040
const uri = resource as Uri;
4141
return typeof uri.path === 'string' && typeof uri.scheme === 'string';
4242
}
43+
44+
/**
45+
* Checking whether something is a Uri.
46+
* Using `instanceof Uri` doesn't always work as the object is not an instance of Uri (at least not in tests).
47+
* That's why VSC too has a helper method `URI.isUri` (though not public).
48+
*
49+
* @export
50+
* @param {InterpreterUri} [resource]
51+
* @returns {resource is Uri}
52+
*/
53+
// tslint:disable-next-line: no-any
54+
export function isUri(resource?: Uri | any): resource is Uri {
55+
if (!resource) {
56+
return false;
57+
}
58+
const uri = resource as Uri;
59+
return typeof uri.path === 'string' && typeof uri.scheme === 'string';
60+
}

src/client/common/variables/environmentVariablesProvider.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,7 @@ export class EnvironmentVariablesProvider implements IEnvironmentVariablesProvid
4444
}
4545
@cacheResourceSpecificInterpreterData('getEnvironmentVariables', cacheDuration)
4646
public async getEnvironmentVariables(resource?: Uri): Promise<EnvironmentVariables> {
47-
const settings = this.configurationService.getSettings(resource);
48-
const workspaceFolderUri = this.getWorkspaceFolderUri(resource);
49-
this.trackedWorkspaceFolders.add(workspaceFolderUri ? workspaceFolderUri.fsPath : '');
50-
this.createFileWatcher(settings.envFile, workspaceFolderUri);
51-
let mergedVars = await this.envVarsService.parseFile(settings.envFile, this.process.env);
47+
let mergedVars = await this.getCustomEnvironmentVariables(resource);
5248
if (!mergedVars) {
5349
mergedVars = {};
5450
}
@@ -63,6 +59,13 @@ export class EnvironmentVariablesProvider implements IEnvironmentVariablesProvid
6359
}
6460
return mergedVars;
6561
}
62+
public async getCustomEnvironmentVariables(resource?: Uri): Promise<EnvironmentVariables | undefined> {
63+
const settings = this.configurationService.getSettings(resource);
64+
const workspaceFolderUri = this.getWorkspaceFolderUri(resource);
65+
this.trackedWorkspaceFolders.add(workspaceFolderUri ? workspaceFolderUri.fsPath : '');
66+
this.createFileWatcher(settings.envFile, workspaceFolderUri);
67+
return this.envVarsService.parseFile(settings.envFile, this.process.env);
68+
}
6669
public configurationChanged(e: ConfigurationChangeEvent) {
6770
this.trackedWorkspaceFolders.forEach(item => {
6871
const uri = item && item.length > 0 ? Uri.file(item) : undefined;

src/client/common/variables/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,5 @@ export const IEnvironmentVariablesProvider = Symbol('IEnvironmentVariablesProvid
3838
export interface IEnvironmentVariablesProvider {
3939
onDidEnvironmentVariablesChange: Event<Uri | undefined>;
4040
getEnvironmentVariables(resource?: Uri): Promise<EnvironmentVariables>;
41+
getCustomEnvironmentVariables(resource?: Uri): Promise<EnvironmentVariables | undefined>;
4142
}

0 commit comments

Comments
 (0)