Skip to content

Commit b7e63c5

Browse files
committed
chore: more refactoring
1 parent 837904c commit b7e63c5

File tree

18 files changed

+205
-182
lines changed

18 files changed

+205
-182
lines changed

.vscode/launch.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
3131
"skipFiles": ["<node_internals>/**"],
3232
"outFiles": ["${workspaceFolder}/out/**/*.js", "!${workspaceFolder}/**/node_modules**/*"],
33-
"preLaunchTask": "${defaultBuildTask}"
33+
"preLaunchTask": "npm: watch-tests"
3434
},
3535
{
3636
"name": "Extension Tests",

.vscode/tasks.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111
"presentation": {
1212
"reveal": "never",
1313
"group": "watchers"
14+
},
15+
"group": {
16+
"kind": "build",
17+
"isDefault": true
1418
}
1519
},
1620
{
@@ -30,10 +34,6 @@
3034
"presentation": {
3135
"reveal": "never",
3236
"group": "watchers"
33-
},
34-
"group": {
35-
"kind": "build",
36-
"isDefault": true
3737
}
3838
},
3939
{

src/features/common/activation.ts

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Terminal } from 'vscode';
22
import { PythonCommandRunConfiguration, PythonEnvironment } from '../../api';
33
import { identifyTerminalShell } from './shellDetector';
4+
import { getShellActivationCommand } from '../terminal/shells/common/shellUtils';
45

56
export function isActivatableEnvironment(environment: PythonEnvironment): boolean {
67
return !!environment.execInfo?.activation || !!environment.execInfo?.shellActivation;
@@ -15,26 +16,7 @@ export function getActivationCommand(
1516
environment: PythonEnvironment,
1617
): PythonCommandRunConfiguration[] | undefined {
1718
const shell = identifyTerminalShell(terminal);
18-
return getActivationCommandForShell(environment, shell);
19-
}
20-
21-
export function getActivationCommandForShell(
22-
environment: PythonEnvironment,
23-
shell: string,
24-
): PythonCommandRunConfiguration[] | undefined {
25-
let activation: PythonCommandRunConfiguration[] | undefined;
26-
if (environment.execInfo?.shellActivation) {
27-
activation = environment.execInfo.shellActivation.get(shell);
28-
if (!activation) {
29-
activation = environment.execInfo.shellActivation.get('unknown');
30-
}
31-
}
32-
33-
if (!activation) {
34-
activation = environment.execInfo?.activation;
35-
}
36-
37-
return activation;
19+
return getShellActivationCommand(shell, environment);
3820
}
3921

4022
export function getDeactivationCommand(

src/features/common/shellDetector.ts

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Terminal } from 'vscode';
33
import { vscodeShell } from '../../common/vscodeEnv.apis';
44
import { getConfiguration } from '../../common/workspace.apis';
55
import { isWindows } from '../../common/utils/platformUtils';
6+
import { ShellConstants } from './shellConstants';
67

78
/*
89
When identifying the shell use the following algorithm:
@@ -29,18 +30,18 @@ const IS_NUSHELL = /(nu$)/i;
2930
const IS_XONSH = /(xonsh$)/i;
3031

3132
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],
33+
[ShellConstants.PWSH, IS_POWERSHELL],
34+
[ShellConstants.GITBASH, IS_GITBASH],
35+
[ShellConstants.BASH, IS_BASH],
36+
[ShellConstants.WSL, IS_WSL],
37+
[ShellConstants.ZSH, IS_ZSH],
38+
[ShellConstants.KSH, IS_KSH],
39+
[ShellConstants.CMD, IS_COMMAND],
40+
[ShellConstants.FISH, IS_FISH],
41+
[ShellConstants.TCSH, IS_TCSHELL],
42+
[ShellConstants.CSH, IS_CSHELL],
43+
[ShellConstants.NU, IS_NUSHELL],
44+
[ShellConstants.XONSH, IS_XONSH],
4445
]);
4546

4647
function identifyShellFromShellPath(shellPath: string): string {
@@ -62,10 +63,10 @@ function identifyShellFromShellPath(shellPath: string): string {
6263
}
6364

6465
function identifyShellFromTerminalName(terminal: Terminal): string {
65-
if (terminal.name === 'sh') {
66+
if (terminal.name === ShellConstants.SH) {
6667
// Specifically checking this because other shells have `sh` at the end of their name
6768
// We can match and return bash for this case
68-
return 'bash';
69+
return ShellConstants.BASH;
6970
}
7071
return identifyShellFromShellPath(terminal.name);
7172
}
@@ -124,20 +125,20 @@ function identifyShellFromSettings(): string {
124125
function fromShellTypeApi(terminal: Terminal): string {
125126
try {
126127
const known = [
127-
'bash',
128-
'cmd',
129-
'csh',
130-
'fish',
131-
'gitbash',
128+
ShellConstants.BASH,
129+
ShellConstants.CMD,
130+
ShellConstants.CSH,
131+
ShellConstants.FISH,
132+
ShellConstants.GITBASH,
132133
'julia',
133-
'ksh',
134+
ShellConstants.KSH,
134135
'node',
135-
'nu',
136-
'pwsh',
136+
ShellConstants.NU,
137+
ShellConstants.PWSH,
137138
'python',
138-
'sh',
139+
ShellConstants.SH,
139140
'wsl',
140-
'zsh',
141+
ShellConstants.ZSH,
141142
];
142143
if (terminal.state.shell && known.includes(terminal.state.shell.toLowerCase())) {
143144
return terminal.state.shell.toLowerCase();

src/features/terminal/shells/bash/bashEnvs.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11
import { EnvironmentVariableCollection } from 'vscode';
22
import { PythonEnvironment } from '../../../../api';
33
import { traceError } from '../../../../common/logging';
4-
import { getActivationCommandForShell } from '../../../common/activation';
54
import { ShellConstants } from '../../../common/shellConstants';
65
import { ShellEnvsProvider } from '../startupProvider';
7-
import { getCommandAsString } from '../utils';
86
import { BASH_ENV_KEY, ZSH_ENV_KEY } from './bashConstants';
7+
import { getShellActivationCommand, getShellCommandAsString } from '../common/shellUtils';
98

109
export class BashEnvsProvider implements ShellEnvsProvider {
1110
constructor(public readonly shellType: 'bash' | 'gitbash') {}
1211

1312
async updateEnvVariables(collection: EnvironmentVariableCollection, env: PythonEnvironment): Promise<void> {
1413
try {
15-
const bashActivation = getActivationCommandForShell(env, this.shellType);
14+
const bashActivation = getShellActivationCommand(this.shellType, env);
1615
if (bashActivation) {
17-
const command = getCommandAsString(bashActivation, '&&');
16+
const command = getShellCommandAsString(this.shellType, bashActivation);
1817
collection.replace(BASH_ENV_KEY, command);
1918
} else {
2019
collection.delete(BASH_ENV_KEY);
@@ -35,9 +34,9 @@ export class BashEnvsProvider implements ShellEnvsProvider {
3534
}
3635

3736
try {
38-
const bashActivation = getActivationCommandForShell(env, ShellConstants.BASH);
37+
const bashActivation = getShellActivationCommand(this.shellType, env);
3938
if (bashActivation) {
40-
const command = getCommandAsString(bashActivation, '&&');
39+
const command = getShellCommandAsString(this.shellType, bashActivation);
4140
return new Map([[BASH_ENV_KEY, command]]);
4241
}
4342
return undefined;
@@ -52,9 +51,9 @@ export class ZshEnvsProvider implements ShellEnvsProvider {
5251
public readonly shellType: string = ShellConstants.ZSH;
5352
async updateEnvVariables(envVars: EnvironmentVariableCollection, env: PythonEnvironment): Promise<void> {
5453
try {
55-
const zshActivation = getActivationCommandForShell(env, ShellConstants.ZSH);
54+
const zshActivation = getShellActivationCommand(this.shellType, env);
5655
if (zshActivation) {
57-
const command = getCommandAsString(zshActivation, '&&');
56+
const command = getShellCommandAsString(this.shellType, zshActivation);
5857
envVars.replace(ZSH_ENV_KEY, command);
5958
} else {
6059
envVars.delete(ZSH_ENV_KEY);
@@ -75,8 +74,12 @@ export class ZshEnvsProvider implements ShellEnvsProvider {
7574
}
7675

7776
try {
78-
const zshActivation = getActivationCommandForShell(env, ShellConstants.ZSH);
79-
return zshActivation ? new Map([[ZSH_ENV_KEY, getCommandAsString(zshActivation, '&&')]]) : undefined;
77+
const zshActivation = getShellActivationCommand(this.shellType, env);
78+
if (zshActivation) {
79+
const command = getShellCommandAsString(this.shellType, zshActivation);
80+
return new Map([[ZSH_ENV_KEY, command]]);
81+
}
82+
return undefined;
8083
} catch (err) {
8184
traceError('Failed to get env variables for zsh', err);
8285
return undefined;

src/features/terminal/shells/bash/bashStartup.ts

Lines changed: 19 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,19 @@ import { ShellScriptEditState, ShellSetupState, ShellStartupScriptProvider } fro
55
import { traceError, traceInfo, traceVerbose } from '../../../../common/logging';
66
import which from 'which';
77
import { BASH_ENV_KEY, ZSH_ENV_KEY } from './bashConstants';
8+
import { ShellConstants } from '../../../common/shellConstants';
9+
import { hasStartupCode, insertStartupCode, removeStartupCode } from '../common/editUtils';
810

911
async function isBashLikeInstalled(): Promise<boolean> {
10-
const result = await Promise.all([which('bash', { nothrow: true }), which('sh', { nothrow: true })]);
12+
const result = await Promise.all([
13+
which(ShellConstants.BASH, { nothrow: true }),
14+
which(ShellConstants.SH, { nothrow: true }),
15+
]);
1116
return result.some((r) => r !== null);
1217
}
1318

1419
async function isZshInstalled(): Promise<boolean> {
15-
const result = await which('zsh', { nothrow: true });
20+
const result = await which(ShellConstants.ZSH, { nothrow: true });
1621
return result !== null;
1722
}
1823

@@ -44,23 +49,15 @@ const regionEnd = '# <<< vscode python';
4449

4550
function getActivationContent(key: string): string {
4651
const lineSep = '\n';
47-
48-
return [
49-
'',
50-
'',
51-
regionStart,
52-
`if [ -n "$${key}" ] && [ "$TERM_PROGRAM" = "vscode" ]; then`,
53-
` . "$${key}"`,
54-
'fi',
55-
regionEnd,
56-
'',
57-
].join(lineSep);
52+
return [`if [ -n "$${key}" ] && [ "$TERM_PROGRAM" = "vscode" ]; then`, ` . "$${key}"`, 'fi'].join(lineSep);
5853
}
5954

6055
async function isStartupSetup(profile: string, key: string): Promise<ShellSetupState> {
6156
if (await fs.pathExists(profile)) {
6257
const content = await fs.readFile(profile, 'utf8');
63-
return content.includes(key) ? ShellSetupState.Setup : ShellSetupState.NotSetup;
58+
return hasStartupCode(content, regionStart, regionEnd, [key])
59+
? ShellSetupState.Setup
60+
: ShellSetupState.NotSetup;
6461
} else {
6562
return ShellSetupState.NotSetup;
6663
}
@@ -70,23 +67,18 @@ async function setupStartup(profile: string, key: string, name: string): Promise
7067
const activationContent = getActivationContent(key);
7168

7269
try {
73-
// Create profile directory if it doesn't exist
7470
await fs.mkdirp(path.dirname(profile));
7571

76-
// Create or update profile
7772
if (!(await fs.pathExists(profile))) {
78-
// Create new profile with our content
7973
await fs.writeFile(profile, activationContent);
8074
traceInfo(`SHELL: Created new ${name} profile at: ${profile}\n${activationContent}`);
8175
} else {
82-
// Update existing profile
8376
const content = await fs.readFile(profile, 'utf8');
84-
if (!content.includes(key)) {
85-
await fs.writeFile(profile, `${content}${activationContent}`);
86-
traceInfo(`SHELL: Updated existing ${name} profile at: ${profile}\n${activationContent}`);
87-
} else {
88-
// Already contains our activation code
77+
if (hasStartupCode(content, regionStart, regionEnd, [key])) {
8978
traceInfo(`SHELL: ${name} profile already contains activation code at: ${profile}`);
79+
} else {
80+
await fs.appendFile(profile, insertStartupCode(content, regionStart, regionEnd, activationContent));
81+
traceInfo(`SHELL: Updated existing ${name} profile at: ${profile}\n${activationContent}`);
9082
}
9183
}
9284
return true;
@@ -99,14 +91,12 @@ async function setupStartup(profile: string, key: string, name: string): Promise
9991
async function removeStartup(profile: string, key: string): Promise<boolean> {
10092
if (!(await fs.pathExists(profile))) {
10193
return true;
102-
} // If the file doesn't exist, we're done. No need to remove anything. Return true to indicate success.
94+
}
95+
10396
try {
10497
const content = await fs.readFile(profile, 'utf8');
105-
if (content.includes(key)) {
106-
// Use regex to remove the entire region including newlines
107-
const pattern = new RegExp(`${regionStart}[\\s\\S]*?${regionEnd}\\n?`, 'g');
108-
const newContent = content.replace(pattern, '');
109-
await fs.writeFile(profile, newContent);
98+
if (hasStartupCode(content, regionStart, regionEnd, [key])) {
99+
await fs.writeFile(profile, removeStartupCode(content, regionStart, regionEnd));
110100
traceInfo(`SHELL: Removed activation from profile at: ${profile}`);
111101
} else {
112102
traceVerbose(`Profile at ${profile} does not contain activation code`);

src/features/terminal/shells/cmdStartup.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { isWindows } from '../../../common/utils/platformUtils';
55
import { ShellScriptEditState, ShellSetupState, ShellStartupScriptProvider } from './startupProvider';
66
import { EnvironmentVariableCollection } from 'vscode';
77
import { PythonEnvironment } from '../../../api';
8-
import { getActivationCommandForShell } from '../../common/activation';
8+
import { getShellActivationCommand } from '../../common/activation';
99
import { traceError, traceInfo, traceVerbose } from '../../../common/logging';
1010
import { getCommandAsString } from './utils';
1111
import which from 'which';
@@ -313,7 +313,7 @@ export class CmdStartupProvider implements ShellStartupScriptProvider {
313313

314314
async updateEnvVariables(collection: EnvironmentVariableCollection, env: PythonEnvironment): Promise<void> {
315315
try {
316-
const cmdActivation = getActivationCommandForShell(env, ShellConstants.CMD);
316+
const cmdActivation = getShellActivationCommand(env, ShellConstants.CMD);
317317
if (cmdActivation) {
318318
const command = getCommandAsString(cmdActivation, '&');
319319
collection.replace(this.cmdActivationEnvVarKey, command);
@@ -336,7 +336,7 @@ export class CmdStartupProvider implements ShellStartupScriptProvider {
336336
}
337337

338338
try {
339-
const cmdActivation = getActivationCommandForShell(env, ShellConstants.CMD);
339+
const cmdActivation = getShellActivationCommand(env, ShellConstants.CMD);
340340
return cmdActivation
341341
? new Map([[this.cmdActivationEnvVarKey, getCommandAsString(cmdActivation, '&')]])
342342
: undefined;
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { PythonCommandRunConfiguration, PythonEnvironment } from '../../../../api';
2+
import { isWindows } from '../../../../common/utils/platformUtils';
3+
import { ShellConstants } from '../../../common/shellConstants';
4+
import { quoteArgs } from '../../../execution/execUtils';
5+
6+
export function getCommandAsString(command: PythonCommandRunConfiguration[], delimiter: string): string {
7+
const parts = [];
8+
for (const cmd of command) {
9+
const args = cmd.args ?? [];
10+
parts.push(quoteArgs([cmd.executable, ...args]).join(' '));
11+
}
12+
return parts.join(` ${delimiter} `);
13+
}
14+
15+
export function getShellCommandAsString(shell: string, command: PythonCommandRunConfiguration[]): string {
16+
switch (shell) {
17+
case ShellConstants.NU:
18+
return getCommandAsString(command, ';');
19+
case ShellConstants.FISH:
20+
return getCommandAsString(command, '; and');
21+
case ShellConstants.BASH:
22+
case ShellConstants.SH:
23+
case ShellConstants.ZSH:
24+
case ShellConstants.PWSH:
25+
case ShellConstants.CMD:
26+
default:
27+
return getCommandAsString(command, '&&');
28+
}
29+
}
30+
31+
export function normalizeShellPath(filePath: string, shellType?: string): string {
32+
if (isWindows() && shellType) {
33+
if (shellType.toLowerCase() === ShellConstants.GITBASH || shellType.toLowerCase() === 'git-bash') {
34+
return filePath.replace(/\\/g, '/').replace(/^\/([a-zA-Z])/, '$1:');
35+
}
36+
}
37+
return filePath;
38+
}
39+
export function getShellActivationCommand(
40+
shell: string,
41+
environment: PythonEnvironment,
42+
): PythonCommandRunConfiguration[] | undefined {
43+
let activation: PythonCommandRunConfiguration[] | undefined;
44+
if (environment.execInfo?.shellActivation) {
45+
activation = environment.execInfo.shellActivation.get(shell);
46+
if (!activation) {
47+
activation = environment.execInfo.shellActivation.get('unknown');
48+
}
49+
}
50+
51+
if (!activation) {
52+
activation = environment.execInfo?.activation;
53+
}
54+
55+
return activation;
56+
}

0 commit comments

Comments
 (0)