Skip to content

Commit 5528e0e

Browse files
flying-sheepKartik Raj
and
Kartik Raj
authored
Add nushell support to venv activation (#20842)
Fixes #19359 --------- Co-authored-by: Kartik Raj <[email protected]>
1 parent 56d1912 commit 5528e0e

18 files changed

+287
-115
lines changed

.eslintignore

-4
Original file line numberDiff line numberDiff line change
@@ -207,10 +207,6 @@ src/client/common/terminal/shellDetectors/vscEnvironmentShellDetector.ts
207207
src/client/common/terminal/shellDetectors/terminalNameShellDetector.ts
208208
src/client/common/terminal/shellDetectors/settingsShellDetector.ts
209209
src/client/common/terminal/shellDetectors/baseShellDetector.ts
210-
src/client/common/terminal/environmentActivationProviders/baseActivationProvider.ts
211-
src/client/common/terminal/environmentActivationProviders/commandPrompt.ts
212-
src/client/common/terminal/environmentActivationProviders/bash.ts
213-
src/client/common/terminal/environmentActivationProviders/pyenvActivationProvider.ts
214210
src/client/common/utils/decorators.ts
215211
src/client/common/utils/enum.ts
216212
src/client/common/utils/platform.ts

build/existingFiles.json

+1
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,7 @@
379379
"src/test/common/socketStream.test.ts",
380380
"src/test/common/terminals/activation.bash.unit.test.ts",
381381
"src/test/common/terminals/activation.commandPrompt.unit.test.ts",
382+
"src/test/common/terminals/activation.nushell.unit.test.ts",
382383
"src/test/common/terminals/activation.conda.unit.test.ts",
383384
"src/test/common/terminals/activation.unit.test.ts",
384385
"src/test/common/terminals/activator/base.unit.test.ts",

src/client/common/serviceRegistry.ts

+6
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ import { IProcessLogger } from './process/types';
6565
import { TerminalActivator } from './terminal/activator';
6666
import { PowershellTerminalActivationFailedHandler } from './terminal/activator/powershellFailedHandler';
6767
import { Bash } from './terminal/environmentActivationProviders/bash';
68+
import { Nushell } from './terminal/environmentActivationProviders/nushell';
6869
import { CommandPromptAndPowerShell } from './terminal/environmentActivationProviders/commandPrompt';
6970
import { CondaActivationCommandProvider } from './terminal/environmentActivationProviders/condaActivationProvider';
7071
import { PipEnvActivationCommandProvider } from './terminal/environmentActivationProviders/pipEnvActivationProvider';
@@ -143,6 +144,11 @@ export function registerTypes(serviceManager: IServiceManager): void {
143144
CommandPromptAndPowerShell,
144145
TerminalActivationProviders.commandPromptAndPowerShell,
145146
);
147+
serviceManager.addSingleton<ITerminalActivationCommandProvider>(
148+
ITerminalActivationCommandProvider,
149+
Nushell,
150+
TerminalActivationProviders.nushell,
151+
);
146152
serviceManager.addSingleton<ITerminalActivationCommandProvider>(
147153
ITerminalActivationCommandProvider,
148154
PyEnvActivationCommandProvider,

src/client/common/terminal/environmentActivationProviders/baseActivationProvider.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable max-classes-per-file */
12
// Copyright (c) Microsoft Corporation. All rights reserved.
23
// Licensed under the MIT License.
34

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

5051
public abstract isShellSupported(targetShell: TerminalShellType): boolean;
52+
5153
public async getActivationCommands(
5254
resource: Uri | undefined,
5355
targetShell: TerminalShellType,
@@ -60,13 +62,14 @@ abstract class BaseActivationCommandProvider implements ITerminalActivationComma
6062
}
6163
return this.getActivationCommandsForInterpreter(interpreter.path, targetShell);
6264
}
65+
6366
public abstract getActivationCommandsForInterpreter(
6467
pythonPath: string,
6568
targetShell: TerminalShellType,
6669
): Promise<string[] | undefined>;
6770
}
6871

69-
export type ActivationScripts = Record<TerminalShellType, string[]>;
72+
export type ActivationScripts = Partial<Record<TerminalShellType, string[]>>;
7073

7174
export abstract class VenvBaseActivationCommandProvider extends BaseActivationCommandProvider {
7275
public isShellSupported(targetShell: TerminalShellType): boolean {

src/client/common/terminal/environmentActivationProviders/bash.ts

+5-6
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { TerminalShellType } from '../types';
77
import { ActivationScripts, VenvBaseActivationCommandProvider } from './baseActivationProvider';
88

99
// For a given shell the scripts are in order of precedence.
10-
const SCRIPTS: ActivationScripts = ({
10+
const SCRIPTS: ActivationScripts = {
1111
// Group 1
1212
[TerminalShellType.wsl]: ['activate.sh', 'activate'],
1313
[TerminalShellType.ksh]: ['activate.sh', 'activate'],
@@ -19,13 +19,12 @@ const SCRIPTS: ActivationScripts = ({
1919
[TerminalShellType.cshell]: ['activate.csh'],
2020
// Group 3
2121
[TerminalShellType.fish]: ['activate.fish'],
22-
} as unknown) as ActivationScripts;
22+
};
2323

2424
export function getAllScripts(): string[] {
2525
const scripts: string[] = [];
26-
for (const key of Object.keys(SCRIPTS)) {
27-
const shell = key as TerminalShellType;
28-
for (const name of SCRIPTS[shell]) {
26+
for (const names of Object.values(SCRIPTS)) {
27+
for (const name of names) {
2928
if (!scripts.includes(name)) {
3029
scripts.push(name);
3130
}
@@ -44,7 +43,7 @@ export class Bash extends VenvBaseActivationCommandProvider {
4443
): Promise<string[] | undefined> {
4544
const scriptFile = await this.findScriptFile(pythonPath, targetShell);
4645
if (!scriptFile) {
47-
return;
46+
return undefined;
4847
}
4948
return [`source ${scriptFile.fileToCommandArgumentForPythonExt()}`];
5049
}

src/client/common/terminal/environmentActivationProviders/commandPrompt.ts

+15-13
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,18 @@ import { TerminalShellType } from '../types';
99
import { ActivationScripts, VenvBaseActivationCommandProvider } from './baseActivationProvider';
1010

1111
// For a given shell the scripts are in order of precedence.
12-
const SCRIPTS: ActivationScripts = ({
12+
const SCRIPTS: ActivationScripts = {
1313
// Group 1
1414
[TerminalShellType.commandPrompt]: ['activate.bat', 'Activate.ps1'],
1515
// Group 2
1616
[TerminalShellType.powershell]: ['Activate.ps1', 'activate.bat'],
1717
[TerminalShellType.powershellCore]: ['Activate.ps1', 'activate.bat'],
18-
} as unknown) as ActivationScripts;
18+
};
1919

2020
export function getAllScripts(pathJoin: (...p: string[]) => string): string[] {
2121
const scripts: string[] = [];
22-
for (const key of Object.keys(SCRIPTS)) {
23-
const shell = key as TerminalShellType;
24-
for (const name of SCRIPTS[shell]) {
22+
for (const names of Object.values(SCRIPTS)) {
23+
for (const name of names) {
2524
if (!scripts.includes(name)) {
2625
scripts.push(
2726
name,
@@ -38,13 +37,14 @@ export function getAllScripts(pathJoin: (...p: string[]) => string): string[] {
3837
@injectable()
3938
export class CommandPromptAndPowerShell extends VenvBaseActivationCommandProvider {
4039
protected readonly scripts: ActivationScripts;
40+
4141
constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) {
4242
super(serviceContainer);
43-
this.scripts = ({} as unknown) as ActivationScripts;
44-
for (const key of Object.keys(SCRIPTS)) {
43+
this.scripts = {};
44+
for (const [key, names] of Object.entries(SCRIPTS)) {
4545
const shell = key as TerminalShellType;
4646
const scripts: string[] = [];
47-
for (const name of SCRIPTS[shell]) {
47+
for (const name of names) {
4848
scripts.push(
4949
name,
5050
// We also add scripts in subdirs.
@@ -62,21 +62,23 @@ export class CommandPromptAndPowerShell extends VenvBaseActivationCommandProvide
6262
): Promise<string[] | undefined> {
6363
const scriptFile = await this.findScriptFile(pythonPath, targetShell);
6464
if (!scriptFile) {
65-
return;
65+
return undefined;
6666
}
6767

6868
if (targetShell === TerminalShellType.commandPrompt && scriptFile.endsWith('activate.bat')) {
6969
return [scriptFile.fileToCommandArgumentForPythonExt()];
70-
} else if (
70+
}
71+
if (
7172
(targetShell === TerminalShellType.powershell || targetShell === TerminalShellType.powershellCore) &&
7273
scriptFile.endsWith('Activate.ps1')
7374
) {
7475
return [`& ${scriptFile.fileToCommandArgumentForPythonExt()}`];
75-
} else if (targetShell === TerminalShellType.commandPrompt && scriptFile.endsWith('Activate.ps1')) {
76+
}
77+
if (targetShell === TerminalShellType.commandPrompt && scriptFile.endsWith('Activate.ps1')) {
7678
// lets not try to run the powershell file from command prompt (user may not have powershell)
7779
return [];
78-
} else {
79-
return;
8080
}
81+
82+
return undefined;
8183
}
8284
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
import { injectable } from 'inversify';
5+
import '../../extensions';
6+
import { TerminalShellType } from '../types';
7+
import { ActivationScripts, VenvBaseActivationCommandProvider } from './baseActivationProvider';
8+
9+
// For a given shell the scripts are in order of precedence.
10+
const SCRIPTS: ActivationScripts = {
11+
[TerminalShellType.nushell]: ['activate.nu'],
12+
};
13+
14+
export function getAllScripts(): string[] {
15+
const scripts: string[] = [];
16+
for (const names of Object.values(SCRIPTS)) {
17+
for (const name of names) {
18+
if (!scripts.includes(name)) {
19+
scripts.push(name);
20+
}
21+
}
22+
}
23+
return scripts;
24+
}
25+
26+
@injectable()
27+
export class Nushell extends VenvBaseActivationCommandProvider {
28+
protected readonly scripts = SCRIPTS;
29+
30+
public async getActivationCommandsForInterpreter(
31+
pythonPath: string,
32+
targetShell: TerminalShellType,
33+
): Promise<string[] | undefined> {
34+
const scriptFile = await this.findScriptFile(pythonPath, targetShell);
35+
if (!scriptFile) {
36+
return undefined;
37+
}
38+
return [`overlay use ${scriptFile.fileToCommandArgumentForPythonExt()}`];
39+
}
40+
}

src/client/common/terminal/environmentActivationProviders/pyenvActivationProvider.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { ITerminalActivationCommandProvider, TerminalShellType } from '../types'
1414
export class PyEnvActivationCommandProvider implements ITerminalActivationCommandProvider {
1515
constructor(@inject(IServiceContainer) private readonly serviceContainer: IServiceContainer) {}
1616

17+
// eslint-disable-next-line class-methods-use-this
1718
public isShellSupported(_targetShell: TerminalShellType): boolean {
1819
return true;
1920
}
@@ -23,7 +24,7 @@ export class PyEnvActivationCommandProvider implements ITerminalActivationComman
2324
.get<IInterpreterService>(IInterpreterService)
2425
.getActiveInterpreter(resource);
2526
if (!interpreter || interpreter.envType !== EnvironmentType.Pyenv || !interpreter.envName) {
26-
return;
27+
return undefined;
2728
}
2829

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

4344
return [`pyenv shell ${interpreter.envName.toCommandArgumentForPythonExt()}`];

src/client/common/terminal/helper.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ export class TerminalHelper implements ITerminalHelper {
4242
@named(TerminalActivationProviders.commandPromptAndPowerShell)
4343
private readonly commandPromptAndPowerShell: ITerminalActivationCommandProvider,
4444
@inject(ITerminalActivationCommandProvider)
45+
@named(TerminalActivationProviders.nushell)
46+
private readonly nushell: ITerminalActivationCommandProvider,
47+
@inject(ITerminalActivationCommandProvider)
4548
@named(TerminalActivationProviders.pyenv)
4649
private readonly pyenv: ITerminalActivationCommandProvider,
4750
@inject(ITerminalActivationCommandProvider)
@@ -72,7 +75,7 @@ export class TerminalHelper implements ITerminalHelper {
7275
resource?: Uri,
7376
interpreter?: PythonEnvironment,
7477
): Promise<string[] | undefined> {
75-
const providers = [this.pipenv, this.pyenv, this.bashCShellFish, this.commandPromptAndPowerShell];
78+
const providers = [this.pipenv, this.pyenv, this.bashCShellFish, this.commandPromptAndPowerShell, this.nushell];
7679
const promise = this.getActivationCommands(resource || undefined, interpreter, terminalShellType, providers);
7780
this.sendTelemetry(
7881
terminalShellType,
@@ -90,7 +93,7 @@ export class TerminalHelper implements ITerminalHelper {
9093
if (this.platform.osType === OSType.Unknown) {
9194
return;
9295
}
93-
const providers = [this.bashCShellFish, this.commandPromptAndPowerShell];
96+
const providers = [this.bashCShellFish, this.commandPromptAndPowerShell, this.nushell];
9497
const promise = this.getActivationCommands(resource, interpreter, shell, providers);
9598
this.sendTelemetry(
9699
shell,

src/client/common/terminal/shellDetectors/baseShellDetector.ts

+2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ const IS_POWERSHELL_CORE = /(pwsh$)/i;
3030
const IS_FISH = /(fish$)/i;
3131
const IS_CSHELL = /(csh$)/i;
3232
const IS_TCSHELL = /(tcsh$)/i;
33+
const IS_NUSHELL = /(nu$)/i;
3334
const IS_XONSH = /(xonsh$)/i;
3435

3536
const detectableShells = new Map<TerminalShellType, RegExp>();
@@ -43,6 +44,7 @@ detectableShells.set(TerminalShellType.commandPrompt, IS_COMMAND);
4344
detectableShells.set(TerminalShellType.fish, IS_FISH);
4445
detectableShells.set(TerminalShellType.tcshell, IS_TCSHELL);
4546
detectableShells.set(TerminalShellType.cshell, IS_CSHELL);
47+
detectableShells.set(TerminalShellType.nushell, IS_NUSHELL);
4648
detectableShells.set(TerminalShellType.powershellCore, IS_POWERSHELL_CORE);
4749
detectableShells.set(TerminalShellType.xonsh, IS_XONSH);
4850

src/client/common/terminal/types.ts

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { IDisposable, Resource } from '../types';
1111
export enum TerminalActivationProviders {
1212
bashCShellFish = 'bashCShellFish',
1313
commandPromptAndPowerShell = 'commandPromptAndPowerShell',
14+
nushell = 'nushell',
1415
pyenv = 'pyenv',
1516
conda = 'conda',
1617
pipenv = 'pipenv',
@@ -26,6 +27,7 @@ export enum TerminalShellType {
2627
fish = 'fish',
2728
cshell = 'cshell',
2829
tcshell = 'tshell',
30+
nushell = 'nushell',
2931
wsl = 'wsl',
3032
xonsh = 'xonsh',
3133
other = 'other',

src/test/common/installer.test.ts

+6
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import { TerminalActivator } from '../../client/common/terminal/activator';
5151
import { PowershellTerminalActivationFailedHandler } from '../../client/common/terminal/activator/powershellFailedHandler';
5252
import { Bash } from '../../client/common/terminal/environmentActivationProviders/bash';
5353
import { CommandPromptAndPowerShell } from '../../client/common/terminal/environmentActivationProviders/commandPrompt';
54+
import { Nushell } from '../../client/common/terminal/environmentActivationProviders/nushell';
5455
import { CondaActivationCommandProvider } from '../../client/common/terminal/environmentActivationProviders/condaActivationProvider';
5556
import { PipEnvActivationCommandProvider } from '../../client/common/terminal/environmentActivationProviders/pipEnvActivationProvider';
5657
import { PyEnvActivationCommandProvider } from '../../client/common/terminal/environmentActivationProviders/pyenvActivationProvider';
@@ -214,6 +215,11 @@ suite('Installer', () => {
214215
CommandPromptAndPowerShell,
215216
TerminalActivationProviders.commandPromptAndPowerShell,
216217
);
218+
ioc.serviceManager.addSingleton<ITerminalActivationCommandProvider>(
219+
ITerminalActivationCommandProvider,
220+
Nushell,
221+
TerminalActivationProviders.nushell,
222+
);
217223
ioc.serviceManager.addSingleton<ITerminalActivationCommandProvider>(
218224
ITerminalActivationCommandProvider,
219225
PyEnvActivationCommandProvider,

src/test/common/moduleInstaller.test.ts

+6
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import { TerminalActivator } from '../../client/common/terminal/activator';
5050
import { PowershellTerminalActivationFailedHandler } from '../../client/common/terminal/activator/powershellFailedHandler';
5151
import { Bash } from '../../client/common/terminal/environmentActivationProviders/bash';
5252
import { CommandPromptAndPowerShell } from '../../client/common/terminal/environmentActivationProviders/commandPrompt';
53+
import { Nushell } from '../../client/common/terminal/environmentActivationProviders/nushell';
5354
import { CondaActivationCommandProvider } from '../../client/common/terminal/environmentActivationProviders/condaActivationProvider';
5455
import { PipEnvActivationCommandProvider } from '../../client/common/terminal/environmentActivationProviders/pipEnvActivationProvider';
5556
import { PyEnvActivationCommandProvider } from '../../client/common/terminal/environmentActivationProviders/pyenvActivationProvider';
@@ -225,6 +226,11 @@ suite('Module Installer', () => {
225226
CommandPromptAndPowerShell,
226227
TerminalActivationProviders.commandPromptAndPowerShell,
227228
);
229+
ioc.serviceManager.addSingleton<ITerminalActivationCommandProvider>(
230+
ITerminalActivationCommandProvider,
231+
Nushell,
232+
TerminalActivationProviders.nushell,
233+
);
228234
ioc.serviceManager.addSingleton<ITerminalActivationCommandProvider>(
229235
ITerminalActivationCommandProvider,
230236
PyEnvActivationCommandProvider,

src/test/common/serviceRegistry.unit.test.ts

+2
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import { TerminalActivator } from '../../client/common/terminal/activator';
4040
import { PowershellTerminalActivationFailedHandler } from '../../client/common/terminal/activator/powershellFailedHandler';
4141
import { Bash } from '../../client/common/terminal/environmentActivationProviders/bash';
4242
import { CommandPromptAndPowerShell } from '../../client/common/terminal/environmentActivationProviders/commandPrompt';
43+
import { Nushell } from '../../client/common/terminal/environmentActivationProviders/nushell';
4344
import { CondaActivationCommandProvider } from '../../client/common/terminal/environmentActivationProviders/condaActivationProvider';
4445
import { PipEnvActivationCommandProvider } from '../../client/common/terminal/environmentActivationProviders/pipEnvActivationProvider';
4546
import { PyEnvActivationCommandProvider } from '../../client/common/terminal/environmentActivationProviders/pyenvActivationProvider';
@@ -113,6 +114,7 @@ suite('Common - Service Registry', () => {
113114
CommandPromptAndPowerShell,
114115
TerminalActivationProviders.commandPromptAndPowerShell,
115116
],
117+
[ITerminalActivationCommandProvider, Nushell, TerminalActivationProviders.nushell],
116118
[IToolExecutionPath, PipEnvExecutionPath, ToolExecutionPath.pipenv],
117119
[ITerminalActivationCommandProvider, CondaActivationCommandProvider, TerminalActivationProviders.conda],
118120
[ITerminalActivationCommandProvider, PipEnvActivationCommandProvider, TerminalActivationProviders.pipenv],

0 commit comments

Comments
 (0)