diff --git a/news/2 Fixes/9437.md b/news/2 Fixes/9437.md new file mode 100644 index 000000000000..0440859fa7d0 --- /dev/null +++ b/news/2 Fixes/9437.md @@ -0,0 +1 @@ +Shift+Enter can no longer send multiple lines to the interactive window. \ No newline at end of file diff --git a/news/2 Fixes/9439.md b/news/2 Fixes/9439.md new file mode 100644 index 000000000000..47c6305f5cb8 --- /dev/null +++ b/news/2 Fixes/9439.md @@ -0,0 +1 @@ +Shift+Enter can no longer run code in the terminal. \ No newline at end of file diff --git a/src/client/terminals/codeExecution/djangoShellCodeExecution.ts b/src/client/terminals/codeExecution/djangoShellCodeExecution.ts index e1b7d23d7bab..2ead5fe58e82 100644 --- a/src/client/terminals/codeExecution/djangoShellCodeExecution.ts +++ b/src/client/terminals/codeExecution/djangoShellCodeExecution.ts @@ -9,7 +9,7 @@ import { Disposable, Uri } from 'vscode'; import { ICommandManager, IDocumentManager, IWorkspaceService } from '../../common/application/types'; import '../../common/extensions'; import { IFileSystem, IPlatformService } from '../../common/platform/types'; -import { IPythonExecutionFactory, PythonExecutionInfo } from '../../common/process/types'; +import { PythonExecutionInfo } from '../../common/process/types'; import { ITerminalServiceFactory } from '../../common/terminal/types'; import { IConfigurationService, IDisposableRegistry } from '../../common/types'; import { DjangoContextInitializer } from './djangoContext'; @@ -25,10 +25,9 @@ export class DjangoShellCodeExecutionProvider extends TerminalCodeExecutionProvi @inject(IPlatformService) platformService: IPlatformService, @inject(ICommandManager) commandManager: ICommandManager, @inject(IFileSystem) fileSystem: IFileSystem, - @inject(IPythonExecutionFactory) pythonExecFactory: IPythonExecutionFactory, @inject(IDisposableRegistry) disposableRegistry: Disposable[] ) { - super(terminalServiceFactory, configurationService, workspace, disposableRegistry, platformService, pythonExecFactory); + super(terminalServiceFactory, configurationService, workspace, disposableRegistry, platformService); this.terminalTitle = 'Django Shell'; disposableRegistry.push(new DjangoContextInitializer(documentManager, workspace, fileSystem, commandManager)); } diff --git a/src/client/terminals/codeExecution/helper.ts b/src/client/terminals/codeExecution/helper.ts index ee707df767a3..c2172d490391 100644 --- a/src/client/terminals/codeExecution/helper.ts +++ b/src/client/terminals/codeExecution/helper.ts @@ -1,14 +1,16 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +import '../../common/extensions'; import { inject, injectable } from 'inversify'; import * as path from 'path'; import { Range, TextEditor, Uri } from 'vscode'; + import { IApplicationShell, IDocumentManager } from '../../common/application/types'; import { EXTENSION_ROOT_DIR, PYTHON_LANGUAGE } from '../../common/constants'; -import '../../common/extensions'; import { traceError } from '../../common/logger'; -import { IPythonExecutionFactory } from '../../common/process/types'; +import { IProcessServiceFactory } from '../../common/process/types'; +import { IInterpreterService } from '../../interpreter/contracts'; import { IServiceContainer } from '../../ioc/types'; import { ICodeExecutionHelper } from '../types'; @@ -16,11 +18,13 @@ import { ICodeExecutionHelper } from '../types'; export class CodeExecutionHelper implements ICodeExecutionHelper { private readonly documentManager: IDocumentManager; private readonly applicationShell: IApplicationShell; - private readonly pythonServiceFactory: IPythonExecutionFactory; + private readonly processServiceFactory: IProcessServiceFactory; + private readonly interpreterService: IInterpreterService; constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { this.documentManager = serviceContainer.get(IDocumentManager); this.applicationShell = serviceContainer.get(IApplicationShell); - this.pythonServiceFactory = serviceContainer.get(IPythonExecutionFactory); + this.processServiceFactory = serviceContainer.get(IProcessServiceFactory); + this.interpreterService = serviceContainer.get(IInterpreterService); } public async normalizeLines(code: string, resource?: Uri): Promise { try { @@ -30,9 +34,10 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { // On windows cr is not handled well by python when passing in/out via stdin/stdout. // So just remove cr from the input. code = code.replace(new RegExp('\\r', 'g'), ''); + const interpreter = await this.interpreterService.getActiveInterpreter(resource); + const processService = await this.processServiceFactory.create(resource); const args = [path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'normalizeForInterpreter.py'), code]; - const processService = await this.pythonServiceFactory.create({ resource }); - const proc = await processService.exec(args, { throwOnStdErr: true }); + const proc = await processService.exec(interpreter?.path || 'python', args, { throwOnStdErr: true }); return proc.stdout; } catch (ex) { diff --git a/src/client/terminals/codeExecution/repl.ts b/src/client/terminals/codeExecution/repl.ts index 816b3ea3df6b..f3c620e83b75 100644 --- a/src/client/terminals/codeExecution/repl.ts +++ b/src/client/terminals/codeExecution/repl.ts @@ -7,7 +7,6 @@ import { inject, injectable } from 'inversify'; import { Disposable } from 'vscode'; import { IWorkspaceService } from '../../common/application/types'; import { IPlatformService } from '../../common/platform/types'; -import { IPythonExecutionFactory } from '../../common/process/types'; import { ITerminalServiceFactory } from '../../common/terminal/types'; import { IConfigurationService, IDisposableRegistry } from '../../common/types'; import { TerminalCodeExecutionProvider } from './terminalCodeExecution'; @@ -18,11 +17,10 @@ export class ReplProvider extends TerminalCodeExecutionProvider { @inject(ITerminalServiceFactory) terminalServiceFactory: ITerminalServiceFactory, @inject(IConfigurationService) configurationService: IConfigurationService, @inject(IWorkspaceService) workspace: IWorkspaceService, - @inject(IPythonExecutionFactory) pythonExecFactory: IPythonExecutionFactory, @inject(IDisposableRegistry) disposableRegistry: Disposable[], @inject(IPlatformService) platformService: IPlatformService ) { - super(terminalServiceFactory, configurationService, workspace, disposableRegistry, platformService, pythonExecFactory); + super(terminalServiceFactory, configurationService, workspace, disposableRegistry, platformService); this.terminalTitle = 'REPL'; } } diff --git a/src/client/terminals/codeExecution/terminalCodeExecution.ts b/src/client/terminals/codeExecution/terminalCodeExecution.ts index 77cf4a0f9d75..a8e72ba0f58d 100644 --- a/src/client/terminals/codeExecution/terminalCodeExecution.ts +++ b/src/client/terminals/codeExecution/terminalCodeExecution.ts @@ -9,7 +9,7 @@ import { Disposable, Uri } from 'vscode'; import { IWorkspaceService } from '../../common/application/types'; import '../../common/extensions'; import { IPlatformService } from '../../common/platform/types'; -import { IPythonExecutionFactory, PythonExecutionInfo } from '../../common/process/types'; +import { PythonExecutionInfo } from '../../common/process/types'; import { ITerminalService, ITerminalServiceFactory } from '../../common/terminal/types'; import { IConfigurationService, IDisposableRegistry } from '../../common/types'; import { ICodeExecutionService } from '../../terminals/types'; @@ -24,8 +24,7 @@ export class TerminalCodeExecutionProvider implements ICodeExecutionService { @inject(IConfigurationService) protected readonly configurationService: IConfigurationService, @inject(IWorkspaceService) protected readonly workspace: IWorkspaceService, @inject(IDisposableRegistry) protected readonly disposables: Disposable[], - @inject(IPlatformService) protected readonly platformService: IPlatformService, - @inject(IPythonExecutionFactory) private readonly pythonExecFactory: IPythonExecutionFactory + @inject(IPlatformService) protected readonly platformService: IPlatformService ) {} public async executeFile(file: Uri) { @@ -64,11 +63,6 @@ export class TerminalCodeExecutionProvider implements ICodeExecutionService { const command = pythonSettings.pythonPath; const launchArgs = pythonSettings.terminal.launchArgs; - const condaExecutionService = await this.pythonExecFactory.createCondaExecutionService(command, undefined, resource); - if (condaExecutionService) { - return condaExecutionService.getExecutionInfo([...launchArgs, ...args]); - } - const isWindows = this.platformService.isWindows; return { diff --git a/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts b/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts index cb45844345c7..0c00e9ec8c5d 100644 --- a/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts +++ b/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts @@ -54,7 +54,6 @@ suite('Terminal - Django Shell Code Execution', () => { platform.object, commandManager.object, fileSystem.object, - pythonExecutionFactory.object, disposables ); @@ -186,9 +185,7 @@ suite('Terminal - Django Shell Code Execution', () => { const serviceContainer = TypeMoq.Mock.ofType(); const processService = TypeMoq.Mock.ofType(); const condaExecutionService = new CondaExecutionService(serviceContainer.object, processService.object, pythonPath, condaFile, condaEnv); - const hasEnvName = condaEnv.name !== ''; - const condaArgs = ['run', ...(hasEnvName ? ['-n', condaEnv.name] : ['-p', condaEnv.path]), 'python']; - const expectedTerminalArgs = [...condaArgs, ...terminalArgs, 'manage.py', 'shell']; + const expectedTerminalArgs = [...terminalArgs, 'manage.py', 'shell']; pythonExecutionFactory .setup(p => p.createCondaExecutionService(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())) .returns(() => Promise.resolve(condaExecutionService)); @@ -196,8 +193,8 @@ suite('Terminal - Django Shell Code Execution', () => { const replCommandArgs = await (executor as DjangoShellCodeExecutionProvider).getExecutableInfo(resource); expect(replCommandArgs).not.to.be.an('undefined', 'Conda command args are undefined'); - expect(replCommandArgs.command).to.be.equal(condaFile, 'Incorrect conda path'); - expect(replCommandArgs.args).to.be.deep.equal(expectedTerminalArgs, 'Incorrect conda arguments'); + expect(replCommandArgs.command).to.be.equal(pythonPath, 'Repl should use python not conda'); + expect(replCommandArgs.args).to.be.deep.equal(expectedTerminalArgs, 'Incorrect terminal arguments'); } test('Ensure conda args including env name are passed when using a conda environment with a name', async () => { diff --git a/src/test/terminals/codeExecution/helper.test.ts b/src/test/terminals/codeExecution/helper.test.ts index 8df26f02b2ca..767133aaa9d8 100644 --- a/src/test/terminals/codeExecution/helper.test.ts +++ b/src/test/terminals/codeExecution/helper.test.ts @@ -7,6 +7,7 @@ import { expect } from 'chai'; import * as fs from 'fs-extra'; import { EOL } from 'os'; import * as path from 'path'; +import { SemVer } from 'semver'; import * as TypeMoq from 'typemoq'; import { Range, Selection, TextDocument, TextEditor, TextLine, Uri } from 'vscode'; import { IApplicationShell, IDocumentManager } from '../../../client/common/application/types'; @@ -14,9 +15,10 @@ import { EXTENSION_ROOT_DIR, PYTHON_LANGUAGE } from '../../../client/common/cons import '../../../client/common/extensions'; import { BufferDecoder } from '../../../client/common/process/decoder'; import { ProcessService } from '../../../client/common/process/proc'; -import { IPythonExecutionFactory, IPythonExecutionService } from '../../../client/common/process/types'; -import { OSType } from '../../../client/common/utils/platform'; +import { IProcessService, IProcessServiceFactory } from '../../../client/common/process/types'; +import { Architecture, OSType } from '../../../client/common/utils/platform'; import { IEnvironmentVariablesProvider } from '../../../client/common/variables/types'; +import { IInterpreterService, InterpreterType, PythonInterpreter } from '../../../client/interpreter/contracts'; import { IServiceContainer } from '../../../client/ioc/types'; import { CodeExecutionHelper } from '../../../client/terminals/codeExecution/helper'; import { ICodeExecutionHelper } from '../../../client/terminals/types'; @@ -31,19 +33,33 @@ suite('Terminal - Code Execution Helper', () => { let helper: ICodeExecutionHelper; let document: TypeMoq.IMock; let editor: TypeMoq.IMock; - let pythonService: TypeMoq.IMock; + let processService: TypeMoq.IMock; + let interpreterService: TypeMoq.IMock; + const workingPython: PythonInterpreter = { + path: PYTHON_PATH, + version: new SemVer('3.6.6-final'), + sysVersion: '1.0.0.0', + sysPrefix: 'Python', + displayName: 'Python', + type: InterpreterType.Unknown, + architecture: Architecture.x64 + }; + setup(() => { const serviceContainer = TypeMoq.Mock.ofType(); documentManager = TypeMoq.Mock.ofType(); applicationShell = TypeMoq.Mock.ofType(); const envVariablesProvider = TypeMoq.Mock.ofType(); - pythonService = TypeMoq.Mock.ofType(); + processService = TypeMoq.Mock.ofType(); + interpreterService = TypeMoq.Mock.ofType(); // tslint:disable-next-line:no-any - pythonService.setup((x: any) => x.then).returns(() => undefined); + processService.setup((x: any) => x.then).returns(() => undefined); + interpreterService.setup(i => i.getActiveInterpreter(TypeMoq.It.isAny())).returns(() => Promise.resolve(workingPython)); + const processServiceFactory = TypeMoq.Mock.ofType(); + processServiceFactory.setup(p => p.create(TypeMoq.It.isAny())).returns(() => Promise.resolve(processService.object)); envVariablesProvider.setup(e => e.getEnvironmentVariables(TypeMoq.It.isAny())).returns(() => Promise.resolve({})); - const pythonExecFactory = TypeMoq.Mock.ofType(); - pythonExecFactory.setup(p => p.create(TypeMoq.It.isAny())).returns(() => Promise.resolve(pythonService.object)); - serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IPythonExecutionFactory), TypeMoq.It.isAny())).returns(() => pythonExecFactory.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IProcessServiceFactory), TypeMoq.It.isAny())).returns(() => processServiceFactory.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IInterpreterService), TypeMoq.It.isAny())).returns(() => interpreterService.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IDocumentManager), TypeMoq.It.isAny())).returns(() => documentManager.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IApplicationShell), TypeMoq.It.isAny())).returns(() => applicationShell.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IEnvironmentVariablesProvider), TypeMoq.It.isAny())).returns(() => envVariablesProvider.object); @@ -56,10 +72,10 @@ suite('Terminal - Code Execution Helper', () => { async function ensureBlankLinesAreRemoved(source: string, expectedSource: string) { const actualProcessService = new ProcessService(new BufferDecoder()); - pythonService - .setup(p => p.exec(TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns((args, options) => { - return actualProcessService.exec.apply(actualProcessService, [PYTHON_PATH, args, options]); + processService + .setup(p => p.exec(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .returns((file, args, options) => { + return actualProcessService.exec.apply(actualProcessService, [file, args, options]); }); const normalizedZCode = await helper.normalizeLines(source); // In case file has been saved with different line endings. @@ -81,9 +97,9 @@ suite('Terminal - Code Execution Helper', () => { test('Ensure there are no multiple-CR elements in the normalized code.', async () => { const code = ['import sys', '', '', '', 'print(sys.executable)', '', 'print("1234")', '', '', 'print(1)', 'print(2)']; const actualProcessService = new ProcessService(new BufferDecoder()); - pythonService - .setup(p => p.exec(TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns((args, options) => { + processService + .setup(p => p.exec(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .returns((_file, args, options) => { return actualProcessService.exec.apply(actualProcessService, [PYTHON_PATH, args, options]); }); const normalizedCode = await helper.normalizeLines(code.join(EOL)); diff --git a/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts b/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts index c0ccf96c0b5e..d890056d83f5 100644 --- a/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts +++ b/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts @@ -67,18 +67,11 @@ suite('Terminal - Code Execution', () => { switch (testSuiteName) { case 'Terminal Execution': { - executor = new TerminalCodeExecutionProvider( - terminalFactory.object, - configService.object, - workspace.object, - disposables, - platform.object, - pythonExecutionFactory.object - ); + executor = new TerminalCodeExecutionProvider(terminalFactory.object, configService.object, workspace.object, disposables, platform.object); break; } case 'Repl Execution': { - executor = new ReplProvider(terminalFactory.object, configService.object, workspace.object, pythonExecutionFactory.object, disposables, platform.object); + executor = new ReplProvider(terminalFactory.object, configService.object, workspace.object, disposables, platform.object); expectedTerminalTitle = 'REPL'; break; } @@ -97,7 +90,6 @@ suite('Terminal - Code Execution', () => { platform.object, commandManager.object, fileSystem.object, - pythonExecutionFactory.object, disposables ); expectedTerminalTitle = 'Django Shell'; @@ -239,7 +231,6 @@ suite('Terminal - Code Execution', () => { const expectedPythonPath = isWindows ? pythonPath.replace(/\\/g, '/') : pythonPath; const expectedArgs = terminalArgs.concat(file.fsPath.fileToCommandArgument()); terminalService.verify(async t => t.sendCommand(TypeMoq.It.isValue(expectedPythonPath), TypeMoq.It.isValue(expectedArgs)), TypeMoq.Times.once()); - pythonExecutionFactory.verify(async p => p.createCondaExecutionService(pythonPath, undefined, file), TypeMoq.Times.once()); } test('Ensure python file execution script is sent to terminal on windows', async () => { @@ -278,12 +269,9 @@ suite('Terminal - Code Execution', () => { await executor.executeFile(file); - const hasEnvName = condaEnv.name !== ''; - const condaArgs = ['run', ...(hasEnvName ? ['-n', condaEnv.name] : ['-p', condaEnv.path]), 'python']; - const expectedArgs = [...condaArgs, ...terminalArgs, file.fsPath.fileToCommandArgument()]; + const expectedArgs = [...terminalArgs, file.fsPath.fileToCommandArgument()]; - pythonExecutionFactory.verify(async p => p.createCondaExecutionService(pythonPath, undefined, file), TypeMoq.Times.once()); - terminalService.verify(async t => t.sendCommand(TypeMoq.It.isValue(condaFile), TypeMoq.It.isValue(expectedArgs)), TypeMoq.Times.once()); + terminalService.verify(async t => t.sendCommand(TypeMoq.It.isValue(pythonPath), TypeMoq.It.isValue(expectedArgs)), TypeMoq.Times.once()); } test('Ensure conda args with conda env name are sent to terminal if there is a conda environment with a name', async () => { @@ -309,7 +297,6 @@ suite('Terminal - Code Execution', () => { expect(replCommandArgs).not.to.be.an('undefined', 'Command args is undefined'); expect(replCommandArgs.command).to.be.equal(expectedPythonPath, 'Incorrect python path'); expect(replCommandArgs.args).to.be.deep.equal(expectedTerminalArgs, 'Incorrect arguments'); - pythonExecutionFactory.verify(async p => p.createCondaExecutionService(pythonPath, undefined, undefined), TypeMoq.Times.once()); } test('Ensure fully qualified python path is escaped when building repl args on Windows', async () => { @@ -359,17 +346,14 @@ suite('Terminal - Code Execution', () => { .setup(p => p.createCondaExecutionService(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())) .returns(() => Promise.resolve(condaExecutionService)); - const hasEnvName = condaEnv.name !== ''; - const condaArgs = ['run', ...(hasEnvName ? ['-n', condaEnv.name] : ['-p', condaEnv.path]), 'python']; const djangoArgs = isDjangoRepl ? ['manage.py', 'shell'] : []; - const expectedTerminalArgs = [...condaArgs, ...terminalArgs, ...djangoArgs]; + const expectedTerminalArgs = [...terminalArgs, ...djangoArgs]; const replCommandArgs = await (executor as TerminalCodeExecutionProvider).getExecutableInfo(); expect(replCommandArgs).not.to.be.an('undefined', 'Conda command args are undefined'); - expect(replCommandArgs.command).to.be.equal('conda', 'Incorrect conda path'); - expect(replCommandArgs.args).to.be.deep.equal(expectedTerminalArgs, 'Incorrect conda arguments'); - pythonExecutionFactory.verify(async p => p.createCondaExecutionService(pythonPath, undefined, undefined), TypeMoq.Times.once()); + expect(replCommandArgs.command).to.be.equal(pythonPath, 'Repl needs to use python, not conda'); + expect(replCommandArgs.args).to.be.deep.equal(expectedTerminalArgs, 'Incorrect terminal arguments'); } test('Ensure conda args with env name are returned when building repl args with a conda env with a name', async () => {