Skip to content

Commit b01d2b8

Browse files
authored
fix: Shell Type API updated in core (#221)
1 parent b5f7a8d commit b01d2b8

File tree

10 files changed

+162
-226
lines changed

10 files changed

+162
-226
lines changed

.vscode/tasks.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,20 @@
3232
"label": "tasks: watch-tests",
3333
"dependsOn": ["npm: watch", "npm: watch-tests"],
3434
"problemMatcher": []
35+
},
36+
{
37+
"type": "npm",
38+
"script": "unittest",
39+
"dependsOn": ["tasks: watch-tests"],
40+
"problemMatcher": "$tsc",
41+
"presentation": {
42+
"reveal": "never",
43+
"group": "test"
44+
},
45+
"group": {
46+
"kind": "test",
47+
"isDefault": false
48+
}
3549
}
3650
]
3751
}

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@
66
"publisher": "ms-python",
77
"preview": true,
88
"engines": {
9-
"vscode": "^1.98.0-20250221"
9+
"vscode": "^1.99.0-20250317"
1010
},
1111
"categories": [
1212
"Other"
1313
],
1414
"enabledApiProposals": [
15-
"terminalShellType"
15+
"terminalShellType",
16+
"terminalShellEnv"
1617
],
1718
"capabilities": {
1819
"untrustedWorkspaces": {

src/api.ts

Lines changed: 8 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -48,23 +48,6 @@ export interface PythonCommandRunConfiguration {
4848
args?: string[];
4949
}
5050

51-
export enum TerminalShellType {
52-
powershell = 'powershell',
53-
powershellCore = 'powershellCore',
54-
commandPrompt = 'commandPrompt',
55-
gitbash = 'gitbash',
56-
bash = 'bash',
57-
zsh = 'zsh',
58-
ksh = 'ksh',
59-
fish = 'fish',
60-
cshell = 'cshell',
61-
tcshell = 'tcshell',
62-
nushell = 'nushell',
63-
wsl = 'wsl',
64-
xonsh = 'xonsh',
65-
unknown = 'unknown',
66-
}
67-
6851
/**
6952
* Contains details on how to use a particular python environment
7053
*
@@ -73,7 +56,7 @@ export enum TerminalShellType {
7356
* 2. If {@link PythonEnvironmentExecutionInfo.activatedRun} is not provided, then:
7457
* - If {@link PythonEnvironmentExecutionInfo.shellActivation} is provided and shell type is known, then that will be used.
7558
* - If {@link PythonEnvironmentExecutionInfo.shellActivation} is provided and shell type is not known, then:
76-
* - {@link TerminalShellType.unknown} will be used if provided.
59+
* - 'unknown' will be used if provided.
7760
* - {@link PythonEnvironmentExecutionInfo.activation} will be used otherwise.
7861
* - If {@link PythonEnvironmentExecutionInfo.shellActivation} is not provided, then {@link PythonEnvironmentExecutionInfo.activation} will be used.
7962
* - If {@link PythonEnvironmentExecutionInfo.activation} is not provided, then {@link PythonEnvironmentExecutionInfo.run} will be used.
@@ -82,7 +65,7 @@ export enum TerminalShellType {
8265
* 1. If {@link PythonEnvironmentExecutionInfo.shellActivation} is provided and shell type is known, then that will be used.
8366
* 2. If {@link PythonEnvironmentExecutionInfo.shellActivation} is provided and shell type is not known, then {@link PythonEnvironmentExecutionInfo.activation} will be used.
8467
* 3. If {@link PythonEnvironmentExecutionInfo.shellActivation} is not provided, then:
85-
* - {@link TerminalShellType.unknown} will be used if provided.
68+
* - 'unknown' will be used if provided.
8669
* - {@link PythonEnvironmentExecutionInfo.activation} will be used otherwise.
8770
* 4. If {@link PythonEnvironmentExecutionInfo.activation} is not provided, then {@link PythonEnvironmentExecutionInfo.run} will be used.
8871
*
@@ -107,11 +90,11 @@ export interface PythonEnvironmentExecutionInfo {
10790
/**
10891
* Details on how to activate an environment using a shell specific command.
10992
* If set this will override the {@link PythonEnvironmentExecutionInfo.activation}.
110-
* {@link TerminalShellType.unknown} is used if shell type is not known.
111-
* If {@link TerminalShellType.unknown} is not provided and shell type is not known then
93+
* 'unknown' is used if shell type is not known.
94+
* If 'unknown' is not provided and shell type is not known then
11295
* {@link PythonEnvironmentExecutionInfo.activation} if set.
11396
*/
114-
shellActivation?: Map<TerminalShellType, PythonCommandRunConfiguration[]>;
97+
shellActivation?: Map<string, PythonCommandRunConfiguration[]>;
11598

11699
/**
117100
* Details on how to deactivate an environment.
@@ -121,11 +104,11 @@ export interface PythonEnvironmentExecutionInfo {
121104
/**
122105
* Details on how to deactivate an environment using a shell specific command.
123106
* If set this will override the {@link PythonEnvironmentExecutionInfo.deactivation} property.
124-
* {@link TerminalShellType.unknown} is used if shell type is not known.
125-
* If {@link TerminalShellType.unknown} is not provided and shell type is not known then
107+
* 'unknown' is used if shell type is not known.
108+
* If 'unknown' is not provided and shell type is not known then
126109
* {@link PythonEnvironmentExecutionInfo.deactivation} if set.
127110
*/
128-
shellDeactivation?: Map<TerminalShellType, PythonCommandRunConfiguration[]>;
111+
shellDeactivation?: Map<string, PythonCommandRunConfiguration[]>;
129112
}
130113

131114
/**

src/features/common/activation.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Terminal } from 'vscode';
2-
import { PythonCommandRunConfiguration, PythonEnvironment, TerminalShellType } from '../../api';
2+
import { PythonCommandRunConfiguration, PythonEnvironment } from '../../api';
33
import { identifyTerminalShell } from './shellDetector';
44

55
export function isActivatableEnvironment(environment: PythonEnvironment): boolean {
@@ -20,7 +20,7 @@ export function getActivationCommand(
2020
if (environment.execInfo?.shellActivation) {
2121
activation = environment.execInfo.shellActivation.get(shell);
2222
if (!activation) {
23-
activation = environment.execInfo.shellActivation.get(TerminalShellType.unknown);
23+
activation = environment.execInfo.shellActivation.get('unknown');
2424
}
2525
}
2626

@@ -41,7 +41,7 @@ export function getDeactivationCommand(
4141
if (environment.execInfo?.shellDeactivation) {
4242
deactivation = environment.execInfo.shellDeactivation.get(shell);
4343
if (!deactivation) {
44-
deactivation = environment.execInfo.shellDeactivation.get(TerminalShellType.unknown);
44+
deactivation = environment.execInfo.shellDeactivation.get('unknown');
4545
}
4646
}
4747

src/features/common/shellDetector.ts

Lines changed: 52 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import * as os from 'os';
2-
import { Terminal, TerminalShellType as TerminalShellTypeVscode } from 'vscode';
2+
import { Terminal } from 'vscode';
33
import { isWindows } from '../../managers/common/utils';
44
import { vscodeShell } from '../../common/vscodeEnv.apis';
55
import { getConfiguration } from '../../common/workspace.apis';
6-
import { TerminalShellType } from '../../api';
76

87
/*
98
When identifying the shell use the following algorithm:
@@ -22,67 +21,56 @@ const IS_WSL = /(wsl$)/i;
2221
const IS_ZSH = /(zsh$)/i;
2322
const IS_KSH = /(ksh$)/i;
2423
const IS_COMMAND = /(cmd$)/i;
25-
const IS_POWERSHELL = /(powershell$)/i;
26-
const IS_POWERSHELL_CORE = /(pwsh$)/i;
24+
const IS_POWERSHELL = /(powershell$|pwsh$)/i;
2725
const IS_FISH = /(fish$)/i;
2826
const IS_CSHELL = /(csh$)/i;
2927
const IS_TCSHELL = /(tcsh$)/i;
3028
const IS_NUSHELL = /(nu$)/i;
3129
const IS_XONSH = /(xonsh$)/i;
3230

33-
/** Converts an object from a trusted source (i.e. without unknown entries) to a typed array */
34-
function _entries<T extends { [key: string]: object }, K extends keyof T>(o: T): [keyof T, T[K]][] {
35-
return Object.entries(o) as [keyof T, T[K]][];
36-
}
37-
38-
type KnownShellType = Exclude<TerminalShellType, TerminalShellType.unknown>;
39-
const detectableShells = new Map<KnownShellType, RegExp>(
40-
_entries({
41-
[TerminalShellType.powershell]: IS_POWERSHELL,
42-
[TerminalShellType.gitbash]: IS_GITBASH,
43-
[TerminalShellType.bash]: IS_BASH,
44-
[TerminalShellType.wsl]: IS_WSL,
45-
[TerminalShellType.zsh]: IS_ZSH,
46-
[TerminalShellType.ksh]: IS_KSH,
47-
[TerminalShellType.commandPrompt]: IS_COMMAND,
48-
[TerminalShellType.fish]: IS_FISH,
49-
[TerminalShellType.tcshell]: IS_TCSHELL,
50-
[TerminalShellType.cshell]: IS_CSHELL,
51-
[TerminalShellType.nushell]: IS_NUSHELL,
52-
[TerminalShellType.powershellCore]: IS_POWERSHELL_CORE,
53-
[TerminalShellType.xonsh]: IS_XONSH,
54-
// This `satisfies` makes sure all shells are covered
55-
} satisfies Record<KnownShellType, RegExp>),
56-
);
57-
58-
function identifyShellFromShellPath(shellPath: string): TerminalShellType {
31+
const detectableShells = new Map<string, RegExp>([
32+
['pwsh', IS_POWERSHELL],
33+
['gitbash', IS_GITBASH],
34+
['bash', IS_BASH],
35+
['wsl', IS_WSL],
36+
['zsh', IS_ZSH],
37+
['ksh', IS_KSH],
38+
['cmd', IS_COMMAND],
39+
['fish', IS_FISH],
40+
['tcsh', IS_TCSHELL],
41+
['csh', IS_CSHELL],
42+
['nu', IS_NUSHELL],
43+
['xonsh', IS_XONSH],
44+
]);
45+
46+
function identifyShellFromShellPath(shellPath: string): string {
5947
// Remove .exe extension so shells can be more consistently detected
6048
// on Windows (including Cygwin).
6149
const basePath = shellPath.replace(/\.exe$/i, '');
6250

6351
const shell = Array.from(detectableShells.keys()).reduce((matchedShell, shellToDetect) => {
64-
if (matchedShell === TerminalShellType.unknown) {
52+
if (matchedShell === 'unknown') {
6553
const pat = detectableShells.get(shellToDetect);
6654
if (pat && pat.test(basePath)) {
6755
return shellToDetect;
6856
}
6957
}
7058
return matchedShell;
71-
}, TerminalShellType.unknown);
59+
}, 'unknown');
7260

7361
return shell;
7462
}
7563

76-
function identifyShellFromTerminalName(terminal: Terminal): TerminalShellType {
64+
function identifyShellFromTerminalName(terminal: Terminal): string {
7765
if (terminal.name === 'sh') {
7866
// Specifically checking this because other shells have `sh` at the end of their name
7967
// We can match and return bash for this case
80-
return TerminalShellType.bash;
68+
return 'bash';
8169
}
8270
return identifyShellFromShellPath(terminal.name);
8371
}
8472

85-
function identifyPlatformDefaultShell(): TerminalShellType {
73+
function identifyPlatformDefaultShell(): string {
8674
if (isWindows()) {
8775
return identifyShellFromShellPath(getTerminalDefaultShellWindows());
8876
}
@@ -99,16 +87,16 @@ function getTerminalDefaultShellWindows(): string {
9987
return isAtLeastWindows10 ? powerShellPath : process.env.comspec || 'cmd.exe';
10088
}
10189

102-
function identifyShellFromVSC(terminal: Terminal): TerminalShellType {
90+
function identifyShellFromVSC(terminal: Terminal): string {
10391
const shellPath =
10492
terminal?.creationOptions && 'shellPath' in terminal.creationOptions && terminal.creationOptions.shellPath
10593
? terminal.creationOptions.shellPath
10694
: vscodeShell();
10795

108-
return shellPath ? identifyShellFromShellPath(shellPath) : TerminalShellType.unknown;
96+
return shellPath ? identifyShellFromShellPath(shellPath) : 'unknown';
10997
}
11098

111-
function identifyShellFromSettings(): TerminalShellType {
99+
function identifyShellFromSettings(): string {
112100
const shellConfig = getConfiguration('terminal.integrated.shell');
113101
let shellPath: string | undefined;
114102
switch (process.platform) {
@@ -130,56 +118,52 @@ function identifyShellFromSettings(): TerminalShellType {
130118
shellPath = undefined;
131119
}
132120
}
133-
return shellPath ? identifyShellFromShellPath(shellPath) : TerminalShellType.unknown;
121+
return shellPath ? identifyShellFromShellPath(shellPath) : 'unknown';
134122
}
135123

136-
function fromShellTypeApi(terminal: Terminal): TerminalShellType {
124+
function fromShellTypeApi(terminal: Terminal): string {
137125
try {
138-
switch (terminal.state.shellType) {
139-
case TerminalShellTypeVscode.Sh:
140-
case TerminalShellTypeVscode.Bash:
141-
return TerminalShellType.bash;
142-
case TerminalShellTypeVscode.Fish:
143-
return TerminalShellType.fish;
144-
case TerminalShellTypeVscode.Csh:
145-
return TerminalShellType.cshell;
146-
case TerminalShellTypeVscode.Ksh:
147-
return TerminalShellType.ksh;
148-
case TerminalShellTypeVscode.Zsh:
149-
return TerminalShellType.zsh;
150-
case TerminalShellTypeVscode.CommandPrompt:
151-
return TerminalShellType.commandPrompt;
152-
case TerminalShellTypeVscode.GitBash:
153-
return TerminalShellType.gitbash;
154-
case TerminalShellTypeVscode.PowerShell:
155-
return TerminalShellType.powershellCore;
156-
case TerminalShellTypeVscode.NuShell:
157-
return TerminalShellType.nushell;
158-
default:
159-
return TerminalShellType.unknown;
126+
const known = [
127+
'bash',
128+
'cmd',
129+
'csh',
130+
'fish',
131+
'gitbash',
132+
'julia',
133+
'ksh',
134+
'node',
135+
'nu',
136+
'pwsh',
137+
'python',
138+
'sh',
139+
'wsl',
140+
'zsh',
141+
];
142+
if (terminal.state.shell && known.includes(terminal.state.shell)) {
143+
return terminal.state.shell;
160144
}
161145
} catch {
162146
// If the API is not available, return unknown
163-
return TerminalShellType.unknown;
164147
}
148+
return 'unknown';
165149
}
166150

167-
export function identifyTerminalShell(terminal: Terminal): TerminalShellType {
151+
export function identifyTerminalShell(terminal: Terminal): string {
168152
let shellType = fromShellTypeApi(terminal);
169153

170-
if (shellType === TerminalShellType.unknown) {
154+
if (shellType === 'unknown') {
171155
shellType = identifyShellFromVSC(terminal);
172156
}
173157

174-
if (shellType === TerminalShellType.unknown) {
158+
if (shellType === 'unknown') {
175159
shellType = identifyShellFromTerminalName(terminal);
176160
}
177161

178-
if (shellType === TerminalShellType.unknown) {
162+
if (shellType === 'unknown') {
179163
shellType = identifyShellFromSettings();
180164
}
181165

182-
if (shellType === TerminalShellType.unknown) {
166+
if (shellType === 'unknown') {
183167
shellType = identifyPlatformDefaultShell();
184168
}
185169

0 commit comments

Comments
 (0)