From 5f279f1aa0e022947cfa6dd46cc21d2351ef0e2d Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 15 May 2024 20:05:58 -0700 Subject: [PATCH 01/33] start adding command --- package.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/package.json b/package.json index b3dd496d355c..8b69939cb9f0 100644 --- a/package.json +++ b/package.json @@ -311,6 +311,11 @@ "command": "python.execInREPL", "title": "%python.command.python.execInREPL.title%" }, + { + "category": "Python", + "command": "python.execInREPLEnter", + "title": "%python.command.python.execInREPLEnter.title%" + }, { "category": "Python", "command": "python.launchTensorBoard", From 7992d4b0637ddf47b17afa832561cf15f7a85815 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Thu, 16 May 2024 01:45:06 -0700 Subject: [PATCH 02/33] allow execute on enter and execute on blank space with hard coded bool --- package.json | 8 +++++++- src/client/common/constants.ts | 1 + src/client/extensionActivation.ts | 3 ++- src/client/repl/replCommands.ts | 34 +++++++++++++++++++++++++++++++ 4 files changed, 44 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 8b69939cb9f0..710a793fa1da 100644 --- a/package.json +++ b/package.json @@ -459,7 +459,8 @@ "%python.experiments.pythonTestAdapter.description%", "%python.experiments.pythonREPLSmartSend.description%", "%python.experiments.pythonRecommendTensorboardExt.description%", - "%python.experiments.pythonRunREPL.description%" + "%python.experiments.pythonRunREPL.description%", + "%python.command.python.execInREPLEnter.title%" ] }, "scope": "window", @@ -1115,6 +1116,11 @@ "key": "shift+enter", "when": "editorTextFocus && editorLangId == python && !findInputFocussed && !replaceInputFocussed && !jupyter.ownsSelection && !notebookEditorFocused && activeEditor != 'workbench.editor.interactive'" }, + { + "command": "python.execInREPLEnter", + "key": "enter", + "when": "activeEditor == 'workbench.editor.interactive'" + }, { "command": "python.refreshTensorBoard", "key": "ctrl+r", diff --git a/src/client/common/constants.ts b/src/client/common/constants.ts index 663b932c8542..d5b82f68ae97 100644 --- a/src/client/common/constants.ts +++ b/src/client/common/constants.ts @@ -48,6 +48,7 @@ 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_Selection_In_Terminal = 'python.execSelectionInTerminal'; export const GetSelectedInterpreterPath = 'python.interpreterPath'; export const InstallJupyter = 'python.installJupyter'; diff --git a/src/client/extensionActivation.ts b/src/client/extensionActivation.ts index 7c582eb63239..ddebcac6e8a6 100644 --- a/src/client/extensionActivation.ts +++ b/src/client/extensionActivation.ts @@ -53,7 +53,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 { registerReplCommands, registerReplExecuteOnEnter } from './repl/replCommands'; import { EnableRunREPL } from './common/experiments/groups'; export async function activateComponents( @@ -117,6 +117,7 @@ export function activateFeatures(ext: ExtensionState, _components: Components): if (replExperimentValue) { registerReplCommands(ext.disposables, interpreterService); commands.executeCommand('setContext', 'pythonRunREPL', true); + registerReplExecuteOnEnter(ext.disposables); } } } diff --git a/src/client/repl/replCommands.ts b/src/client/repl/replCommands.ts index e7a40b01c6be..18cef03dee7e 100644 --- a/src/client/repl/replCommands.ts +++ b/src/client/repl/replCommands.ts @@ -12,6 +12,7 @@ import { WorkspaceEdit, NotebookEditor, TextEditor, + Selection, } from 'vscode'; import { Disposable } from 'vscode-jsonrpc'; import { Commands, PVSC_EXTENSION_ID } from '../common/constants'; @@ -102,3 +103,36 @@ export async function registerReplCommands( }), ); } + +// Register Python execute command for keybinding 'Enter' +// Conditionally call interactive.execute OR insert \n in text input box. +export async function registerReplExecuteOnEnter(disposables: Disposable[]): Promise { + disposables.push( + commands.registerCommand(Commands.Exec_In_REPL_Enter, async (uri: Uri) => { + const completeCode = false; + const uri2 = uri; + if (completeCode) { + await commands.executeCommand('interactive.execute'); + } else { + // Insert new line on behalf of user. "Regular" monaco editor behavior + const editor = window.activeTextEditor; + if (editor) { + const position = editor.selection.active; + // move cursor to end of line and also add newline character + const newPosition = position.with(position.line, editor.document.lineAt(position.line).text.length); + editor.selection = new Selection(newPosition, newPosition); + // add newline character + editor.edit((editBuilder) => { + editBuilder.insert(newPosition, '\n'); + }); + } + + // If user types enter on blank line, call interactive.execute + if (editor && editor.document.lineAt(editor.selection.active.line).text === '') { + await commands.executeCommand('interactive.execute'); + } + } + }), // This closing parenthesis was missing. + ); + // Handle case when user enters on blank line, just trigger interactive.execute +} From 31f7db4cb2e2d5e60b2f23c6772f4314aa58fc68 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Fri, 17 May 2024 00:33:52 -0700 Subject: [PATCH 03/33] remove unintended enum to experiment --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 710a793fa1da..74534df11fd3 100644 --- a/package.json +++ b/package.json @@ -459,8 +459,7 @@ "%python.experiments.pythonTestAdapter.description%", "%python.experiments.pythonREPLSmartSend.description%", "%python.experiments.pythonRecommendTensorboardExt.description%", - "%python.experiments.pythonRunREPL.description%", - "%python.command.python.execInREPLEnter.title%" + "%python.experiments.pythonRunREPL.description%" ] }, "scope": "window", From 4523502c96182c6d89e9f5f36d794368ab482cdc Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Fri, 17 May 2024 00:39:17 -0700 Subject: [PATCH 04/33] change to TODO --- src/client/repl/replCommands.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/client/repl/replCommands.ts b/src/client/repl/replCommands.ts index 18cef03dee7e..9d749b3b490d 100644 --- a/src/client/repl/replCommands.ts +++ b/src/client/repl/replCommands.ts @@ -104,8 +104,8 @@ export async function registerReplCommands( ); } -// Register Python execute command for keybinding 'Enter' -// Conditionally call interactive.execute OR insert \n in text input box. +// TODO: Register Python execute command for keybinding 'Enter' +// TODO: Conditionally call interactive.execute OR insert \n in text input box. export async function registerReplExecuteOnEnter(disposables: Disposable[]): Promise { disposables.push( commands.registerCommand(Commands.Exec_In_REPL_Enter, async (uri: Uri) => { @@ -127,12 +127,11 @@ export async function registerReplExecuteOnEnter(disposables: Disposable[]): Pro }); } - // If user types enter on blank line, call interactive.execute + // 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'); } } - }), // This closing parenthesis was missing. + }), ); - // Handle case when user enters on blank line, just trigger interactive.execute } From 6defed9137df137299795186cdfa84b1ce8a5f84 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Fri, 17 May 2024 02:27:50 -0700 Subject: [PATCH 05/33] compile() arg 1 must be a string, bytes or AST object --- python_files/python_server.py | 14 ++++++++++ src/client/common/application/commands.ts | 1 + src/client/extensionActivation.ts | 2 +- src/client/repl/pythonServer.ts | 5 ++++ src/client/repl/replCommands.ts | 32 ++++++++++++++++++++--- 5 files changed, 50 insertions(+), 4 deletions(-) diff --git a/python_files/python_server.py b/python_files/python_server.py index 4d27a168bc4c..dcb52ed893f7 100644 --- a/python_files/python_server.py +++ b/python_files/python_server.py @@ -1,11 +1,14 @@ from typing import Dict, List, Optional, Union +import debugpy +debugpy.connect(5678) import sys import json import contextlib import io import traceback import uuid +import ast STDIN = sys.stdin STDOUT = sys.stdout @@ -88,6 +91,15 @@ def exec_function(user_input): return eval +def check_valid_command(request): + try: + user_input = request["params"] + ast.parse(user_input) + return True + except SyntaxError: + return False + + def execute(request, user_globals): str_output = CustomIO("", encoding="utf-8") str_error = CustomIO("", encoding="utf-8") @@ -160,6 +172,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/extensionActivation.ts b/src/client/extensionActivation.ts index ddebcac6e8a6..aac8376acbbb 100644 --- a/src/client/extensionActivation.ts +++ b/src/client/extensionActivation.ts @@ -117,7 +117,7 @@ export function activateFeatures(ext: ExtensionState, _components: Components): if (replExperimentValue) { registerReplCommands(ext.disposables, interpreterService); commands.executeCommand('setContext', 'pythonRunREPL', true); - registerReplExecuteOnEnter(ext.disposables); + registerReplExecuteOnEnter(ext.disposables, interpreterService); } } } diff --git a/src/client/repl/pythonServer.ts b/src/client/repl/pythonServer.ts index e25ba3a25092..e40a4d645a3c 100644 --- a/src/client/repl/pythonServer.ts +++ b/src/client/repl/pythonServer.ts @@ -11,6 +11,7 @@ export interface PythonServer extends Disposable { execute(code: string): Promise; interrupt(): void; input(): void; + checkValidCommand(code: string): Promise; } class PythonServerImpl implements Disposable { @@ -54,6 +55,10 @@ class PythonServerImpl implements Disposable { } } + public async checkValidCommand(code: string): Promise { + return this.connection.sendRequest('check_valid_command', code); + } + public dispose(): void { this.connection.sendNotification('exit'); this.connection.dispose(); diff --git a/src/client/repl/replCommands.ts b/src/client/repl/replCommands.ts index 9d749b3b490d..3dbe761a9a49 100644 --- a/src/client/repl/replCommands.ts +++ b/src/client/repl/replCommands.ts @@ -19,6 +19,7 @@ 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'; let notebookController: NotebookController | undefined; @@ -106,16 +107,41 @@ export async function registerReplCommands( // TODO: Register Python execute command for keybinding 'Enter' // TODO: Conditionally call interactive.execute OR insert \n in text input box. -export async function registerReplExecuteOnEnter(disposables: Disposable[]): Promise { +export async function registerReplExecuteOnEnter( + disposables: Disposable[], + interpreterService: IInterpreterService, +): Promise { disposables.push( commands.registerCommand(Commands.Exec_In_REPL_Enter, async (uri: Uri) => { - const completeCode = false; - const uri2 = 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); // NOT EVEN CALLING METHOD!!!!! + } + if (completeCode) { await commands.executeCommand('interactive.execute'); } else { // Insert new line on behalf of user. "Regular" monaco editor behavior const editor = window.activeTextEditor; + if (editor) { const position = editor.selection.active; // move cursor to end of line and also add newline character From ac7cc8cd93bf80ca98befc1bca08117901135239 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Fri, 17 May 2024 13:53:49 -0700 Subject: [PATCH 06/33] WORKING --PROPERLY CHECKING IF SOMETHING IS VALID AND COMPLETE --- python_files/python_server.py | 12 +++++++----- src/client/repl/pythonServer.ts | 19 ++++++++++++++----- src/client/repl/replCommands.ts | 5 ++++- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/python_files/python_server.py b/python_files/python_server.py index dcb52ed893f7..8d0a5c8f9f4c 100644 --- a/python_files/python_server.py +++ b/python_files/python_server.py @@ -1,7 +1,7 @@ from typing import Dict, List, Optional, Union -import debugpy +# import debugpy -debugpy.connect(5678) +# debugpy.connect(5678) import sys import json import contextlib @@ -94,10 +94,12 @@ def exec_function(user_input): def check_valid_command(request): try: user_input = request["params"] - ast.parse(user_input) - return True + ast.parse(user_input[0]) + send_response("True", request["id"]) + # return True except SyntaxError: - return False + send_response("False", request["id"]) + # return False def execute(request, user_globals): diff --git a/src/client/repl/pythonServer.ts b/src/client/repl/pythonServer.ts index e40a4d645a3c..4642ae65993c 100644 --- a/src/client/repl/pythonServer.ts +++ b/src/client/repl/pythonServer.ts @@ -1,3 +1,4 @@ +// eslint-disable-next-line max-classes-per-file import * as path from 'path'; import * as ch from 'child_process'; import * as rpc from 'vscode-jsonrpc/node'; @@ -6,12 +7,15 @@ import { EXTENSION_ROOT_DIR } from '../constants'; import { traceError, traceLog } from '../logging'; const SERVER_PATH = path.join(EXTENSION_ROOT_DIR, 'python_files', 'python_server.py'); - +// export let serverInstance: PythonServer | undefined; +export class pythonServerInstance { + public static serverInstance: PythonServer | undefined; +} export interface PythonServer extends Disposable { execute(code: string): Promise; interrupt(): void; input(): void; - checkValidCommand(code: string): Promise; + checkValidCommand(code: string): Promise; } class PythonServerImpl implements Disposable { @@ -55,7 +59,7 @@ class PythonServerImpl implements Disposable { } } - public async checkValidCommand(code: string): Promise { + public async checkValidCommand(code: string): Promise { return this.connection.sendRequest('check_valid_command', code); } @@ -66,6 +70,10 @@ class PythonServerImpl implements Disposable { } export function createPythonServer(interpreter: string[]): PythonServer { + if (pythonServerInstance.serverInstance) { + return pythonServerInstance.serverInstance; + } + const pythonServer = ch.spawn(interpreter[0], [...interpreter.slice(1), SERVER_PATH]); pythonServer.stderr.on('data', (data) => { @@ -81,6 +89,7 @@ export function createPythonServer(interpreter: string[]): PythonServer { new rpc.StreamMessageReader(pythonServer.stdout), new rpc.StreamMessageWriter(pythonServer.stdin), ); - - return new PythonServerImpl(connection, pythonServer); + const ourPythonServerImpl = new PythonServerImpl(connection, pythonServer); + pythonServerInstance.serverInstance = ourPythonServerImpl; + return ourPythonServerImpl; } diff --git a/src/client/repl/replCommands.ts b/src/client/repl/replCommands.ts index 3dbe761a9a49..5dc10867be0b 100644 --- a/src/client/repl/replCommands.ts +++ b/src/client/repl/replCommands.ts @@ -133,7 +133,10 @@ export async function registerReplExecuteOnEnter( // Check if userTextInput is a complete Python command if (userTextInput) { - completeCode = await pythonServer.checkValidCommand(userTextInput); // NOT EVEN CALLING METHOD!!!!! + const stringBoolean = await pythonServer.checkValidCommand(userTextInput); + if (stringBoolean === 'True') { + completeCode = true; + } } if (completeCode) { From 6389fdbcf657c86875101574c38e271786c8451f Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Mon, 20 May 2024 11:23:30 -0700 Subject: [PATCH 07/33] Try to handle interrupt on windows --- python_files/ctrlc.py | 12 ++++++++++++ src/client/repl/pythonServer.ts | 25 ++++++++++++++++++++++--- 2 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 python_files/ctrlc.py diff --git a/python_files/ctrlc.py b/python_files/ctrlc.py new file mode 100644 index 000000000000..a86788255355 --- /dev/null +++ b/python_files/ctrlc.py @@ -0,0 +1,12 @@ +# This file sends keyboard interrupt to Windows Python REPL instance +import ctypes +import sys + +kernel = ctypes.windll.kernel32 + +pid = int(sys.argv[1]) +kernel.FreeConsole() +kernel.AttachConsole(pid) +kernel.SetConsoleCtrlHandler(None, 1) +kernel.GenerateConsoleCtrlEvent(0, 0) +sys.exit(0) diff --git a/src/client/repl/pythonServer.ts b/src/client/repl/pythonServer.ts index 4642ae65993c..c59c791e9db3 100644 --- a/src/client/repl/pythonServer.ts +++ b/src/client/repl/pythonServer.ts @@ -19,7 +19,11 @@ export interface PythonServer extends Disposable { } class PythonServerImpl implements Disposable { - constructor(private connection: rpc.MessageConnection, private pythonServer: ch.ChildProcess) { + constructor( + private connection: rpc.MessageConnection, + private pythonServer: ch.ChildProcess, + private interpreter: string, + ) { this.initialize(); this.input(); } @@ -54,8 +58,23 @@ 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'); + } else { + // Handle interrupt for windows + // Run python_files/ctrlc.py with 12345 as argument + const ctrlc = ch.spawn(this.interpreter, [ + path.join(EXTENSION_ROOT_DIR, 'python_files', 'ctrlc.py'), + '12345', + ]); + ctrlc.on('exit', (code) => { + if (code === 0) { + traceLog('Windows Python REPL server interrupted successfully with exit code 0'); + } else { + traceLog('Windows Python REPL interrupt may have failed'); + } + }); } } @@ -89,7 +108,7 @@ export function createPythonServer(interpreter: string[]): PythonServer { new rpc.StreamMessageReader(pythonServer.stdout), new rpc.StreamMessageWriter(pythonServer.stdin), ); - const ourPythonServerImpl = new PythonServerImpl(connection, pythonServer); + const ourPythonServerImpl = new PythonServerImpl(connection, pythonServer, interpreter[0]); pythonServerInstance.serverInstance = ourPythonServerImpl; return ourPythonServerImpl; } From e3b974cf72be9febcb7421eae950e4b6e1a7c713 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Mon, 20 May 2024 11:50:22 -0700 Subject: [PATCH 08/33] hide execute on enter from command palette --- package.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/package.json b/package.json index 74534df11fd3..ccb529dc24f9 100644 --- a/package.json +++ b/package.json @@ -1279,6 +1279,12 @@ "title": "%python.command.python.execInREPL.title%", "when": "false" }, + { + "category": "Python", + "command": "python.execInREPLEnter", + "title": "%python.command.python.execInREPLEnter.title%", + "when": "false" + }, { "category": "Python", "command": "python.launchTensorBoard", From 303967040c5ffd0b25c76d2a7a14d74005c607d4 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Mon, 20 May 2024 13:06:04 -0700 Subject: [PATCH 09/33] add execInREPLEnter to package.nls.json --- package.nls.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.nls.json b/package.nls.json index aa84f31a91b2..e25bb8b78c51 100644 --- a/package.nls.json +++ b/package.nls.json @@ -15,6 +15,7 @@ "python.command.testing.rerunFailedTests.title": "Rerun Failed Tests", "python.command.python.execSelectionInTerminal.title": "Run Selection/Line in Python Terminal", "python.command.python.execInREPL.title": "Run Selection/Line in Python REPL", + "python.command.python.execInREPLEnter": "Execute on REPL Enter", "python.command.python.execSelectionInDjangoShell.title": "Run Selection/Line in Django Shell", "python.command.python.reportIssue.title": "Report Issue...", "python.command.python.enableSourceMapSupport.title": "Enable Source Map Support For Extension Debugging", From e6034b7e904b4df643b40e6d429dbf7ca9b0fd4d Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Mon, 20 May 2024 17:48:54 -0700 Subject: [PATCH 10/33] temp save interactive.open and multi-line --- src/client/repl/replCommands.ts | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/client/repl/replCommands.ts b/src/client/repl/replCommands.ts index 5dc10867be0b..c39e9b48b11f 100644 --- a/src/client/repl/replCommands.ts +++ b/src/client/repl/replCommands.ts @@ -72,10 +72,19 @@ export async function registerReplCommands( // We want to keep notebookEditor, whenever we want to run. // Find interactive window, or open it. + let res; if (!notebookEditor) { - notebookEditor = await window.showNotebookDocument(notebookDocument, { - viewColumn: ViewColumn.Beside, - }); + // notebookEditor = await window.showNotebookDocument(notebookDocument, { + // viewColumn: ViewColumn.Beside, + // }); comment out to try _interactive.open + res = (await commands.executeCommand('interactive.open', { + ViewColumn: ViewColumn.Beside, + preserveFocus: true, + undefined, + controllerId: notebookController.id, + title: 'Python REPL', + })) as { notebookEditor: NotebookEditor }; + notebookEditor = res.notebookEditor; } notebookController!.updateNotebookAffinity(notebookDocument, NotebookControllerAffinity.Default); @@ -138,12 +147,12 @@ export async function registerReplExecuteOnEnter( completeCode = true; } } - - if (completeCode) { + 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 - const editor = window.activeTextEditor; if (editor) { const position = editor.selection.active; @@ -164,3 +173,11 @@ export async function registerReplExecuteOnEnter( }), ); } + +function isMultiLineText(textEditor: TextEditor | undefined): boolean { + if (textEditor) { + const { document } = textEditor; + return document.lineCount > 1; + } + return false; +} From ab490dd0b6a52f838ce7f66d20738ab199167626 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 21 May 2024 10:46:17 -0700 Subject: [PATCH 11/33] temp save --> sending to IW, intellisense pt2 --- src/client/repl/replCommands.ts | 34 ++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/src/client/repl/replCommands.ts b/src/client/repl/replCommands.ts index c39e9b48b11f..e476c0f3bf2d 100644 --- a/src/client/repl/replCommands.ts +++ b/src/client/repl/replCommands.ts @@ -13,6 +13,7 @@ import { NotebookEditor, TextEditor, Selection, + NotebookDocument, } from 'vscode'; import { Disposable } from 'vscode-jsonrpc'; import { Commands, PVSC_EXTENSION_ID } from '../common/constants'; @@ -25,6 +26,7 @@ import { createReplController } from './replController'; 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) { @@ -65,29 +67,34 @@ export async function registerReplCommands( const activeEditor = window.activeTextEditor as TextEditor; const code = await getSelectedTextToExecute(activeEditor); - const ourResource = Uri.from({ scheme: 'untitled', path: 'repl.interactive' }); + // const ourResource = Uri.from({ scheme: 'untitled', path: 'repl.interactive' }); - const notebookDocument = await workspace.openNotebookDocument(ourResource); + // const notebookDocument = await workspace.openNotebookDocument(ourResource); // before using interactive.open // 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. let res; + if (!notebookEditor) { // notebookEditor = await window.showNotebookDocument(notebookDocument, { // viewColumn: ViewColumn.Beside, - // }); comment out to try _interactive.open - res = (await commands.executeCommand('interactive.open', { - ViewColumn: ViewColumn.Beside, - preserveFocus: true, + // }); // comment out to try _interactive.open + res = (await commands.executeCommand( + 'interactive.open', + { + ViewColumn: ViewColumn.Beside, + preserveFocus: false, + }, undefined, - controllerId: notebookController.id, - title: 'Python REPL', - })) as { notebookEditor: NotebookEditor }; + notebookController.id, + 'Python REPL', + )) as { notebookEditor: NotebookEditor }; notebookEditor = res.notebookEditor; + notebookDocument = res.notebookEditor.notebook; } - notebookController!.updateNotebookAffinity(notebookDocument, NotebookControllerAffinity.Default); + notebookController!.updateNotebookAffinity(notebookDocument!, NotebookControllerAffinity.Default); // Auto-Select Python REPL Kernel await commands.executeCommand('notebook.selectKernel', { @@ -97,17 +104,18 @@ export async function registerReplCommands( }); const notebookCellData = new NotebookCellData(NotebookCellKind.Code, code as string, 'python'); - const { cellCount } = notebookDocument; + 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]); + 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, + // document: ourResource, + document: notebookDocument!.uri, }); } }), From e25ec455143c8054acada3d4d3c95087299d4213 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 21 May 2024 13:44:15 -0700 Subject: [PATCH 12/33] refactoring, adding disposables --- python_files/ctrlc.py | 4 +++- src/client/repl/pythonServer.ts | 26 +++++++++++++++----------- src/client/repl/replCommands.ts | 8 ++++++-- src/client/repl/replController.ts | 8 +++++++- 4 files changed, 31 insertions(+), 15 deletions(-) diff --git a/python_files/ctrlc.py b/python_files/ctrlc.py index a86788255355..b5571f546d7e 100644 --- a/python_files/ctrlc.py +++ b/python_files/ctrlc.py @@ -6,7 +6,9 @@ pid = int(sys.argv[1]) kernel.FreeConsole() -kernel.AttachConsole(pid) +kernel.AttachConsole( + pid +) # TODO: can we attach second time in windows? # Run something, interrupt, run another thing, interrupt kernel.SetConsoleCtrlHandler(None, 1) kernel.GenerateConsoleCtrlEvent(0, 0) sys.exit(0) diff --git a/src/client/repl/pythonServer.ts b/src/client/repl/pythonServer.ts index c59c791e9db3..1f0fb94e8d6a 100644 --- a/src/client/repl/pythonServer.ts +++ b/src/client/repl/pythonServer.ts @@ -7,10 +7,8 @@ import { EXTENSION_ROOT_DIR } from '../constants'; import { traceError, traceLog } from '../logging'; const SERVER_PATH = path.join(EXTENSION_ROOT_DIR, 'python_files', 'python_server.py'); -// export let serverInstance: PythonServer | undefined; -export class pythonServerInstance { - public static serverInstance: PythonServer | undefined; -} +let serverInstance: PythonServer | undefined; + export interface PythonServer extends Disposable { execute(code: string): Promise; interrupt(): void; @@ -19,6 +17,8 @@ export interface PythonServer extends Disposable { } class PythonServerImpl implements Disposable { + private readonly disposables: Disposable[] = []; + constructor( private connection: rpc.MessageConnection, private pythonServer: ch.ChildProcess, @@ -29,9 +29,11 @@ class PythonServerImpl implements Disposable { } 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(); } @@ -62,8 +64,9 @@ class PythonServerImpl implements Disposable { if (this.pythonServer.kill('SIGINT')) { traceLog('Python REPL server interrupted'); } else { - // Handle interrupt for windows + // TODO: Handle interrupt for windows // Run python_files/ctrlc.py with 12345 as argument + // TODO: properly get PID from Python Server const ctrlc = ch.spawn(this.interpreter, [ path.join(EXTENSION_ROOT_DIR, 'python_files', 'ctrlc.py'), '12345', @@ -84,13 +87,14 @@ class PythonServerImpl implements Disposable { public dispose(): void { this.connection.sendNotification('exit'); + this.disposables.forEach((d) => d.dispose()); this.connection.dispose(); } } export function createPythonServer(interpreter: string[]): PythonServer { - if (pythonServerInstance.serverInstance) { - return pythonServerInstance.serverInstance; + if (serverInstance) { + return serverInstance; } const pythonServer = ch.spawn(interpreter[0], [...interpreter.slice(1), SERVER_PATH]); @@ -109,6 +113,6 @@ export function createPythonServer(interpreter: string[]): PythonServer { new rpc.StreamMessageWriter(pythonServer.stdin), ); const ourPythonServerImpl = new PythonServerImpl(connection, pythonServer, interpreter[0]); - pythonServerInstance.serverInstance = ourPythonServerImpl; + serverInstance = ourPythonServerImpl; return ourPythonServerImpl; } diff --git a/src/client/repl/replCommands.ts b/src/client/repl/replCommands.ts index e476c0f3bf2d..9b1b0dd083a3 100644 --- a/src/client/repl/replCommands.ts +++ b/src/client/repl/replCommands.ts @@ -62,7 +62,7 @@ export async function registerReplCommands( const interpreterPath = interpreter.path; if (!notebookController) { - notebookController = createReplController(interpreterPath); + notebookController = createReplController(interpreterPath, disposables); } const activeEditor = window.activeTextEditor as TextEditor; @@ -83,8 +83,8 @@ export async function registerReplCommands( res = (await commands.executeCommand( 'interactive.open', { + preserveFocus: true, ViewColumn: ViewColumn.Beside, - preserveFocus: false, }, undefined, notebookController.id, @@ -92,6 +92,10 @@ export async function registerReplCommands( )) as { notebookEditor: NotebookEditor }; notebookEditor = res.notebookEditor; notebookDocument = res.notebookEditor.notebook; + + // await window.showNotebookDocument(notebookDocument!, { + // viewColumn: ViewColumn.Beside, + // }); correctly open IW on the side. } notebookController!.updateNotebookAffinity(notebookDocument!, NotebookControllerAffinity.Default); 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; } From 58b7772717d2a7c44c1242a04c6e102b5785d5b0 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 21 May 2024 14:49:36 -0700 Subject: [PATCH 13/33] cleanup --- src/client/repl/pythonServer.ts | 2 -- src/client/repl/replCommands.ts | 20 +++----------------- 2 files changed, 3 insertions(+), 19 deletions(-) diff --git a/src/client/repl/pythonServer.ts b/src/client/repl/pythonServer.ts index 1f0fb94e8d6a..20d70d1dc490 100644 --- a/src/client/repl/pythonServer.ts +++ b/src/client/repl/pythonServer.ts @@ -37,7 +37,6 @@ class PythonServerImpl implements Disposable { this.connection.listen(); } - // Register input handler public input(): void { // Register input request handler this.connection.onRequest('input', async (request) => { @@ -65,7 +64,6 @@ class PythonServerImpl implements Disposable { traceLog('Python REPL server interrupted'); } else { // TODO: Handle interrupt for windows - // Run python_files/ctrlc.py with 12345 as argument // TODO: properly get PID from Python Server const ctrlc = ch.spawn(this.interpreter, [ path.join(EXTENSION_ROOT_DIR, 'python_files', 'ctrlc.py'), diff --git a/src/client/repl/replCommands.ts b/src/client/repl/replCommands.ts index 9b1b0dd083a3..b76b499d9d4b 100644 --- a/src/client/repl/replCommands.ts +++ b/src/client/repl/replCommands.ts @@ -67,24 +67,17 @@ export async function registerReplCommands( 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); // before using interactive.open - // 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. let res; if (!notebookEditor) { - // notebookEditor = await window.showNotebookDocument(notebookDocument, { - // viewColumn: ViewColumn.Beside, - // }); // comment out to try _interactive.open res = (await commands.executeCommand( 'interactive.open', { preserveFocus: true, - ViewColumn: ViewColumn.Beside, + viewColumn: ViewColumn.Beside, }, undefined, notebookController.id, @@ -92,10 +85,6 @@ export async function registerReplCommands( )) as { notebookEditor: NotebookEditor }; notebookEditor = res.notebookEditor; notebookDocument = res.notebookEditor.notebook; - - // await window.showNotebookDocument(notebookDocument!, { - // viewColumn: ViewColumn.Beside, - // }); correctly open IW on the side. } notebookController!.updateNotebookAffinity(notebookDocument!, NotebookControllerAffinity.Default); @@ -126,8 +115,7 @@ export async function registerReplCommands( ); } -// TODO: Register Python execute command for keybinding 'Enter' -// TODO: Conditionally call interactive.execute OR insert \n in text input box. +// Command for 'Enter': Conditionally call interactive.execute OR insert \n in text input box. export async function registerReplExecuteOnEnter( disposables: Disposable[], interpreterService: IInterpreterService, @@ -165,13 +153,11 @@ export async function registerReplExecuteOnEnter( await commands.executeCommand('interactive.execute'); } else { // Insert new line on behalf of user. "Regular" monaco editor behavior - if (editor) { const position = editor.selection.active; - // move cursor to end of line and also add newline character const newPosition = position.with(position.line, editor.document.lineAt(position.line).text.length); editor.selection = new Selection(newPosition, newPosition); - // add newline character + editor.edit((editBuilder) => { editBuilder.insert(newPosition, '\n'); }); From 931f3bf13bcb85df0d36c72ed812b2c2d4cac199 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 21 May 2024 14:55:56 -0700 Subject: [PATCH 14/33] remove handling Windows interrupt for now --- python_files/ctrlc.py | 14 -------------- src/client/repl/pythonServer.ts | 22 ++-------------------- 2 files changed, 2 insertions(+), 34 deletions(-) delete mode 100644 python_files/ctrlc.py diff --git a/python_files/ctrlc.py b/python_files/ctrlc.py deleted file mode 100644 index b5571f546d7e..000000000000 --- a/python_files/ctrlc.py +++ /dev/null @@ -1,14 +0,0 @@ -# This file sends keyboard interrupt to Windows Python REPL instance -import ctypes -import sys - -kernel = ctypes.windll.kernel32 - -pid = int(sys.argv[1]) -kernel.FreeConsole() -kernel.AttachConsole( - pid -) # TODO: can we attach second time in windows? # Run something, interrupt, run another thing, interrupt -kernel.SetConsoleCtrlHandler(None, 1) -kernel.GenerateConsoleCtrlEvent(0, 0) -sys.exit(0) diff --git a/src/client/repl/pythonServer.ts b/src/client/repl/pythonServer.ts index 20d70d1dc490..783d933e2fbe 100644 --- a/src/client/repl/pythonServer.ts +++ b/src/client/repl/pythonServer.ts @@ -19,11 +19,7 @@ export interface PythonServer extends Disposable { class PythonServerImpl implements Disposable { private readonly disposables: Disposable[] = []; - constructor( - private connection: rpc.MessageConnection, - private pythonServer: ch.ChildProcess, - private interpreter: string, - ) { + constructor(private connection: rpc.MessageConnection, private pythonServer: ch.ChildProcess) { this.initialize(); this.input(); } @@ -62,20 +58,6 @@ class PythonServerImpl implements Disposable { // Passing SIGINT to interrupt only would work for Mac and Linux if (this.pythonServer.kill('SIGINT')) { traceLog('Python REPL server interrupted'); - } else { - // TODO: Handle interrupt for windows - // TODO: properly get PID from Python Server - const ctrlc = ch.spawn(this.interpreter, [ - path.join(EXTENSION_ROOT_DIR, 'python_files', 'ctrlc.py'), - '12345', - ]); - ctrlc.on('exit', (code) => { - if (code === 0) { - traceLog('Windows Python REPL server interrupted successfully with exit code 0'); - } else { - traceLog('Windows Python REPL interrupt may have failed'); - } - }); } } @@ -110,7 +92,7 @@ export function createPythonServer(interpreter: string[]): PythonServer { new rpc.StreamMessageReader(pythonServer.stdout), new rpc.StreamMessageWriter(pythonServer.stdin), ); - const ourPythonServerImpl = new PythonServerImpl(connection, pythonServer, interpreter[0]); + const ourPythonServerImpl = new PythonServerImpl(connection, pythonServer); serverInstance = ourPythonServerImpl; return ourPythonServerImpl; } From f1dd8a04b78727ed68b018357ceb83142af9fdf0 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 21 May 2024 14:57:54 -0700 Subject: [PATCH 15/33] more cleanup --- python_files/python_server.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/python_files/python_server.py b/python_files/python_server.py index 8d0a5c8f9f4c..30be834631c6 100644 --- a/python_files/python_server.py +++ b/python_files/python_server.py @@ -1,7 +1,4 @@ from typing import Dict, List, Optional, Union -# import debugpy - -# debugpy.connect(5678) import sys import json import contextlib @@ -96,10 +93,8 @@ def check_valid_command(request): user_input = request["params"] ast.parse(user_input[0]) send_response("True", request["id"]) - # return True except SyntaxError: send_response("False", request["id"]) - # return False def execute(request, user_globals): From 55cad61b13ed13fe4485fdb5d84e5e73d1c065dd Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 21 May 2024 15:09:31 -0700 Subject: [PATCH 16/33] rename variable --- src/client/repl/replCommands.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/client/repl/replCommands.ts b/src/client/repl/replCommands.ts index b76b499d9d4b..19e785b143e1 100644 --- a/src/client/repl/replCommands.ts +++ b/src/client/repl/replCommands.ts @@ -70,10 +70,10 @@ export async function registerReplCommands( // We want to keep notebookEditor, whenever we want to run. // Find interactive window, or open it. - let res; + let interactiveWindowObject; if (!notebookEditor) { - res = (await commands.executeCommand( + interactiveWindowObject = (await commands.executeCommand( 'interactive.open', { preserveFocus: true, @@ -83,8 +83,8 @@ export async function registerReplCommands( notebookController.id, 'Python REPL', )) as { notebookEditor: NotebookEditor }; - notebookEditor = res.notebookEditor; - notebookDocument = res.notebookEditor.notebook; + notebookEditor = interactiveWindowObject.notebookEditor; + notebookDocument = interactiveWindowObject.notebookEditor.notebook; } notebookController!.updateNotebookAffinity(notebookDocument!, NotebookControllerAffinity.Default); From 059334926785cf748eabc7797daf2d61dab9448e Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 21 May 2024 19:30:55 -0700 Subject: [PATCH 17/33] shift enter sends to REPL or Terminal depending on setting --- package.json | 13 ++++++++++++- package.nls.json | 1 + src/client/common/types.ts | 2 +- src/client/extensionActivation.ts | 21 +++++++++++++++++---- src/client/repl/replCommands.ts | 18 ++++++++++++++++++ 5 files changed, 49 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index ccb529dc24f9..ad4bca7876e4 100644 --- a/package.json +++ b/package.json @@ -633,6 +633,12 @@ "scope": "resource", "type": "boolean" }, + "python.REPL.sendToNativeREPL": { + "default": false, + "description": "%python.sendToNativeREPL.description%", + "scope": "resource", + "type": "boolean" + }, "python.testing.autoTestDiscoverOnSaveEnabled": { "default": true, "description": "%python.testing.autoTestDiscoverOnSaveEnabled.description%", @@ -1113,7 +1119,12 @@ { "command": "python.execSelectionInTerminal", "key": "shift+enter", - "when": "editorTextFocus && editorLangId == python && !findInputFocussed && !replaceInputFocussed && !jupyter.ownsSelection && !notebookEditorFocused && activeEditor != 'workbench.editor.interactive'" + "when": "!pythonRunREPL && editorTextFocus && editorLangId == python && !findInputFocussed && !replaceInputFocussed && !jupyter.ownsSelection && !notebookEditorFocused && activeEditor != 'workbench.editor.interactive'" + }, + { + "command": "python.execInREPL", + "key": "shift+enter", + "when": "pythonRunREPL" }, { "command": "python.execInREPLEnter", diff --git a/package.nls.json b/package.nls.json index e25bb8b78c51..1111b417cc7b 100644 --- a/package.nls.json +++ b/package.nls.json @@ -64,6 +64,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.sendToNativeREPL.description": "Toggle to send commands to IW Python REPL instead of the terminal.", "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/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/extensionActivation.ts b/src/client/extensionActivation.ts index aac8376acbbb..f2fdeb0dd9b4 100644 --- a/src/client/extensionActivation.ts +++ b/src/client/extensionActivation.ts @@ -9,7 +9,12 @@ import { registerTypes as activationRegisterTypes } from './activation/serviceRe import { IExtensionActivationManager } from './activation/types'; import { registerTypes as appRegisterTypes } from './application/serviceRegistry'; import { IApplicationDiagnostics } from './application/types'; -import { IApplicationEnvironment, ICommandManager, IWorkspaceService } from './common/application/types'; +import { + IActiveResourceService, + IApplicationEnvironment, + ICommandManager, + IWorkspaceService, +} from './common/application/types'; import { Commands, PYTHON_LANGUAGE, UseProposedApi } from './common/constants'; import { registerTypes as installerRegisterTypes } from './common/installer/serviceRegistry'; import { IFileSystem } from './common/platform/types'; @@ -111,12 +116,20 @@ export function activateFeatures(ext: ExtensionState, _components: Components): // Register native REPL context menu when in experiment const experimentService = ext.legacyIOC.serviceContainer.get(IExperimentService); - commands.executeCommand('setContext', 'pythonRunREPL', false); + const configurationService = ext.legacyIOC.serviceContainer.get(IConfigurationService); + const activeResourceService = ext.legacyIOC.serviceContainer.get(IActiveResourceService); + // commands.executeCommand('setContext', 'pythonRunREPL', false); if (experimentService) { const replExperimentValue = experimentService.inExperimentSync(EnableRunREPL.experiment); + commands.executeCommand('setContext', 'pythonRunREPL', replExperimentValue); + // If user is in pythonRunREPL experiment, we enable the sendToNativeREPL setting to True by default. if (replExperimentValue) { - registerReplCommands(ext.disposables, interpreterService); - commands.executeCommand('setContext', 'pythonRunREPL', true); + configurationService.updateSetting( + 'REPL.sendToNativeREPL', + true, + activeResourceService.getActiveResource(), + ); + registerReplCommands(ext.disposables, interpreterService, configurationService, activeResourceService); registerReplExecuteOnEnter(ext.disposables, interpreterService); } } diff --git a/src/client/repl/replCommands.ts b/src/client/repl/replCommands.ts index 19e785b143e1..f5b5bfd93dc6 100644 --- a/src/client/repl/replCommands.ts +++ b/src/client/repl/replCommands.ts @@ -16,7 +16,9 @@ import { NotebookDocument, } from 'vscode'; import { Disposable } from 'vscode-jsonrpc'; +import { IActiveResourceService } from '../common/application/types'; import { Commands, PVSC_EXTENSION_ID } from '../common/constants'; +import { IConfigurationService } from '../common/types'; import { noop } from '../common/utils/misc'; import { IInterpreterService } from '../interpreter/contracts'; import { getMultiLineSelectionText, getSingleLineSelectionText } from '../terminals/codeExecution/helper'; @@ -47,12 +49,28 @@ async function getSelectedTextToExecute(textEditor: TextEditor): Promise { disposables.push( commands.registerCommand(Commands.Exec_In_REPL, async (uri: Uri) => { + let nativeREPLSetting = false; + + // Get REPL setting value from user settings + if (configurationService) { + const pythonSettings = configurationService.getSettings(activeResourceService.getActiveResource()); + nativeREPLSetting = pythonSettings.REPL.sendToNativeREPL; + // 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); From dbf48e66577d6338d77845a5f1df793f2be7e3ad Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 21 May 2024 19:55:22 -0700 Subject: [PATCH 18/33] hide context menu depending on setting --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index ad4bca7876e4..184d4458e077 100644 --- a/package.json +++ b/package.json @@ -1394,12 +1394,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 && pythonRunREPL && config.python.REPL.sendToNativeREPL" } ], "editor/title": [ From cfed9d7effe866056ea0219dad9e849d638de9d3 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 22 May 2024 13:54:18 -0700 Subject: [PATCH 19/33] fixing test --- src/test/terminals/codeExecution/helper.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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())) From 76d61649442e99c5165b05f3e068bc1f2731dad2 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 22 May 2024 13:55:55 -0700 Subject: [PATCH 20/33] fix more test --- src/test/terminals/codeExecution/smartSend.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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); From c0bc3b8210cd9ffb17ebb0cf329fde626d172aa5 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 22 May 2024 16:46:32 -0700 Subject: [PATCH 21/33] remove execInREPLEnter from package.json and nls.json --- package.json | 11 ----------- package.nls.json | 1 - 2 files changed, 12 deletions(-) diff --git a/package.json b/package.json index 184d4458e077..668fe7b64a55 100644 --- a/package.json +++ b/package.json @@ -311,11 +311,6 @@ "command": "python.execInREPL", "title": "%python.command.python.execInREPL.title%" }, - { - "category": "Python", - "command": "python.execInREPLEnter", - "title": "%python.command.python.execInREPLEnter.title%" - }, { "category": "Python", "command": "python.launchTensorBoard", @@ -1290,12 +1285,6 @@ "title": "%python.command.python.execInREPL.title%", "when": "false" }, - { - "category": "Python", - "command": "python.execInREPLEnter", - "title": "%python.command.python.execInREPLEnter.title%", - "when": "false" - }, { "category": "Python", "command": "python.launchTensorBoard", diff --git a/package.nls.json b/package.nls.json index 1111b417cc7b..d39228bb4df6 100644 --- a/package.nls.json +++ b/package.nls.json @@ -15,7 +15,6 @@ "python.command.testing.rerunFailedTests.title": "Rerun Failed Tests", "python.command.python.execSelectionInTerminal.title": "Run Selection/Line in Python Terminal", "python.command.python.execInREPL.title": "Run Selection/Line in Python REPL", - "python.command.python.execInREPLEnter": "Execute on REPL Enter", "python.command.python.execSelectionInDjangoShell.title": "Run Selection/Line in Django Shell", "python.command.python.reportIssue.title": "Report Issue...", "python.command.python.enableSourceMapSupport.title": "Enable Source Map Support For Extension Debugging", From f5126701e640d17d5954336da4c930015a24103c Mon Sep 17 00:00:00 2001 From: Anthony Kim <62267334+anthonykim1@users.noreply.github.com> Date: Wed, 22 May 2024 17:18:56 -0700 Subject: [PATCH 22/33] Update package.nls.json Co-authored-by: Courtney Webster <60238438+cwebster-99@users.noreply.github.com> --- package.nls.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.nls.json b/package.nls.json index d39228bb4df6..2b98af1e3162 100644 --- a/package.nls.json +++ b/package.nls.json @@ -63,7 +63,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.sendToNativeREPL.description": "Toggle to send commands to IW Python REPL instead of the terminal.", + "python.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`.", From 1b2c451118409c28986e7c0f71f1d156a9110ad9 Mon Sep 17 00:00:00 2001 From: Anthony Kim <62267334+anthonykim1@users.noreply.github.com> Date: Wed, 22 May 2024 18:55:35 -0700 Subject: [PATCH 23/33] match setting python.sendToNativeREPL.description Co-authored-by: Karthik Nadig --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 668fe7b64a55..b60a07994870 100644 --- a/package.json +++ b/package.json @@ -630,7 +630,7 @@ }, "python.REPL.sendToNativeREPL": { "default": false, - "description": "%python.sendToNativeREPL.description%", + "description": "%python.REPL.sendToNativeREPL.description%", "scope": "resource", "type": "boolean" }, From d369f11b09bffb93d65cad6e05629f9d30227ddb Mon Sep 17 00:00:00 2001 From: Anthony Kim <62267334+anthonykim1@users.noreply.github.com> Date: Wed, 22 May 2024 18:55:57 -0700 Subject: [PATCH 24/33] Update package.nls.json Co-authored-by: Karthik Nadig --- package.nls.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.nls.json b/package.nls.json index 2b98af1e3162..6256da62697a 100644 --- a/package.nls.json +++ b/package.nls.json @@ -63,7 +63,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.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.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`.", From 8b27c2f76d3837b020488701770cf0de25c8bdcb Mon Sep 17 00:00:00 2001 From: Anthony Kim <62267334+anthonykim1@users.noreply.github.com> Date: Wed, 22 May 2024 23:01:19 -0700 Subject: [PATCH 25/33] Update src/client/repl/pythonServer.ts Co-authored-by: Karthik Nadig --- src/client/repl/pythonServer.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/client/repl/pythonServer.ts b/src/client/repl/pythonServer.ts index 783d933e2fbe..fae33b1fbfcb 100644 --- a/src/client/repl/pythonServer.ts +++ b/src/client/repl/pythonServer.ts @@ -92,7 +92,6 @@ export function createPythonServer(interpreter: string[]): PythonServer { new rpc.StreamMessageReader(pythonServer.stdout), new rpc.StreamMessageWriter(pythonServer.stdin), ); - const ourPythonServerImpl = new PythonServerImpl(connection, pythonServer); - serverInstance = ourPythonServerImpl; - return ourPythonServerImpl; + serverInstance = new PythonServerImpl(connection, pythonServer); + return serverInstance; } From a745cb18430a5262d9046a25c53d0ba536c889d1 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 22 May 2024 23:21:52 -0700 Subject: [PATCH 26/33] switch checkValidCommand to return boolean --- src/client/repl/pythonServer.ts | 11 +++++++---- src/client/repl/replCommands.ts | 11 ++++++----- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/client/repl/pythonServer.ts b/src/client/repl/pythonServer.ts index fae33b1fbfcb..766fcf4cc92c 100644 --- a/src/client/repl/pythonServer.ts +++ b/src/client/repl/pythonServer.ts @@ -1,4 +1,3 @@ -// eslint-disable-next-line max-classes-per-file import * as path from 'path'; import * as ch from 'child_process'; import * as rpc from 'vscode-jsonrpc/node'; @@ -13,7 +12,7 @@ export interface PythonServer extends Disposable { execute(code: string): Promise; interrupt(): void; input(): void; - checkValidCommand(code: string): Promise; + checkValidCommand(code: string): Promise; } class PythonServerImpl implements Disposable { @@ -61,8 +60,12 @@ class PythonServerImpl implements Disposable { } } - public async checkValidCommand(code: string): Promise { - return this.connection.sendRequest('check_valid_command', code); + 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 { diff --git a/src/client/repl/replCommands.ts b/src/client/repl/replCommands.ts index f5b5bfd93dc6..20f0adcb6b01 100644 --- a/src/client/repl/replCommands.ts +++ b/src/client/repl/replCommands.ts @@ -133,7 +133,11 @@ export async function registerReplCommands( ); } -// Command for 'Enter': Conditionally call interactive.execute OR insert \n in text input box. +/** + * 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, @@ -160,10 +164,7 @@ export async function registerReplExecuteOnEnter( // Check if userTextInput is a complete Python command if (userTextInput) { - const stringBoolean = await pythonServer.checkValidCommand(userTextInput); - if (stringBoolean === 'True') { - completeCode = true; - } + completeCode = await pythonServer.checkValidCommand(userTextInput); } const editor = window.activeTextEditor; // Execute right away when complete code and Not multi-line From 393d2fda0093889b5a98d99a64607f6cb7d3f648 Mon Sep 17 00:00:00 2001 From: Anthony Kim <62267334+anthonykim1@users.noreply.github.com> Date: Wed, 22 May 2024 23:29:49 -0700 Subject: [PATCH 27/33] Update src/client/repl/replCommands.ts Co-authored-by: Karthik Nadig --- src/client/repl/replCommands.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/client/repl/replCommands.ts b/src/client/repl/replCommands.ts index 20f0adcb6b01..af8bd86d3b38 100644 --- a/src/client/repl/replCommands.ts +++ b/src/client/repl/replCommands.ts @@ -192,9 +192,5 @@ export async function registerReplExecuteOnEnter( } function isMultiLineText(textEditor: TextEditor | undefined): boolean { - if (textEditor) { - const { document } = textEditor; - return document.lineCount > 1; - } - return false; + return (textEditor?.document?.lineCount ?? 0) > 1; } From 0cffa5afdf6f8a906c3f76d331ca8bb5de5b1acc Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 22 May 2024 23:48:12 -0700 Subject: [PATCH 28/33] Stop using var! --- src/client/repl/replCommands.ts | 50 +++++++++++++++++---------------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/src/client/repl/replCommands.ts b/src/client/repl/replCommands.ts index af8bd86d3b38..9232c2a45512 100644 --- a/src/client/repl/replCommands.ts +++ b/src/client/repl/replCommands.ts @@ -105,29 +105,31 @@ export async function registerReplCommands( notebookDocument = interactiveWindowObject.notebookEditor.notebook; } - 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, - document: notebookDocument!.uri, - }); + 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 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, + document: notebookDocument.uri, + }); + } } }), ); @@ -151,7 +153,7 @@ export async function registerReplExecuteOnEnter( } // Create Separate Python server to check valid command - const pythonServer = createPythonServer([interpreter!.path! as string]); + const pythonServer = createPythonServer([interpreter.path as string]); const activeEditor = window.activeTextEditor; let userTextInput; From a6036cd5de7d77fc97907033d05557b38451f8bc Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Thu, 23 May 2024 11:11:06 -0700 Subject: [PATCH 29/33] delete stale experiment, properly get setting --- package.json | 23 ++++++++-------- package.nls.json | 1 - src/client/common/experiments/groups.ts | 5 ---- src/client/common/vscodeApis/windowApis.ts | 11 ++++++++ src/client/extensionActivation.ts | 32 +++------------------- src/client/repl/replCommands.ts | 29 +++++++++----------- 6 files changed, 39 insertions(+), 62 deletions(-) diff --git a/package.json b/package.json index b60a07994870..0c4cc49545bc 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", @@ -632,7 +628,10 @@ "default": false, "description": "%python.REPL.sendToNativeREPL.description%", "scope": "resource", - "type": "boolean" + "type": "boolean", + "tags": [ + "experimental" + ] }, "python.testing.autoTestDiscoverOnSaveEnabled": { "default": true, @@ -1114,12 +1113,12 @@ { "command": "python.execSelectionInTerminal", "key": "shift+enter", - "when": "!pythonRunREPL && 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": "pythonRunREPL" + "when": "config.python.REPL.sendToNativeREPL" }, { "command": "python.execInREPLEnter", @@ -1388,7 +1387,7 @@ { "command": "python.execInREPL", "group": "Python", - "when": "editorFocus && editorLangId == python && !virtualWorkspace && shellExecutionSupported && pythonRunREPL && config.python.REPL.sendToNativeREPL" + "when": "editorFocus && editorLangId == python && !virtualWorkspace && shellExecutionSupported && config.python.REPL.sendToNativeREPL" } ], "editor/title": [ diff --git a/package.nls.json b/package.nls.json index 6256da62697a..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.", 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/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 f2fdeb0dd9b4..1c7e8f384ff1 100644 --- a/src/client/extensionActivation.ts +++ b/src/client/extensionActivation.ts @@ -3,25 +3,19 @@ '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'; import { registerTypes as appRegisterTypes } from './application/serviceRegistry'; import { IApplicationDiagnostics } from './application/types'; -import { - IActiveResourceService, - IApplicationEnvironment, - ICommandManager, - IWorkspaceService, -} from './common/application/types'; +import { IApplicationEnvironment, ICommandManager, IWorkspaceService } from './common/application/types'; import { Commands, PYTHON_LANGUAGE, UseProposedApi } from './common/constants'; import { registerTypes as installerRegisterTypes } from './common/installer/serviceRegistry'; import { IFileSystem } from './common/platform/types'; import { IConfigurationService, IDisposableRegistry, - IExperimentService, IExtensions, IInterpreterPathService, ILogOutputChannel, @@ -59,7 +53,6 @@ import { logAndNotifyOnLegacySettings } from './logging/settingLogs'; import { DebuggerTypeName } from './debugger/constants'; import { StopWatch } from './common/utils/stopWatch'; import { registerReplCommands, registerReplExecuteOnEnter } from './repl/replCommands'; -import { EnableRunREPL } from './common/experiments/groups'; export async function activateComponents( // `ext` is passed to any extra activation funcs. @@ -114,25 +107,8 @@ export function activateFeatures(ext: ExtensionState, _components: Components): pathUtils, ); - // Register native REPL context menu when in experiment - const experimentService = ext.legacyIOC.serviceContainer.get(IExperimentService); - const configurationService = ext.legacyIOC.serviceContainer.get(IConfigurationService); - const activeResourceService = ext.legacyIOC.serviceContainer.get(IActiveResourceService); - // commands.executeCommand('setContext', 'pythonRunREPL', false); - if (experimentService) { - const replExperimentValue = experimentService.inExperimentSync(EnableRunREPL.experiment); - commands.executeCommand('setContext', 'pythonRunREPL', replExperimentValue); - // If user is in pythonRunREPL experiment, we enable the sendToNativeREPL setting to True by default. - if (replExperimentValue) { - configurationService.updateSetting( - 'REPL.sendToNativeREPL', - true, - activeResourceService.getActiveResource(), - ); - registerReplCommands(ext.disposables, interpreterService, configurationService, activeResourceService); - registerReplExecuteOnEnter(ext.disposables, interpreterService); - } - } + registerReplCommands(ext.disposables, interpreterService); + registerReplExecuteOnEnter(ext.disposables, interpreterService); } /// ////////////////////////// diff --git a/src/client/repl/replCommands.ts b/src/client/repl/replCommands.ts index 9232c2a45512..5b056ce2a9d7 100644 --- a/src/client/repl/replCommands.ts +++ b/src/client/repl/replCommands.ts @@ -16,14 +16,14 @@ import { NotebookDocument, } from 'vscode'; import { Disposable } from 'vscode-jsonrpc'; -import { IActiveResourceService } from '../common/application/types'; import { Commands, PVSC_EXTENSION_ID } from '../common/constants'; -import { IConfigurationService } from '../common/types'; 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; @@ -48,27 +48,24 @@ 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, - configurationService: IConfigurationService, - activeResourceService: IActiveResourceService, ): Promise { disposables.push( commands.registerCommand(Commands.Exec_In_REPL, async (uri: Uri) => { - let nativeREPLSetting = false; - - // Get REPL setting value from user settings - if (configurationService) { - const pythonSettings = configurationService.getSettings(activeResourceService.getActiveResource()); - nativeREPLSetting = pythonSettings.REPL.sendToNativeREPL; - // 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 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); From 57b2bdd73c4d03cfa950502b98b2a28117665f0c Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Thu, 23 May 2024 13:30:55 -0700 Subject: [PATCH 30/33] refactor registerReplCommands --- src/client/repl/replCommands.ts | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/client/repl/replCommands.ts b/src/client/repl/replCommands.ts index 5b056ce2a9d7..5880126546ed 100644 --- a/src/client/repl/replCommands.ts +++ b/src/client/repl/replCommands.ts @@ -80,7 +80,6 @@ export async function registerReplCommands( notebookController = createReplController(interpreterPath, disposables); } const activeEditor = window.activeTextEditor as TextEditor; - const code = await getSelectedTextToExecute(activeEditor); // We want to keep notebookEditor, whenever we want to run. @@ -112,18 +111,11 @@ export async function registerReplCommands( 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); - + await addCellToNotebook(code as string); // Execute the cell commands.executeCommand('notebook.cell.execute', { ranges: [{ start: cellCount, end: cellCount + 1 }], - // document: ourResource, document: notebookDocument.uri, }); } @@ -131,6 +123,21 @@ export async function registerReplCommands( }), ); } +/** + * 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); +} /** * Command triggered for 'Enter': Conditionally call interactive.execute OR insert \n in text input box. From 0788e9903efe8fb12045c45d0254918252f235a7 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Thu, 23 May 2024 13:33:00 -0700 Subject: [PATCH 31/33] clean up more --- src/client/repl/replCommands.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/client/repl/replCommands.ts b/src/client/repl/replCommands.ts index 5880126546ed..258b851e3373 100644 --- a/src/client/repl/replCommands.ts +++ b/src/client/repl/replCommands.ts @@ -82,12 +82,8 @@ export async function registerReplCommands( const activeEditor = window.activeTextEditor as TextEditor; const code = await getSelectedTextToExecute(activeEditor); - // We want to keep notebookEditor, whenever we want to run. - // Find interactive window, or open it. - let interactiveWindowObject; - if (!notebookEditor) { - interactiveWindowObject = (await commands.executeCommand( + const interactiveWindowObject = (await commands.executeCommand( 'interactive.open', { preserveFocus: true, From 910783b89643667c9f47491af9af2fc7495f6a17 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Thu, 23 May 2024 15:37:55 -0700 Subject: [PATCH 32/33] allow users with shift+enter functional --- package.json | 9 +++++++-- src/client/common/constants.ts | 1 + src/client/extensionActivation.ts | 3 ++- src/client/repl/replCommands.ts | 6 ++++++ 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 0c4cc49545bc..b03fdfe81de8 100644 --- a/package.json +++ b/package.json @@ -1118,12 +1118,17 @@ { "command": "python.execInREPL", "key": "shift+enter", - "when": "config.python.REPL.sendToNativeREPL" + "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": "activeEditor == 'workbench.editor.interactive'" + "when": "!config.interactiveWindow.executeWithShiftEnter && activeEditor == 'workbench.editor.interactive'" }, { "command": "python.refreshTensorBoard", diff --git a/src/client/common/constants.ts b/src/client/common/constants.ts index d5b82f68ae97..94bcc78c6c6b 100644 --- a/src/client/common/constants.ts +++ b/src/client/common/constants.ts @@ -49,6 +49,7 @@ export namespace Commands { 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/extensionActivation.ts b/src/client/extensionActivation.ts index 1c7e8f384ff1..c8712281673d 100644 --- a/src/client/extensionActivation.ts +++ b/src/client/extensionActivation.ts @@ -52,7 +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, registerReplExecuteOnEnter } from './repl/replCommands'; +import { registerReplCommands, registerReplExecuteOnEnter, registerReplExecuteOnShiftEnter } from './repl/replCommands'; export async function activateComponents( // `ext` is passed to any extra activation funcs. @@ -109,6 +109,7 @@ export function activateFeatures(ext: ExtensionState, _components: Components): registerReplCommands(ext.disposables, interpreterService); registerReplExecuteOnEnter(ext.disposables, interpreterService); + registerReplExecuteOnShiftEnter(); } /// ////////////////////////// diff --git a/src/client/repl/replCommands.ts b/src/client/repl/replCommands.ts index 258b851e3373..2c0217873b8b 100644 --- a/src/client/repl/replCommands.ts +++ b/src/client/repl/replCommands.ts @@ -193,6 +193,12 @@ export async function registerReplExecuteOnEnter( ); } +export async function registerReplExecuteOnShiftEnter(): Promise { + 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; } From e270705b4b38c95ca4a09d58c72a365b60305151 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Thu, 23 May 2024 16:05:56 -0700 Subject: [PATCH 33/33] properly add to disposables --- src/client/extensionActivation.ts | 2 +- src/client/repl/replCommands.ts | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/client/extensionActivation.ts b/src/client/extensionActivation.ts index c8712281673d..37d97b8ccbab 100644 --- a/src/client/extensionActivation.ts +++ b/src/client/extensionActivation.ts @@ -109,7 +109,7 @@ export function activateFeatures(ext: ExtensionState, _components: Components): registerReplCommands(ext.disposables, interpreterService); registerReplExecuteOnEnter(ext.disposables, interpreterService); - registerReplExecuteOnShiftEnter(); + registerReplExecuteOnShiftEnter(ext.disposables); } /// ////////////////////////// diff --git a/src/client/repl/replCommands.ts b/src/client/repl/replCommands.ts index 2c0217873b8b..d9d0858a6637 100644 --- a/src/client/repl/replCommands.ts +++ b/src/client/repl/replCommands.ts @@ -193,10 +193,12 @@ export async function registerReplExecuteOnEnter( ); } -export async function registerReplExecuteOnShiftEnter(): Promise { - commands.registerCommand(Commands.Exec_In_REPL_Shift_Enter, async () => { - await commands.executeCommand(Commands.Exec_In_REPL_Enter); - }); +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 {