diff --git a/package.nls.json b/package.nls.json index 37a9ce435f2f..57f2ed95b2c0 100644 --- a/package.nls.json +++ b/package.nls.json @@ -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.", diff --git a/src/client/extensionActivation.ts b/src/client/extensionActivation.ts index 8fae9d5131ff..5fe08eee4d12 100644 --- a/src/client/extensionActivation.ts +++ b/src/client/extensionActivation.ts @@ -184,7 +184,7 @@ async function activateLegacy(ext: ExtensionState, startupStopWatch: StopWatch): serviceManager.get(ITerminalAutoActivation).register(); await registerPythonStartup(ext.context); - await registerBasicRepl(ext.context); + await registerBasicRepl(ext.context, serviceContainer); serviceManager.get(ICodeExecutionManager).registerCommands(); diff --git a/src/client/terminals/pythonStartup.ts b/src/client/terminals/pythonStartup.ts index 1a2576dce772..d6dcb7f85918 100644 --- a/src/client/terminals/pythonStartup.ts +++ b/src/client/terminals/pythonStartup.ts @@ -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 { const config = getConfiguration('python'); @@ -37,7 +40,40 @@ export async function registerPythonStartup(context: ExtensionContext): Promise< ); } -export async function registerBasicRepl(context: ExtensionContext): Promise { - // TODO: Configurable by setting - context.environmentVariableCollection.replace('PYTHON_BASIC_REPL', '1'); +async function applyBasicReplSetting(context: ExtensionContext, serviceContainer?: IServiceContainer): Promise { + const config = getConfiguration('python'); + const shellIntegrationEnabled = config.get('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); + 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 { + await applyBasicReplSetting(context, serviceContainer); + context.subscriptions.push( + onDidChangeConfiguration(async (e) => { + if (e.affectsConfiguration('python.terminal.shellIntegration.enabled')) { + await applyBasicReplSetting(context, serviceContainer); + } + }), + ); } diff --git a/src/test/terminals/shellIntegration/pythonStartup.test.ts b/src/test/terminals/shellIntegration/pythonStartup.test.ts index 3c755adf0d9b..f92f5e7e48c3 100644 --- a/src/test/terminals/shellIntegration/pythonStartup.test.ts +++ b/src/test/terminals/shellIntegration/pythonStartup.test.ts @@ -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(); + const interpreterService = TypeMoq.Mock.ofType(); + 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(); + const interpreterService = TypeMoq.Mock.ofType(); + 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,