Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add nushell support to venv activation #20842

Merged
merged 11 commits into from
Mar 31, 2023
4 changes: 0 additions & 4 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -208,10 +208,6 @@ src/client/common/terminal/shellDetectors/vscEnvironmentShellDetector.ts
src/client/common/terminal/shellDetectors/terminalNameShellDetector.ts
src/client/common/terminal/shellDetectors/settingsShellDetector.ts
src/client/common/terminal/shellDetectors/baseShellDetector.ts
src/client/common/terminal/environmentActivationProviders/baseActivationProvider.ts
src/client/common/terminal/environmentActivationProviders/commandPrompt.ts
src/client/common/terminal/environmentActivationProviders/bash.ts
src/client/common/terminal/environmentActivationProviders/pyenvActivationProvider.ts
src/client/common/utils/decorators.ts
src/client/common/utils/enum.ts
src/client/common/utils/platform.ts
Expand Down
1 change: 1 addition & 0 deletions build/existingFiles.json
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,7 @@
"src/test/common/socketStream.test.ts",
"src/test/common/terminals/activation.bash.unit.test.ts",
"src/test/common/terminals/activation.commandPrompt.unit.test.ts",
"src/test/common/terminals/activation.nushell.unit.test.ts",
"src/test/common/terminals/activation.conda.unit.test.ts",
"src/test/common/terminals/activation.unit.test.ts",
"src/test/common/terminals/activator/base.unit.test.ts",
Expand Down
6 changes: 6 additions & 0 deletions src/client/common/serviceRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ import { IProcessLogger } from './process/types';
import { TerminalActivator } from './terminal/activator';
import { PowershellTerminalActivationFailedHandler } from './terminal/activator/powershellFailedHandler';
import { Bash } from './terminal/environmentActivationProviders/bash';
import { Nushell } from './terminal/environmentActivationProviders/nushell';
import { CommandPromptAndPowerShell } from './terminal/environmentActivationProviders/commandPrompt';
import { CondaActivationCommandProvider } from './terminal/environmentActivationProviders/condaActivationProvider';
import { PipEnvActivationCommandProvider } from './terminal/environmentActivationProviders/pipEnvActivationProvider';
Expand Down Expand Up @@ -149,6 +150,11 @@ export function registerTypes(serviceManager: IServiceManager): void {
CommandPromptAndPowerShell,
TerminalActivationProviders.commandPromptAndPowerShell,
);
serviceManager.addSingleton<ITerminalActivationCommandProvider>(
ITerminalActivationCommandProvider,
Nushell,
TerminalActivationProviders.nushell,
);
serviceManager.addSingleton<ITerminalActivationCommandProvider>(
ITerminalActivationCommandProvider,
PyEnvActivationCommandProvider,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable max-classes-per-file */
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

Expand Down Expand Up @@ -48,6 +49,7 @@ abstract class BaseActivationCommandProvider implements ITerminalActivationComma
constructor(@inject(IServiceContainer) protected readonly serviceContainer: IServiceContainer) {}

public abstract isShellSupported(targetShell: TerminalShellType): boolean;

public async getActivationCommands(
resource: Uri | undefined,
targetShell: TerminalShellType,
Expand All @@ -60,13 +62,14 @@ abstract class BaseActivationCommandProvider implements ITerminalActivationComma
}
return this.getActivationCommandsForInterpreter(interpreter.path, targetShell);
}

public abstract getActivationCommandsForInterpreter(
pythonPath: string,
targetShell: TerminalShellType,
): Promise<string[] | undefined>;
}

export type ActivationScripts = Record<TerminalShellType, string[]>;
export type ActivationScripts = Partial<Record<TerminalShellType, string[]>>;
flying-sheep marked this conversation as resolved.
Show resolved Hide resolved

export abstract class VenvBaseActivationCommandProvider extends BaseActivationCommandProvider {
public isShellSupported(targetShell: TerminalShellType): boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { TerminalShellType } from '../types';
import { ActivationScripts, VenvBaseActivationCommandProvider } from './baseActivationProvider';

// For a given shell the scripts are in order of precedence.
const SCRIPTS: ActivationScripts = ({
const SCRIPTS: ActivationScripts = {
// Group 1
[TerminalShellType.wsl]: ['activate.sh', 'activate'],
[TerminalShellType.ksh]: ['activate.sh', 'activate'],
Expand All @@ -19,13 +19,12 @@ const SCRIPTS: ActivationScripts = ({
[TerminalShellType.cshell]: ['activate.csh'],
// Group 3
[TerminalShellType.fish]: ['activate.fish'],
} as unknown) as ActivationScripts;
};

export function getAllScripts(): string[] {
const scripts: string[] = [];
for (const key of Object.keys(SCRIPTS)) {
const shell = key as TerminalShellType;
for (const name of SCRIPTS[shell]) {
for (const names of Object.values(SCRIPTS)) {
for (const name of names) {
if (!scripts.includes(name)) {
scripts.push(name);
}
Expand All @@ -44,7 +43,7 @@ export class Bash extends VenvBaseActivationCommandProvider {
): Promise<string[] | undefined> {
const scriptFile = await this.findScriptFile(pythonPath, targetShell);
if (!scriptFile) {
return;
return undefined;
}
return [`source ${scriptFile.fileToCommandArgumentForPythonExt()}`];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,18 @@ import { TerminalShellType } from '../types';
import { ActivationScripts, VenvBaseActivationCommandProvider } from './baseActivationProvider';

// For a given shell the scripts are in order of precedence.
const SCRIPTS: ActivationScripts = ({
const SCRIPTS: ActivationScripts = {
// Group 1
[TerminalShellType.commandPrompt]: ['activate.bat', 'Activate.ps1'],
// Group 2
[TerminalShellType.powershell]: ['Activate.ps1', 'activate.bat'],
[TerminalShellType.powershellCore]: ['Activate.ps1', 'activate.bat'],
} as unknown) as ActivationScripts;
};

export function getAllScripts(pathJoin: (...p: string[]) => string): string[] {
const scripts: string[] = [];
for (const key of Object.keys(SCRIPTS)) {
const shell = key as TerminalShellType;
for (const name of SCRIPTS[shell]) {
for (const names of Object.values(SCRIPTS)) {
for (const name of names) {
if (!scripts.includes(name)) {
scripts.push(
name,
Expand All @@ -38,13 +37,14 @@ export function getAllScripts(pathJoin: (...p: string[]) => string): string[] {
@injectable()
export class CommandPromptAndPowerShell extends VenvBaseActivationCommandProvider {
protected readonly scripts: ActivationScripts;

constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) {
super(serviceContainer);
this.scripts = ({} as unknown) as ActivationScripts;
for (const key of Object.keys(SCRIPTS)) {
this.scripts = {};
for (const [key, names] of Object.entries(SCRIPTS)) {
const shell = key as TerminalShellType;
const scripts: string[] = [];
for (const name of SCRIPTS[shell]) {
for (const name of names) {
scripts.push(
name,
// We also add scripts in subdirs.
Expand All @@ -62,21 +62,23 @@ export class CommandPromptAndPowerShell extends VenvBaseActivationCommandProvide
): Promise<string[] | undefined> {
const scriptFile = await this.findScriptFile(pythonPath, targetShell);
if (!scriptFile) {
return;
return undefined;
}

if (targetShell === TerminalShellType.commandPrompt && scriptFile.endsWith('activate.bat')) {
return [scriptFile.fileToCommandArgumentForPythonExt()];
} else if (
}
if (
(targetShell === TerminalShellType.powershell || targetShell === TerminalShellType.powershellCore) &&
scriptFile.endsWith('Activate.ps1')
) {
return [`& ${scriptFile.fileToCommandArgumentForPythonExt()}`];
} else if (targetShell === TerminalShellType.commandPrompt && scriptFile.endsWith('Activate.ps1')) {
}
if (targetShell === TerminalShellType.commandPrompt && scriptFile.endsWith('Activate.ps1')) {
// lets not try to run the powershell file from command prompt (user may not have powershell)
return [];
} else {
return;
}

return undefined;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import { injectable } from 'inversify';
import '../../extensions';
import { TerminalShellType } from '../types';
import { ActivationScripts, VenvBaseActivationCommandProvider } from './baseActivationProvider';

// For a given shell the scripts are in order of precedence.
const SCRIPTS: ActivationScripts = {
[TerminalShellType.nushell]: ['activate.nu'],
};

export function getAllScripts(): string[] {
const scripts: string[] = [];
for (const names of Object.values(SCRIPTS)) {
for (const name of names) {
if (!scripts.includes(name)) {
scripts.push(name);
}
}
}
return scripts;
}

@injectable()
export class Nushell extends VenvBaseActivationCommandProvider {
protected readonly scripts = SCRIPTS;

public async getActivationCommandsForInterpreter(
pythonPath: string,
targetShell: TerminalShellType,
): Promise<string[] | undefined> {
const scriptFile = await this.findScriptFile(pythonPath, targetShell);
if (!scriptFile) {
return undefined;
}
return [`overlay use ${scriptFile.fileToCommandArgumentForPythonExt()}`];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { ITerminalActivationCommandProvider, TerminalShellType } from '../types'
export class PyEnvActivationCommandProvider implements ITerminalActivationCommandProvider {
constructor(@inject(IServiceContainer) private readonly serviceContainer: IServiceContainer) {}

// eslint-disable-next-line class-methods-use-this
public isShellSupported(_targetShell: TerminalShellType): boolean {
return true;
}
Expand All @@ -23,7 +24,7 @@ export class PyEnvActivationCommandProvider implements ITerminalActivationComman
.get<IInterpreterService>(IInterpreterService)
.getActiveInterpreter(resource);
if (!interpreter || interpreter.envType !== EnvironmentType.Pyenv || !interpreter.envName) {
return;
return undefined;
}

return [`pyenv shell ${interpreter.envName.toCommandArgumentForPythonExt()}`];
Expand All @@ -37,7 +38,7 @@ export class PyEnvActivationCommandProvider implements ITerminalActivationComman
.get<IInterpreterService>(IInterpreterService)
.getInterpreterDetails(pythonPath);
if (!interpreter || interpreter.envType !== EnvironmentType.Pyenv || !interpreter.envName) {
return;
return undefined;
}

return [`pyenv shell ${interpreter.envName.toCommandArgumentForPythonExt()}`];
Expand Down
7 changes: 5 additions & 2 deletions src/client/common/terminal/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ export class TerminalHelper implements ITerminalHelper {
@named(TerminalActivationProviders.commandPromptAndPowerShell)
private readonly commandPromptAndPowerShell: ITerminalActivationCommandProvider,
@inject(ITerminalActivationCommandProvider)
@named(TerminalActivationProviders.nushell)
private readonly nushell: ITerminalActivationCommandProvider,
@inject(ITerminalActivationCommandProvider)
@named(TerminalActivationProviders.pyenv)
private readonly pyenv: ITerminalActivationCommandProvider,
@inject(ITerminalActivationCommandProvider)
Expand Down Expand Up @@ -72,7 +75,7 @@ export class TerminalHelper implements ITerminalHelper {
resource?: Uri,
interpreter?: PythonEnvironment,
): Promise<string[] | undefined> {
const providers = [this.pipenv, this.pyenv, this.bashCShellFish, this.commandPromptAndPowerShell];
const providers = [this.pipenv, this.pyenv, this.bashCShellFish, this.commandPromptAndPowerShell, this.nushell];
const promise = this.getActivationCommands(resource || undefined, interpreter, terminalShellType, providers);
this.sendTelemetry(
terminalShellType,
Expand All @@ -90,7 +93,7 @@ export class TerminalHelper implements ITerminalHelper {
if (this.platform.osType === OSType.Unknown) {
return;
}
const providers = [this.bashCShellFish, this.commandPromptAndPowerShell];
const providers = [this.bashCShellFish, this.commandPromptAndPowerShell, this.nushell];
const promise = this.getActivationCommands(resource, interpreter, shell, providers);
this.sendTelemetry(
shell,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const IS_POWERSHELL_CORE = /(pwsh$)/i;
const IS_FISH = /(fish$)/i;
const IS_CSHELL = /(csh$)/i;
const IS_TCSHELL = /(tcsh$)/i;
const IS_NUSHELL = /(nu$)/i;
const IS_XONSH = /(xonsh$)/i;

const detectableShells = new Map<TerminalShellType, RegExp>();
Expand All @@ -43,6 +44,7 @@ detectableShells.set(TerminalShellType.commandPrompt, IS_COMMAND);
detectableShells.set(TerminalShellType.fish, IS_FISH);
detectableShells.set(TerminalShellType.tcshell, IS_TCSHELL);
detectableShells.set(TerminalShellType.cshell, IS_CSHELL);
detectableShells.set(TerminalShellType.nushell, IS_NUSHELL);
detectableShells.set(TerminalShellType.powershellCore, IS_POWERSHELL_CORE);
detectableShells.set(TerminalShellType.xonsh, IS_XONSH);

Expand Down
2 changes: 2 additions & 0 deletions src/client/common/terminal/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { IDisposable, Resource } from '../types';
export enum TerminalActivationProviders {
bashCShellFish = 'bashCShellFish',
commandPromptAndPowerShell = 'commandPromptAndPowerShell',
nushell = 'nushell',
pyenv = 'pyenv',
conda = 'conda',
pipenv = 'pipenv',
Expand All @@ -26,6 +27,7 @@ export enum TerminalShellType {
fish = 'fish',
cshell = 'cshell',
tcshell = 'tshell',
nushell = 'nushell',
wsl = 'wsl',
xonsh = 'xonsh',
other = 'other',
Expand Down
6 changes: 6 additions & 0 deletions src/test/common/installer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import { TerminalActivator } from '../../client/common/terminal/activator';
import { PowershellTerminalActivationFailedHandler } from '../../client/common/terminal/activator/powershellFailedHandler';
import { Bash } from '../../client/common/terminal/environmentActivationProviders/bash';
import { CommandPromptAndPowerShell } from '../../client/common/terminal/environmentActivationProviders/commandPrompt';
import { Nushell } from '../../client/common/terminal/environmentActivationProviders/nushell';
import { CondaActivationCommandProvider } from '../../client/common/terminal/environmentActivationProviders/condaActivationProvider';
import { PipEnvActivationCommandProvider } from '../../client/common/terminal/environmentActivationProviders/pipEnvActivationProvider';
import { PyEnvActivationCommandProvider } from '../../client/common/terminal/environmentActivationProviders/pyenvActivationProvider';
Expand Down Expand Up @@ -220,6 +221,11 @@ suite('Installer', () => {
CommandPromptAndPowerShell,
TerminalActivationProviders.commandPromptAndPowerShell,
);
ioc.serviceManager.addSingleton<ITerminalActivationCommandProvider>(
ITerminalActivationCommandProvider,
Nushell,
TerminalActivationProviders.nushell,
);
ioc.serviceManager.addSingleton<ITerminalActivationCommandProvider>(
ITerminalActivationCommandProvider,
PyEnvActivationCommandProvider,
Expand Down
6 changes: 6 additions & 0 deletions src/test/common/moduleInstaller.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import { TerminalActivator } from '../../client/common/terminal/activator';
import { PowershellTerminalActivationFailedHandler } from '../../client/common/terminal/activator/powershellFailedHandler';
import { Bash } from '../../client/common/terminal/environmentActivationProviders/bash';
import { CommandPromptAndPowerShell } from '../../client/common/terminal/environmentActivationProviders/commandPrompt';
import { Nushell } from '../../client/common/terminal/environmentActivationProviders/nushell';
import { CondaActivationCommandProvider } from '../../client/common/terminal/environmentActivationProviders/condaActivationProvider';
import { PipEnvActivationCommandProvider } from '../../client/common/terminal/environmentActivationProviders/pipEnvActivationProvider';
import { PyEnvActivationCommandProvider } from '../../client/common/terminal/environmentActivationProviders/pyenvActivationProvider';
Expand Down Expand Up @@ -231,6 +232,11 @@ suite('Module Installer', () => {
CommandPromptAndPowerShell,
TerminalActivationProviders.commandPromptAndPowerShell,
);
ioc.serviceManager.addSingleton<ITerminalActivationCommandProvider>(
ITerminalActivationCommandProvider,
Nushell,
TerminalActivationProviders.nushell,
);
ioc.serviceManager.addSingleton<ITerminalActivationCommandProvider>(
ITerminalActivationCommandProvider,
PyEnvActivationCommandProvider,
Expand Down
2 changes: 2 additions & 0 deletions src/test/common/serviceRegistry.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import { TerminalActivator } from '../../client/common/terminal/activator';
import { PowershellTerminalActivationFailedHandler } from '../../client/common/terminal/activator/powershellFailedHandler';
import { Bash } from '../../client/common/terminal/environmentActivationProviders/bash';
import { CommandPromptAndPowerShell } from '../../client/common/terminal/environmentActivationProviders/commandPrompt';
import { Nushell } from '../../client/common/terminal/environmentActivationProviders/nushell';
import { CondaActivationCommandProvider } from '../../client/common/terminal/environmentActivationProviders/condaActivationProvider';
import { PipEnvActivationCommandProvider } from '../../client/common/terminal/environmentActivationProviders/pipEnvActivationProvider';
import { PyEnvActivationCommandProvider } from '../../client/common/terminal/environmentActivationProviders/pyenvActivationProvider';
Expand Down Expand Up @@ -116,6 +117,7 @@ suite('Common - Service Registry', () => {
CommandPromptAndPowerShell,
TerminalActivationProviders.commandPromptAndPowerShell,
],
[ITerminalActivationCommandProvider, Nushell, TerminalActivationProviders.nushell],
[IToolExecutionPath, PipEnvExecutionPath, ToolExecutionPath.pipenv],
[ITerminalActivationCommandProvider, CondaActivationCommandProvider, TerminalActivationProviders.conda],
[ITerminalActivationCommandProvider, PipEnvActivationCommandProvider, TerminalActivationProviders.pipenv],
Expand Down
14 changes: 10 additions & 4 deletions src/test/common/terminals/activation.bash.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,15 @@ suite('Terminal Environment Activation (bash)', () => {
? 'and there are spaces in the script file (pythonpath),'
: 'and there are no spaces in the script file (pythonpath),';
suite(suiteTitle, () => {
['activate', 'activate.sh', 'activate.csh', 'activate.fish', 'activate.bat', 'Activate.ps1'].forEach(
(scriptFileName) => {
[
'activate',
'activate.sh',
'activate.csh',
'activate.fish',
'activate.bat',
'activate.nu',
karrtikr marked this conversation as resolved.
Show resolved Hide resolved
'Activate.ps1',
].forEach((scriptFileName) => {
suite(`and script file is ${scriptFileName}`, () => {
let serviceContainer: TypeMoq.IMock<IServiceContainer>;
let interpreterService: TypeMoq.IMock<IInterpreterService>;
Expand Down Expand Up @@ -123,8 +130,7 @@ suite('Terminal Environment Activation (bash)', () => {
});
});
});
},
);
});
});
});
});
Loading