diff --git a/src/client/terminals/codeExecution/terminalCodeExecution.ts b/src/client/terminals/codeExecution/terminalCodeExecution.ts index ea444af4d89e..e7ed3bf7f61d 100644 --- a/src/client/terminals/codeExecution/terminalCodeExecution.ts +++ b/src/client/terminals/codeExecution/terminalCodeExecution.ts @@ -38,12 +38,15 @@ export class TerminalCodeExecutionProvider implements ICodeExecutionService { ) {} public async executeFile(file: Uri, options?: { newTerminalPerFile: boolean }) { - await this.setCwdForFileExecution(file, options); + // If we're currently in a REPL, force creation of a new terminal for file execution + const finalOptions = await this.shouldForceNewTerminalForFileExecution(options); + + await this.setCwdForFileExecution(file, finalOptions); const { command, args } = await this.getExecuteFileArgs(file, [ file.fsPath.fileToCommandArgumentForPythonExt(), ]); - await this.getTerminalService(file, options).sendCommand(command, args); + await this.getTerminalService(file, finalOptions).sendCommand(command, args); } public async execute(code: string, resource?: Uri): Promise { @@ -124,6 +127,28 @@ export class TerminalCodeExecutionProvider implements ICodeExecutionService { public async getExecuteFileArgs(resource?: Uri, executeArgs: string[] = []): Promise { return this.getExecutableInfo(resource, executeArgs); } + + /** + * Check if we should force creation of a new terminal for file execution. + * This is needed when the current terminal is in Python REPL mode to avoid + * sending the file execution command to the Python interpreter instead of the shell. + */ + private async shouldForceNewTerminalForFileExecution( + options?: { newTerminalPerFile: boolean } + ): Promise<{ newTerminalPerFile: boolean }> { + // If already set to create new terminal, respect that + if (options?.newTerminalPerFile) { + return { newTerminalPerFile: true }; + } + + // If we have an active REPL, force creation of a new terminal + if (this.replActive && (await this.replActive)) { + return { newTerminalPerFile: true }; + } + + // Otherwise, use original options or default to false + return options || { newTerminalPerFile: false }; + } private getTerminalService(resource: Resource, options?: { newTerminalPerFile: boolean }): ITerminalService { return this.terminalServiceFactory.getTerminalService({ resource, diff --git a/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts b/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts index b5bcecd971ea..0eda12ce5b3f 100644 --- a/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts +++ b/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts @@ -649,6 +649,43 @@ suite('Terminal - Code Execution', () => { terminalService.verify(async (t) => t.executeCommand('cmd2', true), TypeMoq.Times.once()); }); + test('Ensure new terminal is created when REPL is active and executeFile is called', async () => { + // Setup a file to execute + const file = Uri.file(path.join('c:', 'path', 'to', 'file', 'test.py')); + + // Setup mocks for interpreter and settings + const pythonPath = 'usr/bin/python1234'; + const terminalArgs = ['-a', 'b', 'c']; + platform.setup((p) => p.isWindows).returns(() => false); + interpreterService + .setup((s) => s.getActiveInterpreter(TypeMoq.It.isAny())) + .returns(() => Promise.resolve(({ path: pythonPath } as unknown) as PythonEnvironment)); + terminalSettings.setup((t) => t.launchArgs).returns(() => terminalArgs); + terminalSettings.setup((t) => t.executeInFileDir).returns(() => false); + + // First, initialize REPL to make replActive = true + await executor.initializeRepl(file); + + // Reset terminal factory to track calls for file execution + terminalFactory.reset(); + terminalFactory + .setup((f) => f.getTerminalService(TypeMoq.It.isAny())) + .callback((options: TerminalCreationOptions & { newTerminalPerFile?: boolean }) => { + // When REPL is active, executeFile should force newTerminalPerFile = true + assert.equal(options.newTerminalPerFile, true, 'Should force new terminal when REPL is active'); + }) + .returns(() => terminalService.object); + + // Execute file - this should force creation of a new terminal + await executor.executeFile(file); + + // Verify that getTerminalService was called with newTerminalPerFile: true + terminalFactory.verify( + (f) => f.getTerminalService(TypeMoq.It.isAny()), + TypeMoq.Times.once() + ); + }); + test('Ensure code is sent to the same terminal for a particular resource', async () => { const resource = Uri.file('a'); terminalFactory.reset();