Skip to content

Make PyREPL disabling conditional on Shell integration setting for Python 3.13+ #25281

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

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
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
2 changes: 1 addition & 1 deletion package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
"python.tensorBoard.logDirectory.description": "Set this setting to your preferred TensorBoard log directory to skip log directory prompt when starting TensorBoard.",
"python.tensorBoard.logDirectory.markdownDeprecationMessage": "Tensorboard support has been moved to the extension [Tensorboard extension](https://marketplace.visualstudio.com/items?itemName=ms-toolsai.tensorboard). Instead use the setting `tensorBoard.logDirectory`.",
"python.tensorBoard.logDirectory.deprecationMessage": "Tensorboard support has been moved to the extension Tensorboard extension. Instead use the setting `tensorBoard.logDirectory`.",
"python.terminal.shellIntegration.enabled.description": "Enable [shell integration](https://code.visualstudio.com/docs/terminal/shell-integration) for the terminals running python. Shell integration enhances the terminal experience by enabling command decorations, run recent command, improving accessibility among other things.",
"python.terminal.shellIntegration.enabled.description": "Enable [shell integration](https://code.visualstudio.com/docs/terminal/shell-integration) for the terminals running python. Shell integration enhances the terminal experience by enabling command decorations, run recent command, improving accessibility among other things. Note: PyREPL (available in Python 3.13+) is automatically disabled when shell integration is enabled to avoid cursor indentation issues.",
"python.terminal.activateEnvInCurrentTerminal.description": "Activate Python Environment in the current Terminal on load of the Extension.",
"python.terminal.activateEnvironment.description": "Activate Python Environment in all Terminals created.",
"python.terminal.executeInFileDir.description": "When executing a file in the terminal, whether to use execute in the file's directory, instead of the current open folder.",
Expand Down
2 changes: 1 addition & 1 deletion src/client/extensionActivation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ async function activateLegacy(ext: ExtensionState, startupStopWatch: StopWatch):
serviceManager.get<ITerminalAutoActivation>(ITerminalAutoActivation).register();

await registerPythonStartup(ext.context);
await registerBasicRepl(ext.context);
await registerBasicRepl(ext.context, serviceContainer);

serviceManager.get<ICodeExecutionManager>(ICodeExecutionManager).registerCommands();

Expand Down
42 changes: 39 additions & 3 deletions src/client/terminals/pythonStartup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import { ExtensionContext, Uri } from 'vscode';
import * as path from 'path';
import { copy, createDirectory, getConfiguration, onDidChangeConfiguration } from '../common/vscodeApis/workspaceApis';
import { EXTENSION_ROOT_DIR } from '../constants';
import { IInterpreterService } from '../interpreter/contracts';
import { IServiceContainer } from '../ioc/types';
import { getPythonMinorVersion } from '../repl/replUtils';

async function applyPythonStartupSetting(context: ExtensionContext): Promise<void> {
const config = getConfiguration('python');
Expand Down Expand Up @@ -37,7 +40,40 @@ export async function registerPythonStartup(context: ExtensionContext): Promise<
);
}

export async function registerBasicRepl(context: ExtensionContext): Promise<void> {
// TODO: Configurable by setting
context.environmentVariableCollection.replace('PYTHON_BASIC_REPL', '1');
async function applyBasicReplSetting(context: ExtensionContext, serviceContainer?: IServiceContainer): Promise<void> {
const config = getConfiguration('python');
const shellIntegrationEnabled = config.get<boolean>('terminal.shellIntegration.enabled');

if (shellIntegrationEnabled && serviceContainer) {
// Only disable PyREPL (set PYTHON_BASIC_REPL=1) when shell integration is enabled
// and Python version is 3.13 or higher
try {
const interpreterService = serviceContainer.get<IInterpreterService>(IInterpreterService);
const pythonMinorVersion = await getPythonMinorVersion(undefined, interpreterService);

if ((pythonMinorVersion ?? 0) >= 13) {
context.environmentVariableCollection.replace('PYTHON_BASIC_REPL', '1');
return;
}
} catch {
// If we can't get the Python version, don't set PYTHON_BASIC_REPL
}
}

// Remove PYTHON_BASIC_REPL if shell integration is disabled or Python < 3.13
context.environmentVariableCollection.delete('PYTHON_BASIC_REPL');
}

export async function registerBasicRepl(
context: ExtensionContext,
serviceContainer?: IServiceContainer,
): Promise<void> {
await applyBasicReplSetting(context, serviceContainer);
context.subscriptions.push(
onDidChangeConfiguration(async (e) => {
if (e.affectsConfiguration('python.terminal.shellIntegration.enabled')) {
await applyBasicReplSetting(context, serviceContainer);
}
}),
);
}
51 changes: 49 additions & 2 deletions src/test/terminals/shellIntegration/pythonStartup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,14 +135,61 @@ suite('Terminal - Shell Integration with PYTHONSTARTUP', () => {
globalEnvironmentVariableCollection.verify((c) => c.delete('PYTHONSTARTUP'), TypeMoq.Times.once());
});

test('PYTHON_BASIC_REPL is set when registerBasicRepl is called', async () => {
await registerBasicRepl(context.object);
test('PYTHON_BASIC_REPL is set when shell integration is enabled and Python >= 3.13', async () => {
pythonConfig.setup((p) => p.get('terminal.shellIntegration.enabled')).returns(() => true);

// Mock service container with interpreter service
const serviceContainer = TypeMoq.Mock.ofType<any>();
const interpreterService = TypeMoq.Mock.ofType<any>();
const mockInterpreter = { version: { minor: 13 } };

serviceContainer.setup((sc) => sc.get(TypeMoq.It.isAny())).returns(() => interpreterService.object);
interpreterService
.setup((is) => is.getActiveInterpreter(TypeMoq.It.isAny()))
.returns(() => Promise.resolve(mockInterpreter));

await registerBasicRepl(context.object, serviceContainer.object);

globalEnvironmentVariableCollection.verify(
(c) => c.replace('PYTHON_BASIC_REPL', '1', TypeMoq.It.isAny()),
TypeMoq.Times.once(),
);
});

test('PYTHON_BASIC_REPL is not set when shell integration is disabled', async () => {
pythonConfig.setup((p) => p.get('terminal.shellIntegration.enabled')).returns(() => false);

await registerBasicRepl(context.object);

globalEnvironmentVariableCollection.verify(
(c) => c.replace('PYTHON_BASIC_REPL', '1', TypeMoq.It.isAny()),
TypeMoq.Times.never(),
);
globalEnvironmentVariableCollection.verify((c) => c.delete('PYTHON_BASIC_REPL'), TypeMoq.Times.once());
});

test('PYTHON_BASIC_REPL is not set when Python < 3.13 even with shell integration enabled', async () => {
pythonConfig.setup((p) => p.get('terminal.shellIntegration.enabled')).returns(() => true);

// Mock service container with interpreter service for Python 3.12
const serviceContainer = TypeMoq.Mock.ofType<any>();
const interpreterService = TypeMoq.Mock.ofType<any>();
const mockInterpreter = { version: { minor: 12 } };

serviceContainer.setup((sc) => sc.get(TypeMoq.It.isAny())).returns(() => interpreterService.object);
interpreterService
.setup((is) => is.getActiveInterpreter(TypeMoq.It.isAny()))
.returns(() => Promise.resolve(mockInterpreter));

await registerBasicRepl(context.object, serviceContainer.object);

globalEnvironmentVariableCollection.verify(
(c) => c.replace('PYTHON_BASIC_REPL', '1', TypeMoq.It.isAny()),
TypeMoq.Times.never(),
);
globalEnvironmentVariableCollection.verify((c) => c.delete('PYTHON_BASIC_REPL'), TypeMoq.Times.once());
});

test('Ensure registering terminal link calls registerTerminalLinkProvider', async () => {
const registerTerminalLinkProviderStub = sinon.stub(
pythonStartupLinkProvider,
Expand Down
Loading