From 1ce9f5eb054dfb98f76ed640fd27860c4308f831 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 14 Jul 2025 01:34:22 +0000 Subject: [PATCH 1/3] Initial plan From 4bb50dcc2ff59e27eb7cde80d79771626f388108 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 14 Jul 2025 01:44:53 +0000 Subject: [PATCH 2/3] Implement conditional PyREPL disabling based on shell integration and Python version Co-authored-by: anthonykim1 <62267334+anthonykim1@users.noreply.github.com> --- package.nls.json | 2 +- src/client/extensionActivation.ts | 2 +- src/client/terminals/pythonStartup.ts | 39 ++++++++++++-- .../shellIntegration/pythonStartup.test.ts | 51 ++++++++++++++++++- 4 files changed, 88 insertions(+), 6 deletions(-) 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..3d11073dcad9 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,37 @@ 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..923e5cdf1b5f 100644 --- a/src/test/terminals/shellIntegration/pythonStartup.test.ts +++ b/src/test/terminals/shellIntegration/pythonStartup.test.ts @@ -135,10 +135,59 @@ 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 () => { + 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(), ); }); From d0e825924112ba7c2a242fb8bcbb06330df45055 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 14 Jul 2025 01:47:27 +0000 Subject: [PATCH 3/3] Apply formatting fixes and finalize PyREPL conditional logic implementation Co-authored-by: anthonykim1 <62267334+anthonykim1@users.noreply.github.com> --- src/client/terminals/pythonStartup.ts | 11 ++++-- .../shellIntegration/pythonStartup.test.ts | 38 +++++++++---------- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/src/client/terminals/pythonStartup.ts b/src/client/terminals/pythonStartup.ts index 3d11073dcad9..d6dcb7f85918 100644 --- a/src/client/terminals/pythonStartup.ts +++ b/src/client/terminals/pythonStartup.ts @@ -43,14 +43,14 @@ export async function registerPythonStartup(context: ExtensionContext): Promise< 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; @@ -59,12 +59,15 @@ async function applyBasicReplSetting(context: ExtensionContext, serviceContainer // 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 { +export async function registerBasicRepl( + context: ExtensionContext, + serviceContainer?: IServiceContainer, +): Promise { await applyBasicReplSetting(context, serviceContainer); context.subscriptions.push( onDidChangeConfiguration(async (e) => { diff --git a/src/test/terminals/shellIntegration/pythonStartup.test.ts b/src/test/terminals/shellIntegration/pythonStartup.test.ts index 923e5cdf1b5f..f92f5e7e48c3 100644 --- a/src/test/terminals/shellIntegration/pythonStartup.test.ts +++ b/src/test/terminals/shellIntegration/pythonStartup.test.ts @@ -137,17 +137,19 @@ suite('Terminal - Shell Integration with PYTHONSTARTUP', () => { 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)); - + 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(), @@ -156,40 +158,36 @@ suite('Terminal - Shell Integration with PYTHONSTARTUP', () => { 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(), - ); + 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)); - + 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(), - ); + globalEnvironmentVariableCollection.verify((c) => c.delete('PYTHON_BASIC_REPL'), TypeMoq.Times.once()); }); test('Ensure registering terminal link calls registerTerminalLinkProvider', async () => {