Skip to content
Open
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
18 changes: 13 additions & 5 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
} from './common/window.apis';
import { getConfiguration } from './common/workspace.apis';
import { createManagerReady } from './features/common/managerReady';
import { identifyTerminalShell } from './features/common/shellDetector';
import { AutoFindProjects } from './features/creators/autoFindProjects';
import { ExistingProjects } from './features/creators/existingProjects';
import { NewPackageProject } from './features/creators/newPackageProject';
Expand Down Expand Up @@ -49,7 +50,7 @@ import { PythonProjectManagerImpl } from './features/projectManager';
import { getPythonApi, setPythonApi } from './features/pythonApi';
import { registerCompletionProvider } from './features/settings/settingCompletions';
import { setActivateMenuButtonContext } from './features/terminal/activateMenuButton';
import { normalizeShellPath } from './features/terminal/shells/common/shellUtils';
import { getShellCommandAsString, normalizeShellPath } from './features/terminal/shells/common/shellUtils';
import {
clearShellProfileCache,
createShellEnvProviders,
Expand Down Expand Up @@ -435,6 +436,7 @@ export async function activate(context: ExtensionContext): Promise<PythonEnviron
commands.registerCommand('python-envs.runPetInTerminal', async () => {
try {
const petPath = await getNativePythonToolsPath();
let command: { subcommand: string; args?: string[] } | undefined;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR.
Looks like command will never be undefined here, we could probably remove the | undefined

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

undefined is required here since the if ... else tests on selectedOption.label below don't have a default else, therefore the ts compiler will complain with ts(2454).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I'm thinking can we just simplify the second else if (...Resolve Environment) into else?
There is only two options that selectionOption.label can take.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's true but I didn't want to introduce any unrelated changes with possibly undesirable effects, keeping the PR context relevant and minimal. Should I still change it?


// Show quick pick menu for PET operation selection
const selectedOption = await window.showQuickPick(
Expand Down Expand Up @@ -467,8 +469,7 @@ export async function activate(context: ExtensionContext): Promise<PythonEnviron

if (selectedOption.label === 'Find All Environments') {
// Run pet find --verbose
terminal.sendText(`"${petPath}" find --verbose`, true);
traceInfo(`Running PET find command: ${petPath} find --verbose`);
command = { subcommand: 'find', args: ['--verbose'] };
} else if (selectedOption.label === 'Resolve Environment...') {
// Show input box for path
const placeholder = isWindows() ? 'C:\\path\\to\\python\\executable' : '/path/to/python/executable';
Expand All @@ -489,8 +490,15 @@ export async function activate(context: ExtensionContext): Promise<PythonEnviron
}

// Run pet resolve with the provided path
terminal.sendText(`"${petPath}" resolve "${inputPath.trim()}"`, true);
traceInfo(`Running PET resolve command: ${petPath} resolve "${inputPath.trim()}"`);
command = { subcommand: 'resolve', args: [inputPath.trim()] };
}

if (command) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (command) { could also go away if we remove undefined from type above.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See reply to previous comment.

const execString = getShellCommandAsString(identifyTerminalShell(terminal), [
{ executable: petPath, args: [command.subcommand, ...(command.args || [])] },
]);
terminal.sendText(execString);
traceInfo(`Running PET ${command.subcommand} command: ${execString}`);
}
} catch (error) {
traceError('Error running PET in terminal', error);
Expand Down
1 change: 1 addition & 0 deletions src/features/execution/execUtils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export function quoteStringIfNecessary(arg: string): string {
arg = arg.trim();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if we really need this. Seems like the only places we call this are:

let executable = environment.execInfo?.activatedRun?.executable ?? environment.execInfo?.run.executable;
if (!executable) {
traceWarn('No Python executable found in environment; falling back to "python".');
executable = 'python';
}
// Check and quote the executable path if necessary
executable = quoteStringIfNecessary(executable);

and
let executable = environment.execInfo?.activatedRun?.executable ?? environment.execInfo?.run.executable;
if (!executable) {
traceWarn('No Python executable found in environment; falling back to "python".');
executable = 'python';
}
// Check and quote the executable path if necessary
executable = quoteStringIfNecessary(executable);

And the executable string that is passed in seems like its path to Python binary.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here:

terminal.sendText(`"${petPath}" resolve "${inputPath.trim()}"`, true);

inputPath is user-provided and could have leading white spaces. I did keep the .trim() in this case before eventually processing the string with getShellCommandAsString but I thought also of adding it in the quoting logic for consistency and robustness in future use cases. What do you think?

if (arg.indexOf(' ') >= 0 && !(arg.startsWith('"') && arg.endsWith('"'))) {
return `"${arg}"`;
}
Expand Down
21 changes: 3 additions & 18 deletions src/features/terminal/runInTerminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ import { Terminal, TerminalShellExecution } from 'vscode';
import { PythonEnvironment, PythonTerminalExecutionOptions } from '../../api';
import { createDeferred } from '../../common/utils/deferred';
import { onDidEndTerminalShellExecution } from '../../common/window.apis';
import { ShellConstants } from '../common/shellConstants';
import { identifyTerminalShell } from '../common/shellDetector';
import { quoteArgs } from '../execution/execUtils';
import { getShellCommandAsString } from './shells/common/shellUtils';

export async function runInTerminal(
environment: PythonEnvironment,
Expand All @@ -29,25 +28,11 @@ export async function runInTerminal(
}
});

const shouldSurroundWithQuotes =
executable.includes(' ') && !executable.startsWith('"') && !executable.endsWith('"');
// Handle case where executable contains white-spaces.
if (shouldSurroundWithQuotes) {
executable = `"${executable}"`;
}

if (shellType === ShellConstants.PWSH && !executable.startsWith('&')) {
// PowerShell requires commands to be prefixed with '&' to run them.
executable = `& ${executable}`;
}
executable = getShellCommandAsString(shellType, [{ executable }]);
execution = terminal.shellIntegration.executeCommand(executable, allArgs);
await deferred.promise;
} else {
let text = quoteArgs([executable, ...allArgs]).join(' ');
if (shellType === ShellConstants.PWSH && !text.startsWith('&')) {
// PowerShell requires commands to be prefixed with '&' to run them.
text = `& ${text}`;
}
const text = getShellCommandAsString(shellType, [{ executable, args: allArgs }]);
terminal.sendText(`${text}\n`);
}
}
3 changes: 2 additions & 1 deletion src/features/terminal/shells/common/shellUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ import { ShellConstants } from '../../../common/shellConstants';
import { quoteArgs } from '../../../execution/execUtils';

function getCommandAsString(command: PythonCommandRunConfiguration[], shell: string, delimiter: string): string {
const parts = [];
let parts = [];
for (const cmd of command) {
const args = cmd.args ?? [];
parts.push(quoteArgs([normalizeShellPath(cmd.executable, shell), ...args]).join(' '));
}
if (shell === ShellConstants.PWSH) {
parts = parts.map((p) => (p.startsWith('"') ? `& ${p}` : p));
if (parts.length === 1) {
return parts[0];
}
Expand Down