diff --git a/package.json b/package.json index b3dd496d355c..b03fdfe81de8 100644 --- a/package.json +++ b/package.json @@ -442,8 +442,7 @@ "pythonDiscoveryUsingWorkers", "pythonTestAdapter", "pythonREPLSmartSend", - "pythonRecommendTensorboardExt", - "pythonRunREPL" + "pythonRecommendTensorboardExt" ], "enumDescriptions": [ "%python.experiments.All.description%", @@ -453,8 +452,7 @@ "%python.experiments.pythonDiscoveryUsingWorkers.description%", "%python.experiments.pythonTestAdapter.description%", "%python.experiments.pythonREPLSmartSend.description%", - "%python.experiments.pythonRecommendTensorboardExt.description%", - "%python.experiments.pythonRunREPL.description%" + "%python.experiments.pythonRecommendTensorboardExt.description%" ] }, "scope": "window", @@ -472,8 +470,7 @@ "pythonTerminalEnvVarActivation", "pythonDiscoveryUsingWorkers", "pythonTestAdapter", - "pythonREPLSmartSend", - "pythonRunREPL" + "pythonREPLSmartSend" ], "enumDescriptions": [ "%python.experiments.All.description%", @@ -482,8 +479,7 @@ "%python.experiments.pythonTerminalEnvVarActivation.description%", "%python.experiments.pythonDiscoveryUsingWorkers.description%", "%python.experiments.pythonTestAdapter.description%", - "%python.experiments.pythonREPLSmartSend.description%", - "%python.experiments.pythonRunREPL.description%" + "%python.experiments.pythonREPLSmartSend.description%" ] }, "scope": "window", @@ -628,6 +624,15 @@ "scope": "resource", "type": "boolean" }, + "python.REPL.sendToNativeREPL": { + "default": false, + "description": "%python.REPL.sendToNativeREPL.description%", + "scope": "resource", + "type": "boolean", + "tags": [ + "experimental" + ] + }, "python.testing.autoTestDiscoverOnSaveEnabled": { "default": true, "description": "%python.testing.autoTestDiscoverOnSaveEnabled.description%", @@ -1108,7 +1113,22 @@ { "command": "python.execSelectionInTerminal", "key": "shift+enter", - "when": "editorTextFocus && editorLangId == python && !findInputFocussed && !replaceInputFocussed && !jupyter.ownsSelection && !notebookEditorFocused && activeEditor != 'workbench.editor.interactive'" + "when": "!config.python.REPL.sendToNativeREPL && editorTextFocus && editorLangId == python && !findInputFocussed && !replaceInputFocussed && !jupyter.ownsSelection && !notebookEditorFocused && activeEditor != 'workbench.editor.interactive'" + }, + { + "command": "python.execInREPL", + "key": "shift+enter", + "when": "config.python.REPL.sendToNativeREPL && activeEditor != 'workbench.editor.interactive'" + }, + { + "command": "python.execREPLShiftEnter", + "key": "shift+enter", + "when": "activeEditor == 'workbench.editor.interactive' && config.interactiveWindow.executeWithShiftEnter" + }, + { + "command": "python.execInREPLEnter", + "key": "enter", + "when": "!config.interactiveWindow.executeWithShiftEnter && activeEditor == 'workbench.editor.interactive'" }, { "command": "python.refreshTensorBoard", @@ -1367,12 +1387,12 @@ { "command": "python.execSelectionInTerminal", "group": "Python", - "when": "editorFocus && editorLangId == python && !virtualWorkspace && shellExecutionSupported" + "when": "!config.python.REPL.sendToNativeREPL && editorFocus && editorLangId == python && !virtualWorkspace && shellExecutionSupported" }, { "command": "python.execInREPL", "group": "Python", - "when": "editorFocus && editorLangId == python && !virtualWorkspace && shellExecutionSupported && pythonRunREPL" + "when": "editorFocus && editorLangId == python && !virtualWorkspace && shellExecutionSupported && config.python.REPL.sendToNativeREPL" } ], "editor/title": [ diff --git a/package.nls.json b/package.nls.json index aa84f31a91b2..669a14bed528 100644 --- a/package.nls.json +++ b/package.nls.json @@ -45,7 +45,6 @@ "python.experiments.pythonTestAdapter.description": "Denotes the Python Test Adapter experiment.", "python.experiments.pythonREPLSmartSend.description": "Denotes the Python REPL Smart Send experiment.", "python.experiments.pythonRecommendTensorboardExt.description": "Denotes the Tensorboard Extension recommendation experiment.", - "python.experiments.pythonRunREPL.description": "Enables users to run code in interactive Python REPL.", "python.globalModuleInstallation.description": "Whether to install Python modules globally when not using an environment.", "python.languageServer.description": "Defines type of the language server.", "python.languageServer.defaultDescription": "Automatically select a language server: Pylance if installed and available, otherwise fallback to Jedi.", @@ -63,6 +62,7 @@ "python.pipenvPath.description": "Path to the pipenv executable to use for activation.", "python.poetryPath.description": "Path to the poetry executable.", "python.EnableREPLSmartSend.description": "Toggle Smart Send for the Python REPL. Smart Send enables sending the smallest runnable block of code to the REPL on Shift+Enter and moves the cursor accordingly.", + "python.REPL.sendToNativeREPL.description": "Toggle to send code to Python REPL instead of the terminal on execution. Turning this on will change the behavior for both Smart Send and Run Selection/Line in the Context Menu.", "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`.", diff --git a/python_files/python_server.py b/python_files/python_server.py index 4d27a168bc4c..30be834631c6 100644 --- a/python_files/python_server.py +++ b/python_files/python_server.py @@ -1,11 +1,11 @@ from typing import Dict, List, Optional, Union - import sys import json import contextlib import io import traceback import uuid +import ast STDIN = sys.stdin STDOUT = sys.stdout @@ -88,6 +88,15 @@ def exec_function(user_input): return eval +def check_valid_command(request): + try: + user_input = request["params"] + ast.parse(user_input[0]) + send_response("True", request["id"]) + except SyntaxError: + send_response("False", request["id"]) + + def execute(request, user_globals): str_output = CustomIO("", encoding="utf-8") str_error = CustomIO("", encoding="utf-8") @@ -160,6 +169,8 @@ def get_headers(): request_json = json.loads(request_text) if request_json["method"] == "execute": execute(request_json, USER_GLOBALS) + if request_json["method"] == "check_valid_command": + check_valid_command(request_json) elif request_json["method"] == "exit": sys.exit(0) diff --git a/src/client/common/application/commands.ts b/src/client/common/application/commands.ts index 626321566332..4c00971ffdd5 100644 --- a/src/client/common/application/commands.ts +++ b/src/client/common/application/commands.ts @@ -39,6 +39,7 @@ interface ICommandNameWithoutArgumentTypeMapping { [Commands.Exec_Selection_In_Terminal]: []; [Commands.Exec_Selection_In_Django_Shell]: []; [Commands.Exec_In_REPL]: []; + [Commands.Exec_In_REPL_Enter]: []; [Commands.Create_Terminal]: []; [Commands.PickLocalProcess]: []; [Commands.ClearStorage]: []; diff --git a/src/client/common/constants.ts b/src/client/common/constants.ts index 663b932c8542..94bcc78c6c6b 100644 --- a/src/client/common/constants.ts +++ b/src/client/common/constants.ts @@ -48,6 +48,8 @@ export namespace Commands { export const Exec_In_Separate_Terminal = 'python.execInDedicatedTerminal'; export const Exec_In_REPL = 'python.execInREPL'; export const Exec_Selection_In_Django_Shell = 'python.execSelectionInDjangoShell'; + export const Exec_In_REPL_Enter = 'python.execInREPLEnter'; + export const Exec_In_REPL_Shift_Enter = 'python.execREPLShiftEnter'; export const Exec_Selection_In_Terminal = 'python.execSelectionInTerminal'; export const GetSelectedInterpreterPath = 'python.interpreterPath'; export const InstallJupyter = 'python.installJupyter'; diff --git a/src/client/common/experiments/groups.ts b/src/client/common/experiments/groups.ts index 543b1e27516f..81f157751346 100644 --- a/src/client/common/experiments/groups.ts +++ b/src/client/common/experiments/groups.ts @@ -30,8 +30,3 @@ export enum RecommendTensobardExtension { export enum CreateEnvOnPipInstallTrigger { experiment = 'pythonCreateEnvOnPipInstall', } - -// Experiment to enable running Python REPL using IW. -export enum EnableRunREPL { - experiment = 'pythonRunREPL', -} diff --git a/src/client/common/types.ts b/src/client/common/types.ts index 8edc76ff2bff..d4a0921140ec 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -200,7 +200,7 @@ export interface ITerminalSettings { export interface IREPLSettings { readonly enableREPLSmartSend: boolean; - readonly enableIWREPL: boolean; + readonly sendToNativeREPL: boolean; } export interface IExperiments { diff --git a/src/client/common/vscodeApis/windowApis.ts b/src/client/common/vscodeApis/windowApis.ts index 6ef02df41d1e..37d302946614 100644 --- a/src/client/common/vscodeApis/windowApis.ts +++ b/src/client/common/vscodeApis/windowApis.ts @@ -21,6 +21,8 @@ import { TerminalShellExecutionStartEvent, } from 'vscode'; import { createDeferred, Deferred } from '../utils/async'; +import { Resource } from '../types'; +import { getWorkspaceFolders } from './workspaceApis'; export function showTextDocument(uri: Uri): Thenable { return window.showTextDocument(uri); @@ -238,3 +240,12 @@ export function createStepForwardEndNode(deferred?: Deferred, result?: T): undefined, ); } + +export function getActiveResource(): Resource { + const editor = window.activeTextEditor; + if (editor && !editor.document.isUntitled) { + return editor.document.uri; + } + const workspaces = getWorkspaceFolders(); + return Array.isArray(workspaces) && workspaces.length > 0 ? workspaces[0].uri : undefined; +} diff --git a/src/client/extensionActivation.ts b/src/client/extensionActivation.ts index 7c582eb63239..37d97b8ccbab 100644 --- a/src/client/extensionActivation.ts +++ b/src/client/extensionActivation.ts @@ -3,7 +3,7 @@ 'use strict'; -import { DebugConfigurationProvider, debug, languages, window, commands } from 'vscode'; +import { DebugConfigurationProvider, debug, languages, window } from 'vscode'; import { registerTypes as activationRegisterTypes } from './activation/serviceRegistry'; import { IExtensionActivationManager } from './activation/types'; @@ -16,7 +16,6 @@ import { IFileSystem } from './common/platform/types'; import { IConfigurationService, IDisposableRegistry, - IExperimentService, IExtensions, IInterpreterPathService, ILogOutputChannel, @@ -53,8 +52,7 @@ import { initializePersistentStateForTriggers } from './common/persistentState'; import { logAndNotifyOnLegacySettings } from './logging/settingLogs'; import { DebuggerTypeName } from './debugger/constants'; import { StopWatch } from './common/utils/stopWatch'; -import { registerReplCommands } from './repl/replCommands'; -import { EnableRunREPL } from './common/experiments/groups'; +import { registerReplCommands, registerReplExecuteOnEnter, registerReplExecuteOnShiftEnter } from './repl/replCommands'; export async function activateComponents( // `ext` is passed to any extra activation funcs. @@ -109,16 +107,9 @@ export function activateFeatures(ext: ExtensionState, _components: Components): pathUtils, ); - // Register native REPL context menu when in experiment - const experimentService = ext.legacyIOC.serviceContainer.get(IExperimentService); - commands.executeCommand('setContext', 'pythonRunREPL', false); - if (experimentService) { - const replExperimentValue = experimentService.inExperimentSync(EnableRunREPL.experiment); - if (replExperimentValue) { - registerReplCommands(ext.disposables, interpreterService); - commands.executeCommand('setContext', 'pythonRunREPL', true); - } - } + registerReplCommands(ext.disposables, interpreterService); + registerReplExecuteOnEnter(ext.disposables, interpreterService); + registerReplExecuteOnShiftEnter(ext.disposables); } /// ////////////////////////// diff --git a/src/client/repl/pythonServer.ts b/src/client/repl/pythonServer.ts index e25ba3a25092..766fcf4cc92c 100644 --- a/src/client/repl/pythonServer.ts +++ b/src/client/repl/pythonServer.ts @@ -6,27 +6,32 @@ import { EXTENSION_ROOT_DIR } from '../constants'; import { traceError, traceLog } from '../logging'; const SERVER_PATH = path.join(EXTENSION_ROOT_DIR, 'python_files', 'python_server.py'); +let serverInstance: PythonServer | undefined; export interface PythonServer extends Disposable { execute(code: string): Promise; interrupt(): void; input(): void; + checkValidCommand(code: string): Promise; } class PythonServerImpl implements Disposable { + private readonly disposables: Disposable[] = []; + constructor(private connection: rpc.MessageConnection, private pythonServer: ch.ChildProcess) { this.initialize(); this.input(); } private initialize(): void { - this.connection.onNotification('log', (message: string) => { - console.log('Log:', message); - }); + this.disposables.push( + this.connection.onNotification('log', (message: string) => { + console.log('Log:', message); + }), + ); this.connection.listen(); } - // Register input handler public input(): void { // Register input request handler this.connection.onRequest('input', async (request) => { @@ -49,18 +54,32 @@ class PythonServerImpl implements Disposable { } public interrupt(): void { + // Passing SIGINT to interrupt only would work for Mac and Linux if (this.pythonServer.kill('SIGINT')) { - traceLog('Python server interrupted'); + traceLog('Python REPL server interrupted'); + } + } + + public async checkValidCommand(code: string): Promise { + const completeCode = await this.connection.sendRequest('check_valid_command', code); + if (completeCode === 'True') { + return new Promise((resolve) => resolve(true)); } + return new Promise((resolve) => resolve(false)); } public dispose(): void { this.connection.sendNotification('exit'); + this.disposables.forEach((d) => d.dispose()); this.connection.dispose(); } } export function createPythonServer(interpreter: string[]): PythonServer { + if (serverInstance) { + return serverInstance; + } + const pythonServer = ch.spawn(interpreter[0], [...interpreter.slice(1), SERVER_PATH]); pythonServer.stderr.on('data', (data) => { @@ -76,6 +95,6 @@ export function createPythonServer(interpreter: string[]): PythonServer { new rpc.StreamMessageReader(pythonServer.stdout), new rpc.StreamMessageWriter(pythonServer.stdin), ); - - return new PythonServerImpl(connection, pythonServer); + serverInstance = new PythonServerImpl(connection, pythonServer); + return serverInstance; } diff --git a/src/client/repl/replCommands.ts b/src/client/repl/replCommands.ts index e7a40b01c6be..d9d0858a6637 100644 --- a/src/client/repl/replCommands.ts +++ b/src/client/repl/replCommands.ts @@ -12,17 +12,23 @@ import { WorkspaceEdit, NotebookEditor, TextEditor, + Selection, + NotebookDocument, } from 'vscode'; import { Disposable } from 'vscode-jsonrpc'; import { Commands, PVSC_EXTENSION_ID } from '../common/constants'; import { noop } from '../common/utils/misc'; import { IInterpreterService } from '../interpreter/contracts'; import { getMultiLineSelectionText, getSingleLineSelectionText } from '../terminals/codeExecution/helper'; +import { createPythonServer } from './pythonServer'; import { createReplController } from './replController'; +import { getActiveResource } from '../common/vscodeApis/windowApis'; +import { getConfiguration } from '../common/vscodeApis/workspaceApis'; let notebookController: NotebookController | undefined; let notebookEditor: NotebookEditor | undefined; // TODO: figure out way to put markdown telling user kernel has been dead and need to pick again. +let notebookDocument: NotebookDocument | undefined; async function getSelectedTextToExecute(textEditor: TextEditor): Promise { if (!textEditor) { @@ -42,13 +48,26 @@ async function getSelectedTextToExecute(textEditor: TextEditor): Promise('REPL.sendToNativeREPL', false); +} +// Will only be called when user has experiment enabled. export async function registerReplCommands( disposables: Disposable[], interpreterService: IInterpreterService, ): Promise { disposables.push( commands.registerCommand(Commands.Exec_In_REPL, async (uri: Uri) => { + const nativeREPLSetting = getSendToNativeREPLSetting(); + + // If nativeREPLSetting is false(Send to Terminal REPL), then fall back to running in Terminal REPL + if (!nativeREPLSetting) { + await commands.executeCommand(Commands.Exec_Selection_In_Terminal); + return; + } + const interpreter = await interpreterService.getActiveInterpreter(uri); if (!interpreter) { commands.executeCommand(Commands.TriggerEnvironmentSelection, uri).then(noop, noop); @@ -58,47 +77,130 @@ export async function registerReplCommands( const interpreterPath = interpreter.path; if (!notebookController) { - notebookController = createReplController(interpreterPath); + notebookController = createReplController(interpreterPath, disposables); } const activeEditor = window.activeTextEditor as TextEditor; - const code = await getSelectedTextToExecute(activeEditor); - const ourResource = Uri.from({ scheme: 'untitled', path: 'repl.interactive' }); - - const notebookDocument = await workspace.openNotebookDocument(ourResource); - // commands.executeCommand('_interactive.open'); command to open interactive window so intellisense is registered. - // We want to keep notebookEditor, whenever we want to run. - // Find interactive window, or open it. if (!notebookEditor) { - notebookEditor = await window.showNotebookDocument(notebookDocument, { - viewColumn: ViewColumn.Beside, + const interactiveWindowObject = (await commands.executeCommand( + 'interactive.open', + { + preserveFocus: true, + viewColumn: ViewColumn.Beside, + }, + undefined, + notebookController.id, + 'Python REPL', + )) as { notebookEditor: NotebookEditor }; + notebookEditor = interactiveWindowObject.notebookEditor; + notebookDocument = interactiveWindowObject.notebookEditor.notebook; + } + + if (notebookDocument) { + notebookController.updateNotebookAffinity(notebookDocument, NotebookControllerAffinity.Default); + + // Auto-Select Python REPL Kernel + await commands.executeCommand('notebook.selectKernel', { + notebookEditor, + id: notebookController.id, + extension: PVSC_EXTENSION_ID, + }); + + const { cellCount } = notebookDocument; + await addCellToNotebook(code as string); + // Execute the cell + commands.executeCommand('notebook.cell.execute', { + ranges: [{ start: cellCount, end: cellCount + 1 }], + document: notebookDocument.uri, }); } + } + }), + ); +} +/** + * Function that adds cell to notebook. + * This function will only get called when notebook document is defined. + * @param code + * + */ +async function addCellToNotebook(code: string): Promise { + const notebookCellData = new NotebookCellData(NotebookCellKind.Code, code as string, 'python'); + const { cellCount } = notebookDocument!; + // Add new cell to interactive window document + const notebookEdit = NotebookEdit.insertCells(cellCount, [notebookCellData]); + const workspaceEdit = new WorkspaceEdit(); + workspaceEdit.set(notebookDocument!.uri, [notebookEdit]); + await workspace.applyEdit(workspaceEdit); +} - notebookController!.updateNotebookAffinity(notebookDocument, NotebookControllerAffinity.Default); - - // Auto-Select Python REPL Kernel - await commands.executeCommand('notebook.selectKernel', { - notebookEditor, - id: notebookController?.id, - extension: PVSC_EXTENSION_ID, - }); - - const notebookCellData = new NotebookCellData(NotebookCellKind.Code, code as string, 'python'); - const { cellCount } = notebookDocument; - // Add new cell to interactive window document - const notebookEdit = NotebookEdit.insertCells(cellCount, [notebookCellData]); - const workspaceEdit = new WorkspaceEdit(); - workspaceEdit.set(notebookDocument.uri, [notebookEdit]); - await workspace.applyEdit(workspaceEdit); - - // Execute the cell - commands.executeCommand('notebook.cell.execute', { - ranges: [{ start: cellCount, end: cellCount + 1 }], - document: ourResource, - }); +/** + * Command triggered for 'Enter': Conditionally call interactive.execute OR insert \n in text input box. + * @param disposables + * @param interpreterService + */ +export async function registerReplExecuteOnEnter( + disposables: Disposable[], + interpreterService: IInterpreterService, +): Promise { + disposables.push( + commands.registerCommand(Commands.Exec_In_REPL_Enter, async (uri: Uri) => { + const interpreter = await interpreterService.getActiveInterpreter(uri); + if (!interpreter) { + commands.executeCommand(Commands.TriggerEnvironmentSelection, uri).then(noop, noop); + return; + } + + // Create Separate Python server to check valid command + const pythonServer = createPythonServer([interpreter.path as string]); + + const activeEditor = window.activeTextEditor; + let userTextInput; + let completeCode = false; + + if (activeEditor) { + const { document } = activeEditor; + userTextInput = document.getText(); + } + + // Check if userTextInput is a complete Python command + if (userTextInput) { + completeCode = await pythonServer.checkValidCommand(userTextInput); + } + const editor = window.activeTextEditor; + // Execute right away when complete code and Not multi-line + if (completeCode && !isMultiLineText(editor)) { + await commands.executeCommand('interactive.execute'); + } else { + // Insert new line on behalf of user. "Regular" monaco editor behavior + if (editor) { + const position = editor.selection.active; + const newPosition = position.with(position.line, editor.document.lineAt(position.line).text.length); + editor.selection = new Selection(newPosition, newPosition); + + editor.edit((editBuilder) => { + editBuilder.insert(newPosition, '\n'); + }); + } + + // Handle case when user enters on blank line, just trigger interactive.execute + if (editor && editor.document.lineAt(editor.selection.active.line).text === '') { + await commands.executeCommand('interactive.execute'); + } } }), ); } + +export async function registerReplExecuteOnShiftEnter(disposables: Disposable[]): Promise { + disposables.push( + commands.registerCommand(Commands.Exec_In_REPL_Shift_Enter, async () => { + await commands.executeCommand(Commands.Exec_In_REPL_Enter); + }), + ); +} + +function isMultiLineText(textEditor: TextEditor | undefined): boolean { + return (textEditor?.document?.lineCount ?? 0) > 1; +} diff --git a/src/client/repl/replController.ts b/src/client/repl/replController.ts index f7ee7e6d486c..86d021cd1c7a 100644 --- a/src/client/repl/replController.ts +++ b/src/client/repl/replController.ts @@ -1,8 +1,13 @@ import * as vscode from 'vscode'; import { createPythonServer } from './pythonServer'; -export function createReplController(interpreterPath: string): vscode.NotebookController { +export function createReplController( + interpreterPath: string, + disposables: vscode.Disposable[], +): vscode.NotebookController { const server = createPythonServer([interpreterPath]); + disposables.push(server); + const controller = vscode.notebooks.createNotebookController('pythonREPL', 'interactive', 'Python REPL'); controller.supportedLanguages = ['python']; controller.supportsExecutionOrder = true; @@ -39,5 +44,6 @@ export function createReplController(interpreterPath: string): vscode.NotebookCo } } }; + disposables.push(controller); return controller; } diff --git a/src/test/terminals/codeExecution/helper.test.ts b/src/test/terminals/codeExecution/helper.test.ts index 9098455c968e..6e2ea2a61061 100644 --- a/src/test/terminals/codeExecution/helper.test.ts +++ b/src/test/terminals/codeExecution/helper.test.ts @@ -112,7 +112,7 @@ suite('Terminal - Code Execution Helper', () => { activeResourceService.setup((a) => a.getActiveResource()).returns(() => resource); pythonSettings .setup((s) => s.REPL) - .returns(() => ({ enableREPLSmartSend: false, REPLSmartSend: false, enableIWREPL: false })); + .returns(() => ({ enableREPLSmartSend: false, REPLSmartSend: false, sendToNativeREPL: false })); configurationService.setup((x) => x.getSettings(TypeMoq.It.isAny())).returns(() => pythonSettings.object); configurationService .setup((c) => c.getSettings(TypeMoq.It.isAny())) diff --git a/src/test/terminals/codeExecution/smartSend.test.ts b/src/test/terminals/codeExecution/smartSend.test.ts index ba5101332bf8..c6f4ae195d16 100644 --- a/src/test/terminals/codeExecution/smartSend.test.ts +++ b/src/test/terminals/codeExecution/smartSend.test.ts @@ -109,7 +109,7 @@ suite('REPL - Smart Send', () => { pythonSettings .setup((s) => s.REPL) - .returns(() => ({ enableREPLSmartSend: true, REPLSmartSend: true, enableIWREPL: false })); + .returns(() => ({ enableREPLSmartSend: true, REPLSmartSend: true, sendToNativeREPL: false })); configurationService.setup((x) => x.getSettings(TypeMoq.It.isAny())).returns(() => pythonSettings.object);