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: 7 additions & 6 deletions src/client/common/enumUtils.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
// tslint:disable:no-any no-unnecessary-class
export class EnumEx {
static getNamesAndValues<T extends number>(e: any) {
return EnumEx.getNames(e).map(n => ({ name: n, value: e[n] as T }));
public static getNamesAndValues<T>(e: any): { name: string; value: T }[] {
return EnumEx.getNames(e).map(n => ({ name: n, value: e[n] }));
}

static getNames(e: any) {
return EnumEx.getObjValues(e).filter(v => typeof v === "string") as string[];
public static getNames(e: any) {
return EnumEx.getObjValues(e).filter(v => typeof v === 'string') as string[];
}

static getValues<T extends number>(e: any) {
return EnumEx.getObjValues(e).filter(v => typeof v === "number") as T[];
public static getValues<T>(e: any) {
return EnumEx.getObjValues(e).filter(v => typeof v === 'number') as any as T[];
}

private static getObjValues(e: any): (number | string)[] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,17 @@ import { BaseActivationCommandProvider } from './baseActivationProvider';

@injectable()
export class Bash extends BaseActivationCommandProvider {
constructor( @inject(IServiceContainer) serviceContainer: IServiceContainer) {
constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) {
super(serviceContainer);
}
public isShellSupported(targetShell: TerminalShellType): boolean {
return targetShell === TerminalShellType.bash ||
targetShell === TerminalShellType.gitbash ||
targetShell === TerminalShellType.wsl ||
targetShell === TerminalShellType.ksh ||
targetShell === TerminalShellType.zsh ||
targetShell === TerminalShellType.cshell ||
targetShell === TerminalShellType.tcshell ||
targetShell === TerminalShellType.fish;
}
public async getActivationCommands(resource: Uri | undefined, targetShell: TerminalShellType): Promise<string[] | undefined> {
Expand All @@ -28,9 +33,14 @@ export class Bash extends BaseActivationCommandProvider {

private getScriptsInOrderOfPreference(targetShell: TerminalShellType): string[] {
switch (targetShell) {
case TerminalShellType.wsl:
case TerminalShellType.ksh:
case TerminalShellType.zsh:
case TerminalShellType.gitbash:
case TerminalShellType.bash: {
return ['activate.sh', 'activate'];
}
case TerminalShellType.tcshell:
case TerminalShellType.cshell: {
return ['activate.csh'];
}
Expand Down
14 changes: 12 additions & 2 deletions src/client/common/terminal/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,33 @@ import { ITerminalActivationCommandProvider, ITerminalHelper, TerminalShellType

// Types of shells can be found here:
// 1. https://wiki.ubuntu.com/ChangingShells
const IS_BASH = /(bash.exe$|wsl.exe$|bash$|zsh$|ksh$)/i;
const IS_GITBASH = /(gitbash.exe$)/i;
const IS_BASH = /(bash.exe$|bash$)/i;
const IS_WSL = /(wsl.exe$)/i;
const IS_ZSH = /(zsh$)/i;
const IS_KSH = /(ksh$)/i;
const IS_COMMAND = /cmd.exe$/i;
const IS_POWERSHELL = /(powershell.exe$|powershell$)/i;
const IS_POWERSHELL_CORE = /(pwsh.exe$|pwsh$)/i;
const IS_FISH = /(fish$)/i;
const IS_CSHELL = /(csh$)/i;
const IS_TCSHELL = /(tcsh$)/i;

@injectable()
export class TerminalHelper implements ITerminalHelper {
private readonly detectableShells: Map<TerminalShellType, RegExp>;
constructor( @inject(IServiceContainer) private serviceContainer: IServiceContainer) {
constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) {

this.detectableShells = new Map<TerminalShellType, RegExp>();
this.detectableShells.set(TerminalShellType.powershell, IS_POWERSHELL);
this.detectableShells.set(TerminalShellType.gitbash, IS_GITBASH);
this.detectableShells.set(TerminalShellType.bash, IS_BASH);
this.detectableShells.set(TerminalShellType.wsl, IS_WSL);
this.detectableShells.set(TerminalShellType.zsh, IS_ZSH);
this.detectableShells.set(TerminalShellType.ksh, IS_KSH);
this.detectableShells.set(TerminalShellType.commandPrompt, IS_COMMAND);
this.detectableShells.set(TerminalShellType.fish, IS_FISH);
this.detectableShells.set(TerminalShellType.tcshell, IS_TCSHELL);
this.detectableShells.set(TerminalShellType.cshell, IS_CSHELL);
this.detectableShells.set(TerminalShellType.powershellCore, IS_POWERSHELL_CORE);
}
Expand Down
19 changes: 12 additions & 7 deletions src/client/common/terminal/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,18 @@
import { Event, Terminal, Uri } from 'vscode';

export enum TerminalShellType {
powershell = 1,
powershellCore = 2,
commandPrompt = 3,
bash = 4,
fish = 5,
cshell = 6,
other = 7
powershell = 'powershell',
powershellCore = 'powershellCore',
commandPrompt = 'commandPrompt',
gitbash = 'gitbash',
bash = 'bash',
zsh = 'zsh',
ksh = 'ksh',
fish = 'fish',
cshell = 'cshell',
tcshell = 'tshell',
wsl = 'wsl',
other = 'other'
}

export interface ITerminalService {
Expand Down
5 changes: 4 additions & 1 deletion src/client/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { registerTypes as platformRegisterTypes } from './common/platform/servic
import { registerTypes as processRegisterTypes } from './common/process/serviceRegistry';
import { registerTypes as commonRegisterTypes } from './common/serviceRegistry';
import { StopWatch } from './common/stopWatch';
import { ITerminalHelper } from './common/terminal/types';
import { GLOBAL_MEMENTO, IConfigurationService, IDisposableRegistry, ILogger, IMemento, IOutputChannel, IPersistentStateFactory, WORKSPACE_MEMENTO } from './common/types';
import { registerTypes as variableRegisterTypes } from './common/variables/serviceRegistry';
import { AttachRequestArguments, LaunchRequestArguments } from './debugger/Common/Contracts';
Expand Down Expand Up @@ -186,10 +187,12 @@ async function sendStartupTelemetry(activatedPromise: Promise<void>, serviceCont
const logger = serviceContainer.get<ILogger>(ILogger);
try {
await activatedPromise;
const terminalHelper = serviceContainer.get<ITerminalHelper>(ITerminalHelper);
const terminalShellType = terminalHelper.identifyTerminalShell(terminalHelper.getTerminalShellPath());
const duration = stopWatch.elapsedTime;
const condaLocator = serviceContainer.get<ICondaService>(ICondaService);
const condaVersion = await condaLocator.getCondaVersion().catch(() => undefined);
const props = condaVersion ? { condaVersion } : undefined;
const props = { condaVersion, terminal: terminalShellType };
sendTelemetryEvent(EDITOR_LOAD, duration, props);
} catch (ex) {
logger.logError('sendStartupTelemetry failed.', ex);
Expand Down
8 changes: 5 additions & 3 deletions src/client/telemetry/types.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { LinterId } from '../linters/types';

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import { TerminalShellType } from '../common/terminal/types';
import { LinterId } from '../linters/types';

export type EditorLoadTelemetry = {
condaVersion: string;
condaVersion: string | undefined;
terminal: TerminalShellType;
};
export type FormatTelemetry = {
tool: 'autopep8' | 'black' | 'yapf';
Expand Down
10 changes: 10 additions & 0 deletions src/test/common/terminals/activation.bash.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ suite('Terminal Environment Activation (bash)', () => {
EnumEx.getNamesAndValues<TerminalShellType>(TerminalShellType).forEach(shellType => {
let isScriptFileSupported = false;
switch (shellType.value) {
case TerminalShellType.zsh:
case TerminalShellType.ksh:
case TerminalShellType.wsl:
case TerminalShellType.gitbash:
case TerminalShellType.bash: {
isScriptFileSupported = ['activate', 'activate.sh'].indexOf(scriptFileName) >= 0;
break;
Expand All @@ -45,6 +49,7 @@ suite('Terminal Environment Activation (bash)', () => {
isScriptFileSupported = ['activate.fish'].indexOf(scriptFileName) >= 0;
break;
}
case TerminalShellType.tcshell:
case TerminalShellType.cshell: {
isScriptFileSupported = ['activate.csh'].indexOf(scriptFileName) >= 0;
break;
Expand All @@ -61,7 +66,12 @@ suite('Terminal Environment Activation (bash)', () => {

const supported = bash.isShellSupported(shellType.value);
switch (shellType.value) {
case TerminalShellType.wsl:
case TerminalShellType.zsh:
case TerminalShellType.ksh:
case TerminalShellType.bash:
case TerminalShellType.gitbash:
case TerminalShellType.tcshell:
case TerminalShellType.cshell:
case TerminalShellType.fish: {
expect(supported).to.be.equal(true, `${shellType.name} shell not supported (it should be)`);
Expand Down
4 changes: 2 additions & 2 deletions src/test/common/terminals/activation.conda.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ suite('Terminal Environment Activation conda', () => {
}
expect(activationCommands).to.deep.equal(expectedActivationCommamnd, 'Incorrect Activation command');
}
EnumEx.getNamesAndValues(TerminalShellType).forEach(shellType => {
EnumEx.getNamesAndValues<TerminalShellType>(TerminalShellType).forEach(shellType => {
test(`Conda activation command for shell ${shellType.name} on (windows)`, async () => {
const pythonPath = path.join('c', 'users', 'xyz', '.conda', 'envs', 'enva', 'python.exe');
await expectNoCondaActivationCommandForPowershell(true, false, false, pythonPath, shellType.value);
Expand All @@ -120,7 +120,7 @@ suite('Terminal Environment Activation conda', () => {
await expectNoCondaActivationCommandForPowershell(false, true, false, pythonPath, shellType.value);
});
});
EnumEx.getNamesAndValues(TerminalShellType).forEach(shellType => {
EnumEx.getNamesAndValues<TerminalShellType>(TerminalShellType).forEach(shellType => {
test(`Conda activation command for shell ${shellType.name} on (windows), containing spaces in environment name`, async () => {
const pythonPath = path.join('c', 'users', 'xyz', '.conda', 'envs', 'enva', 'python.exe');
await expectNoCondaActivationCommandForPowershell(true, false, false, pythonPath, shellType.value, true);
Expand Down
9 changes: 5 additions & 4 deletions src/test/common/terminals/helper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,11 @@ suite('Terminal Service helpers', () => {
shellPathsAndIdentification.set('c:\\windows\\system32\\cmd.exe', TerminalShellType.commandPrompt);

shellPathsAndIdentification.set('c:\\windows\\system32\\bash.exe', TerminalShellType.bash);
shellPathsAndIdentification.set('c:\\windows\\system32\\wsl.exe', TerminalShellType.bash);
shellPathsAndIdentification.set('c:\\windows\\system32\\gitbash.exe', TerminalShellType.bash);
shellPathsAndIdentification.set('c:\\windows\\system32\\wsl.exe', TerminalShellType.wsl);
shellPathsAndIdentification.set('c:\\windows\\system32\\gitbash.exe', TerminalShellType.gitbash);
shellPathsAndIdentification.set('/usr/bin/bash', TerminalShellType.bash);
shellPathsAndIdentification.set('/usr/bin/zsh', TerminalShellType.bash);
shellPathsAndIdentification.set('/usr/bin/ksh', TerminalShellType.bash);
shellPathsAndIdentification.set('/usr/bin/zsh', TerminalShellType.zsh);
shellPathsAndIdentification.set('/usr/bin/ksh', TerminalShellType.ksh);

shellPathsAndIdentification.set('c:\\windows\\system32\\powershell.exe', TerminalShellType.powershell);
shellPathsAndIdentification.set('c:\\windows\\system32\\pwsh.exe', TerminalShellType.powershellCore);
Expand All @@ -65,6 +65,7 @@ suite('Terminal Service helpers', () => {
shellPathsAndIdentification.set('/usr/bin/shell', TerminalShellType.other);

shellPathsAndIdentification.set('/usr/bin/csh', TerminalShellType.cshell);
shellPathsAndIdentification.set('/usr/bin/tcsh', TerminalShellType.tcshell);

shellPathsAndIdentification.forEach((shellType, shellPath) => {
expect(helper.identifyTerminalShell(shellPath)).to.equal(shellType, `Incorrect Shell Type for path '${shellPath}'`);
Expand Down