From 51b5f8eb21e698e0314670133c6fc40821a02efb Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Mon, 15 Apr 2024 10:52:44 -0700 Subject: [PATCH 01/39] start adding interactive REPL experiment --- package.json | 12 ++++++++---- package.nls.json | 3 ++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 695ff8793f31..e50995c24f7a 100644 --- a/package.json +++ b/package.json @@ -517,7 +517,8 @@ "pythonDiscoveryUsingWorkers", "pythonTestAdapter", "pythonREPLSmartSend", - "pythonRecommendTensorboardExt" + "pythonRecommendTensorboardExt", + "pythonRunREPL" ], "enumDescriptions": [ "%python.experiments.All.description%", @@ -527,7 +528,8 @@ "%python.experiments.pythonDiscoveryUsingWorkers.description%", "%python.experiments.pythonTestAdapter.description%", "%python.experiments.pythonREPLSmartSend.description%", - "%python.experiments.pythonRecommendTensorboardExt.description%" + "%python.experiments.pythonRecommendTensorboardExt.description%", + "%python.experiments.pythonRunREPL.description%" ] }, "scope": "machine", @@ -545,7 +547,8 @@ "pythonTerminalEnvVarActivation", "pythonDiscoveryUsingWorkers", "pythonTestAdapter", - "pythonREPLSmartSend" + "pythonREPLSmartSend", + "pythonRunREPL" ], "enumDescriptions": [ "%python.experiments.All.description%", @@ -554,7 +557,8 @@ "%python.experiments.pythonTerminalEnvVarActivation.description%", "%python.experiments.pythonDiscoveryUsingWorkers.description%", "%python.experiments.pythonTestAdapter.description%", - "%python.experiments.pythonREPLSmartSend.description%" + "%python.experiments.pythonREPLSmartSend.description%", + "%python.experiments.pythonRunREPL.description%" ] }, "scope": "machine", diff --git a/package.nls.json b/package.nls.json index 3352c639b080..2117f8418f8b 100644 --- a/package.nls.json +++ b/package.nls.json @@ -44,6 +44,7 @@ "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.", @@ -193,4 +194,4 @@ "walkthrough.step.python.createNewNotebook.altText": "Creating a new Jupyter notebook", "walkthrough.step.python.openInteractiveWindow.altText": "Opening Python interactive window", "walkthrough.step.python.dataScienceLearnMore.altText": "Image representing our documentation page and mailing list resources." -} \ No newline at end of file +} From 51278d531423f116a194ee80c2feec80976642db Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Mon, 15 Apr 2024 11:16:17 -0700 Subject: [PATCH 02/39] add execInREPL command --- package.json | 16 ++++++++++++++++ package.nls.json | 1 + 2 files changed, 17 insertions(+) diff --git a/package.json b/package.json index e50995c24f7a..edfa19143858 100644 --- a/package.json +++ b/package.json @@ -386,6 +386,11 @@ "command": "python.execSelectionInTerminal", "title": "%python.command.python.execSelectionInTerminal.title%" }, + { + "category": "Python", + "command": "python.execInREPL", + "title": "%python.command.python.execInREPL.title%" + }, { "category": "Python", "command": "python.launchTensorBoard", @@ -1325,6 +1330,12 @@ "title": "%python.command.python.execSelectionInTerminal.title%", "when": "!virtualWorkspace && shellExecutionSupported && editorLangId == python" }, + { + "category": "Python", + "command": "python.execInREPL", + "title": "%python.command.python.execInREPL.title%", + "when": "!virtualWorkspace && shellExecutionSupported && editorLangId == python" + }, { "category": "Python", "command": "python.launchTensorBoard", @@ -1424,6 +1435,11 @@ "command": "python.execSelectionInTerminal", "group": "Python", "when": "editorFocus && editorLangId == python && !virtualWorkspace && shellExecutionSupported" + }, + { + "command": "python.execInREPL", + "group": "Python", + "when": "editorFocus && editorLangId == python && !virtualWorkspace && shellExecutionSupported" } ], "editor/title": [ diff --git a/package.nls.json b/package.nls.json index 2117f8418f8b..3fae38edf4f7 100644 --- a/package.nls.json +++ b/package.nls.json @@ -14,6 +14,7 @@ "python.command.python.configureTests.title": "Configure Tests", "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.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 8dbbd057c4f5f41fb3a78768810285b7da23b808 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Mon, 15 Apr 2024 11:23:43 -0700 Subject: [PATCH 03/39] add REPL command --- src/client/common/application/commands.ts | 1 + src/client/common/constants.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/client/common/application/commands.ts b/src/client/common/application/commands.ts index 30ba5d84cf5f..626321566332 100644 --- a/src/client/common/application/commands.ts +++ b/src/client/common/application/commands.ts @@ -38,6 +38,7 @@ interface ICommandNameWithoutArgumentTypeMapping { [Commands.Enable_SourceMap_Support]: []; [Commands.Exec_Selection_In_Terminal]: []; [Commands.Exec_Selection_In_Django_Shell]: []; + [Commands.Exec_In_REPL]: []; [Commands.Create_Terminal]: []; [Commands.PickLocalProcess]: []; [Commands.ClearStorage]: []; diff --git a/src/client/common/constants.ts b/src/client/common/constants.ts index 0eaade703371..663b932c8542 100644 --- a/src/client/common/constants.ts +++ b/src/client/common/constants.ts @@ -46,6 +46,7 @@ export namespace Commands { export const Exec_In_Terminal = 'python.execInTerminal'; export const Exec_In_Terminal_Icon = 'python.execInTerminal-icon'; 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_Selection_In_Terminal = 'python.execSelectionInTerminal'; export const GetSelectedInterpreterPath = 'python.interpreterPath'; From 5b76e4ae9bc8ecd03987a0a39ec3178aba453973 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Mon, 15 Apr 2024 16:46:06 -0700 Subject: [PATCH 04/39] replCommands, replController --- package.json | 2 +- pythonFiles/python_server.py | 125 ++++++++++++++++++++++++++++++ src/client/extensionActivation.ts | 2 + src/client/repl/pythonServer.ts | 50 ++++++++++++ src/client/repl/replCommands.ts | 35 +++++++++ src/client/repl/replController.ts | 37 +++++++++ 6 files changed, 250 insertions(+), 1 deletion(-) create mode 100644 pythonFiles/python_server.py create mode 100644 src/client/repl/pythonServer.ts create mode 100644 src/client/repl/replCommands.ts create mode 100644 src/client/repl/replController.ts diff --git a/package.json b/package.json index edfa19143858..100756802e90 100644 --- a/package.json +++ b/package.json @@ -1334,7 +1334,7 @@ "category": "Python", "command": "python.execInREPL", "title": "%python.command.python.execInREPL.title%", - "when": "!virtualWorkspace && shellExecutionSupported && editorLangId == python" + "when": "false" }, { "category": "Python", diff --git a/pythonFiles/python_server.py b/pythonFiles/python_server.py new file mode 100644 index 000000000000..e4c405a6d662 --- /dev/null +++ b/pythonFiles/python_server.py @@ -0,0 +1,125 @@ +# import debugpy +# debugpy.connect(5678) +import sys +import json +import contextlib +import io +import traceback + + +def send_message(msg: str): + length_msg = len(msg) + sys.stdout.buffer.write( + f"Content-Length: {length_msg}\r\n\r\n{msg}".encode(encoding="utf-8") + ) + sys.stdout.buffer.flush() + + +def print_log(msg: str): + send_message(json.dumps({"jsonrpc": "2.0", "method": "log", "params": msg})) + + +def send_response(response: dict, response_id: int): + send_message(json.dumps({"jsonrpc": "2.0", "id": response_id, "result": response})) + + +def exec_function(user_input): + try: + compile(user_input, "", "eval") + except SyntaxError: + return exec + return eval + + +def execute(user_globals, request): + str_output = CustomIO("", encoding="utf-8") + str_error = CustomIO("", encoding="utf-8") + + with redirect_io("stdout", str_output): + with redirect_io("stderr", str_error): + str_input = CustomIO("", encoding="utf-8", newline="\n") + with redirect_io("stdin", str_input): + user_output_globals = exec_user_input( + request["id"], request["params"], user_globals + ) + send_response(str_output.get_value(), request["id"]) + return user_output_globals + + +def exec_user_input(request_id, user_input, user_globals): + # have to do redirection + user_input = user_input[0] if isinstance(user_input, list) else user_input + user_globals = user_globals.copy() + + try: + callable = exec_function(user_input) + retval = callable(user_input, user_globals) + if retval is not None: + print(retval) + except Exception as e: + send_response( + { + "error": { + "code": -32603, + "message": str(e), + "data": traceback.format_exc(), + }, + "id": request_id, + } + ) + return user_globals + + +class CustomIO(io.TextIOWrapper): + """Custom stream object to replace stdio.""" + + name = None + + def __init__(self, name, encoding="utf-8", newline=None): + self._buffer = io.BytesIO() + self._buffer.name = name + super().__init__(self._buffer, encoding=encoding, newline=newline) + + def close(self): + """Provide this close method which is used by some tools.""" + # This is intentionally empty. + + def get_value(self) -> str: + """Returns value from the buffer as string.""" + self.seek(0) + return self.read() + + +@contextlib.contextmanager +def redirect_io(stream: str, new_stream): + """Redirect stdio streams to a custom stream.""" + old_stream = getattr(sys, stream) + setattr(sys, stream, new_stream) + yield + setattr(sys, stream, old_stream) + + +def get_headers(): + headers = {} + while line := sys.stdin.readline().strip(): + name, value = line.split(":", 1) + headers[name] = value.strip() + return headers + + +if __name__ == "__main__": + user_globals = {} + while not sys.stdin.closed: + try: + headers = get_headers() + content_length = int(headers.get("Content-Length", 0)) + + if content_length: + request_json = json.loads(sys.stdin.read(content_length)) + if request_json["method"] == "execute": + user_globals = execute(user_globals, request_json) + elif request_json["method"] == "exit": + sys.exit(0) + + except Exception as e: + print_log(str(e)) diff --git a/src/client/extensionActivation.ts b/src/client/extensionActivation.ts index 543d2d0b7f49..9b30609ad333 100644 --- a/src/client/extensionActivation.ts +++ b/src/client/extensionActivation.ts @@ -52,6 +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'; export async function activateComponents( // `ext` is passed to any extra activation funcs. @@ -105,6 +106,7 @@ export function activateFeatures(ext: ExtensionState, _components: Components): interpreterService, pathUtils, ); + registerReplCommands(ext.disposables, interpreterService); } /// ////////////////////////// diff --git a/src/client/repl/pythonServer.ts b/src/client/repl/pythonServer.ts new file mode 100644 index 000000000000..8b7a06ba4654 --- /dev/null +++ b/src/client/repl/pythonServer.ts @@ -0,0 +1,50 @@ +import * as path from 'path'; +import * as ch from 'child_process'; +import * as rpc from 'vscode-jsonrpc/node'; +import { Disposable } from 'vscode'; + +const SERVER_PATH = path.join(__dirname, '...', 'python_files', 'python_server.py'); + +export interface PythonServer extends Disposable { + execute(code: string): Promise; +} + +class PythonServerImpl implements Disposable { + constructor(private connection: rpc.MessageConnection) { + this.initialize(); + } + + private initialize(): void { + this.connection.onNotification('log', (message: string) => { + console.log('Log:', message); + }); + this.connection.listen(); + } + + public execute(code: string): Promise { + return this.connection.sendRequest('execute', code); + } + + public dispose(): void { + this.connection.sendNotification('exit'); + this.connection.dispose(); + } +} + +export function createPythonServer(interpreter: string[]): PythonServer { + const pythonServer = ch.spawn(interpreter[0], [...interpreter.slice(1), SERVER_PATH]); + pythonServer.stderr.on('data', (data) => { + console.error(data.toString()); + }); + pythonServer.on('exit', (code) => { + console.error(`Python server exited with code ${code}`); + }); + pythonServer.on('error', (err) => { + console.error(err); + }); + const connection = rpc.createMessageConnection( + new rpc.StreamMessageReader(pythonServer.stdout), + new rpc.StreamMessageWriter(pythonServer.stdin), + ); + return new PythonServerImpl(connection); +} diff --git a/src/client/repl/replCommands.ts b/src/client/repl/replCommands.ts new file mode 100644 index 000000000000..9e1d83a104c2 --- /dev/null +++ b/src/client/repl/replCommands.ts @@ -0,0 +1,35 @@ +import { commands, Uri } from 'vscode'; +import { Disposable } from 'vscode-jsonrpc'; +import { Commands } from '../common/constants'; +import { IInterpreterService } from '../interpreter/contracts'; +import { startRepl } from './replController'; + +export function registerReplCommands(disposables: Disposable[], interpreterService: IInterpreterService): void { + disposables.push( + commands.registerCommand(Commands.Exec_In_REPL, async (uri: Uri) => { + const interpreter = await interpreterService.getActiveInterpreter(uri); + if (interpreter) { + const interpreterPath = interpreter.path; + // How do we get instance of interactive window from Python extension? + // How to go from user clicking Run Python --> Run selection/line via Python REPL -> IW opening + + // TODO: Find interactive window, or open it + + // TODO: Add new cell to interactive window document + + // TODO: Set REPL server on interactive window. Make sure REPL server is running + + // TODO: execute the cell + } + }), + ); +} + +// debugger +// read code, use tools like hover, doc +// think through + +// ask questions +// write down what you know +// write down what you don't know +// write down what you think you know diff --git a/src/client/repl/replController.ts b/src/client/repl/replController.ts new file mode 100644 index 000000000000..fc1d26b57a91 --- /dev/null +++ b/src/client/repl/replController.ts @@ -0,0 +1,37 @@ +import * as vscode from 'vscode'; +import { createPythonServer } from './pythonServer'; + +export function createReplController(interpreterPath: string): vscode.NotebookController { + const server = createPythonServer([interpreterPath]); + const controller = vscode.notebooks.createNotebookController('pythonREPL', 'interactive', 'Python REPL'); + controller.supportedLanguages = ['python']; + controller.supportsExecutionOrder = true; + controller.description = 'GitHub'; + + controller.executeHandler = async (cells) => { + for (const cell of cells) { + const exec = controller.createNotebookCellExecution(cell); + exec.start(Date.now()); + try { + const result = await server.execute(cell.document.getText()); + exec.replaceOutput([ + new vscode.NotebookCellOutput([vscode.NotebookCellOutputItem.text(result, 'text/plain')]), + ]); + exec.end(true); + } catch (err) { + const error = err as Error; + exec.replaceOutput([ + new vscode.NotebookCellOutput([ + vscode.NotebookCellOutputItem.error({ + name: error.name, + message: error.message, + stack: error.stack, + }), + ]), + ]); + exec.end(false); + } + } + }; + return controller; +} From e92c5391907136f68b14d303313ffcac6a18812b Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 16 Apr 2024 00:06:47 -0700 Subject: [PATCH 05/39] check in current status --- src/client/repl/pythonServer.ts | 5 ++++- src/client/repl/replCommands.ts | 9 +++++++-- src/client/repl/replController.ts | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/client/repl/pythonServer.ts b/src/client/repl/pythonServer.ts index 8b7a06ba4654..1b12e8d2f417 100644 --- a/src/client/repl/pythonServer.ts +++ b/src/client/repl/pythonServer.ts @@ -3,7 +3,10 @@ import * as ch from 'child_process'; import * as rpc from 'vscode-jsonrpc/node'; import { Disposable } from 'vscode'; -const SERVER_PATH = path.join(__dirname, '...', 'python_files', 'python_server.py'); +// const SERVER_PATH = path.join(__dirname, '...', 'python_files', 'python_server.py'); +const SERVER_PATH = path.join(__dirname, '..', '..', 'pythonFiles', 'python_server.py'); + +// const SERVER_PATH = path.join(__dirname, '..', '..', '..', 'pythonFiles', 'python_server.py'); export interface PythonServer extends Disposable { execute(code: string): Promise; diff --git a/src/client/repl/replCommands.ts b/src/client/repl/replCommands.ts index 9e1d83a104c2..de607cfb6cf8 100644 --- a/src/client/repl/replCommands.ts +++ b/src/client/repl/replCommands.ts @@ -1,8 +1,8 @@ -import { commands, Uri } from 'vscode'; +import { commands, Uri, workspace } from 'vscode'; import { Disposable } from 'vscode-jsonrpc'; import { Commands } from '../common/constants'; import { IInterpreterService } from '../interpreter/contracts'; -import { startRepl } from './replController'; +import { createReplController, startRepl } from './replController'; export function registerReplCommands(disposables: Disposable[], interpreterService: IInterpreterService): void { disposables.push( @@ -11,6 +11,8 @@ export function registerReplCommands(disposables: Disposable[], interpreterServi if (interpreter) { const interpreterPath = interpreter.path; // How do we get instance of interactive window from Python extension? + const ourController = createReplController(interpreterPath); + // How to go from user clicking Run Python --> Run selection/line via Python REPL -> IW opening // TODO: Find interactive window, or open it @@ -21,6 +23,9 @@ export function registerReplCommands(disposables: Disposable[], interpreterServi // TODO: execute the cell } + // workspace.onDidOpenNotebookDocument; + await workspace.openNotebookDocument('interactive'); + // await window.showNotebookDocument() }), ); } diff --git a/src/client/repl/replController.ts b/src/client/repl/replController.ts index fc1d26b57a91..63f17b150d55 100644 --- a/src/client/repl/replController.ts +++ b/src/client/repl/replController.ts @@ -6,7 +6,7 @@ export function createReplController(interpreterPath: string): vscode.NotebookCo const controller = vscode.notebooks.createNotebookController('pythonREPL', 'interactive', 'Python REPL'); controller.supportedLanguages = ['python']; controller.supportsExecutionOrder = true; - controller.description = 'GitHub'; + controller.description = 'Python REPL'; controller.executeHandler = async (cells) => { for (const cell of cells) { From bc15f8fedf7d66c42e078b129027e6253df78c08 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 16 Apr 2024 10:13:35 -0700 Subject: [PATCH 06/39] myChanges --- {pythonFiles => python_files}/python_server.py | 13 ++----------- src/client/repl/pythonServer.ts | 3 ++- src/client/repl/replCommands.ts | 2 +- 3 files changed, 5 insertions(+), 13 deletions(-) rename {pythonFiles => python_files}/python_server.py (91%) diff --git a/pythonFiles/python_server.py b/python_files/python_server.py similarity index 91% rename from pythonFiles/python_server.py rename to python_files/python_server.py index e4c405a6d662..7415fef1a3c0 100644 --- a/pythonFiles/python_server.py +++ b/python_files/python_server.py @@ -56,17 +56,8 @@ def exec_user_input(request_id, user_input, user_globals): retval = callable(user_input, user_globals) if retval is not None: print(retval) - except Exception as e: - send_response( - { - "error": { - "code": -32603, - "message": str(e), - "data": traceback.format_exc(), - }, - "id": request_id, - } - ) + except Exception: + print(traceback.format_exc()) return user_globals diff --git a/src/client/repl/pythonServer.ts b/src/client/repl/pythonServer.ts index 1b12e8d2f417..065742f4eb71 100644 --- a/src/client/repl/pythonServer.ts +++ b/src/client/repl/pythonServer.ts @@ -2,9 +2,10 @@ import * as path from 'path'; import * as ch from 'child_process'; import * as rpc from 'vscode-jsonrpc/node'; import { Disposable } from 'vscode'; +import { EXTENSION_ROOT_DIR } from '../constants'; // const SERVER_PATH = path.join(__dirname, '...', 'python_files', 'python_server.py'); -const SERVER_PATH = path.join(__dirname, '..', '..', 'pythonFiles', 'python_server.py'); +const SERVER_PATH = path.join(EXTENSION_ROOT_DIR, 'python_files', 'python_server.py'); // const SERVER_PATH = path.join(__dirname, '..', '..', '..', 'pythonFiles', 'python_server.py'); diff --git a/src/client/repl/replCommands.ts b/src/client/repl/replCommands.ts index de607cfb6cf8..e072774e877d 100644 --- a/src/client/repl/replCommands.ts +++ b/src/client/repl/replCommands.ts @@ -24,7 +24,7 @@ export function registerReplCommands(disposables: Disposable[], interpreterServi // TODO: execute the cell } // workspace.onDidOpenNotebookDocument; - await workspace.openNotebookDocument('interactive'); + // await workspace.openNotebookDocument('interactive'); // await window.showNotebookDocument() }), ); From 200ca1ab908b07727b0903e8fd1afbb97bdd9338 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 16 Apr 2024 12:08:56 -0700 Subject: [PATCH 07/39] big progress --- src/client/repl/replCommands.ts | 95 +++++++++++++++++++++++++++++++-- 1 file changed, 90 insertions(+), 5 deletions(-) diff --git a/src/client/repl/replCommands.ts b/src/client/repl/replCommands.ts index e072774e877d..3d635ce97e5d 100644 --- a/src/client/repl/replCommands.ts +++ b/src/client/repl/replCommands.ts @@ -1,9 +1,23 @@ -import { commands, Uri, workspace } from 'vscode'; +import { + commands, + NotebookController, + Uri, + workspace, + window, + NotebookControllerAffinity, + ViewColumn, + NotebookEdit, + NotebookCellData, + NotebookCellKind, + WorkspaceEdit, +} from 'vscode'; import { Disposable } from 'vscode-jsonrpc'; -import { Commands } from '../common/constants'; +import { Commands, PVSC_EXTENSION_ID } from '../common/constants'; import { IInterpreterService } from '../interpreter/contracts'; import { createReplController, startRepl } from './replController'; +let ourController: NotebookController | undefined; + export function registerReplCommands(disposables: Disposable[], interpreterService: IInterpreterService): void { disposables.push( commands.registerCommand(Commands.Exec_In_REPL, async (uri: Uri) => { @@ -11,7 +25,9 @@ export function registerReplCommands(disposables: Disposable[], interpreterServi if (interpreter) { const interpreterPath = interpreter.path; // How do we get instance of interactive window from Python extension? - const ourController = createReplController(interpreterPath); + if (!ourController) { + ourController = createReplController(interpreterPath); + } // How to go from user clicking Run Python --> Run selection/line via Python REPL -> IW opening @@ -24,8 +40,77 @@ export function registerReplCommands(disposables: Disposable[], interpreterServi // TODO: execute the cell } // workspace.onDidOpenNotebookDocument; - // await workspace.openNotebookDocument('interactive'); - // await window.showNotebookDocument() + const ourResource = Uri.from({ scheme: 'untitled', path: 'repl.interactive' }); + const notebookDocument = await workspace.openNotebookDocument(ourResource); + + const notebookEditor = await window.showNotebookDocument(notebookDocument, { + viewColumn: ViewColumn.Beside, + }); + + ourController!.updateNotebookAffinity(notebookDocument, NotebookControllerAffinity.Default); + // await commands.executeCommand( + // 'interactive.open', + // // Keep focus on the owning file if there is one + // { viewColum: 1, preserveFocus: true }, + // ourResource, + // ourController?.id, + // 'Python REPL', + // ); + + await commands.executeCommand('notebook.selectKernel', { + notebookEditor, + id: ourController?.id, + extension: PVSC_EXTENSION_ID, + }); + + const notebookCellData = new NotebookCellData(NotebookCellKind.Code, 'x=5', 'python'); + // keep counter + const { cellCount } = notebookDocument; + const notebookEdit = NotebookEdit.insertCells(cellCount, [notebookCellData]); + const workspaceEdit = new WorkspaceEdit(); + workspaceEdit.set(notebookDocument.uri, [notebookEdit]); + workspace.applyEdit(workspaceEdit); + + const notebookCellExecution = ourController!.createNotebookCellExecution( + notebookDocument.cellAt(cellCount), + ); + notebookCellExecution.start(Date.now()); + // NEED TO TELL TO EXECUTE THE CELL WHICH WILL CALL MY HANDLER + + // args: [ + // { + // name: 'showOptions', + // description: 'Show Options', + // schema: { + // type: 'object', + // properties: { + // 'viewColumn': { + // type: 'number', + // default: -1 + // }, + // 'preserveFocus': { + // type: 'boolean', + // default: true + // } + // }, + // } + // }, + // { + // name: 'resource', + // description: 'Interactive resource Uri', + // isOptional: true + // }, + // { + // name: 'controllerId', + // description: 'Notebook controller Id', + // isOptional: true + // }, + // { + // name: 'title', + // description: 'Notebook editor title', + // isOptional: true + // } + // ] }), ); } From 416ea96cb1203880d09cef1432f52118aa3c9088 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 16 Apr 2024 15:45:40 -0700 Subject: [PATCH 08/39] save notebook editor --- src/client/repl/replCommands.ts | 41 ++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/src/client/repl/replCommands.ts b/src/client/repl/replCommands.ts index 3d635ce97e5d..760379ebe335 100644 --- a/src/client/repl/replCommands.ts +++ b/src/client/repl/replCommands.ts @@ -17,6 +17,16 @@ import { IInterpreterService } from '../interpreter/contracts'; import { createReplController, startRepl } from './replController'; let ourController: NotebookController | undefined; +let ourNotebookEditor: NotebookEditor | undefined; + +// a.py in REPL. +// b.py run in REPL +// Uri to notebookEditor if we want separate REPL for each file. + +// when you reload window, in REPL editor is it restored... +// cache binding uri to python..5 + +// figure out way to put markdown telling user kernel has been dead and need to pick again. export function registerReplCommands(disposables: Disposable[], interpreterService: IInterpreterService): void { disposables.push( @@ -43,9 +53,15 @@ export function registerReplCommands(disposables: Disposable[], interpreterServi const ourResource = Uri.from({ scheme: 'untitled', path: 'repl.interactive' }); const notebookDocument = await workspace.openNotebookDocument(ourResource); - const notebookEditor = await window.showNotebookDocument(notebookDocument, { - viewColumn: ViewColumn.Beside, - }); + // We want to keep notebookEditor, whenever we want to run. + if (!ourNotebookEditor) { + ourNotebookEditor = await window.showNotebookDocument(notebookDocument, { + viewColumn: ViewColumn.Beside, + }); + } + // ourNotebookEditor = await window.showNotebookDocument(notebookDocument, { + // viewColumn: ViewColumn.Beside, + // }); ourController!.updateNotebookAffinity(notebookDocument, NotebookControllerAffinity.Default); // await commands.executeCommand( @@ -58,7 +74,7 @@ export function registerReplCommands(disposables: Disposable[], interpreterServi // ); await commands.executeCommand('notebook.selectKernel', { - notebookEditor, + ourNotebookEditor, id: ourController?.id, extension: PVSC_EXTENSION_ID, }); @@ -71,10 +87,19 @@ export function registerReplCommands(disposables: Disposable[], interpreterServi workspaceEdit.set(notebookDocument.uri, [notebookEdit]); workspace.applyEdit(workspaceEdit); - const notebookCellExecution = ourController!.createNotebookCellExecution( - notebookDocument.cellAt(cellCount), - ); - notebookCellExecution.start(Date.now()); + // const notebookCellExecution = ourController!.createNotebookCellExecution( + // notebookDocument.cellAt(cellCount), + // ); + // notebookCellExecution.start(Date.now()); + // notebookCellExecution.end(true); + + commands.executeCommand('notebook.cell.execute', { + ranges: [{ start: cellCount, end: cellCount + 1 }], + document: ourResource, + }); + + // event fire our executeHandler we made for notebook controller + // NEED TO TELL TO EXECUTE THE CELL WHICH WILL CALL MY HANDLER // args: [ From 71d3e22acc22714c44472c2bf2785e5ff8ddd272 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 16 Apr 2024 23:40:16 -0700 Subject: [PATCH 09/39] start getting user input to pass to IW --- src/client/repl/replCommands.ts | 53 +++++++++++++------- src/client/terminals/codeExecution/helper.ts | 4 +- 2 files changed, 37 insertions(+), 20 deletions(-) diff --git a/src/client/repl/replCommands.ts b/src/client/repl/replCommands.ts index 760379ebe335..916ca91afcc9 100644 --- a/src/client/repl/replCommands.ts +++ b/src/client/repl/replCommands.ts @@ -10,11 +10,16 @@ import { NotebookCellData, NotebookCellKind, WorkspaceEdit, + NotebookEditor, + Range, + TextEditor, + Position, } from 'vscode'; import { Disposable } from 'vscode-jsonrpc'; import { Commands, PVSC_EXTENSION_ID } from '../common/constants'; import { IInterpreterService } from '../interpreter/contracts'; -import { createReplController, startRepl } from './replController'; +import { getMultiLineSelectionText, getSingleLineSelectionText } from '../terminals/codeExecution/helper'; +import { createReplController } from './replController'; let ourController: NotebookController | undefined; let ourNotebookEditor: NotebookEditor | undefined; @@ -28,7 +33,26 @@ let ourNotebookEditor: NotebookEditor | undefined; // figure out way to put markdown telling user kernel has been dead and need to pick again. -export function registerReplCommands(disposables: Disposable[], interpreterService: IInterpreterService): void { +async function getSelectedTextToExecute(textEditor: TextEditor): Promise { + if (!textEditor) { + return undefined; + } + + const { selection } = textEditor; + let code: string; + + if (selection.isEmpty) { + code = textEditor.document.lineAt(selection.start.line).text; + } else if (selection.isSingleLine) { + code = getSingleLineSelectionText(textEditor); + } else { + code = getMultiLineSelectionText(textEditor); + } + + return code; +} + +export async function registerReplCommands(disposables: Disposable[], interpreterService: IInterpreterService): void { disposables.push( commands.registerCommand(Commands.Exec_In_REPL, async (uri: Uri) => { const interpreter = await interpreterService.getActiveInterpreter(uri); @@ -38,30 +62,21 @@ export function registerReplCommands(disposables: Disposable[], interpreterServi if (!ourController) { ourController = createReplController(interpreterPath); } + const activeEditor = window.activeTextEditor as TextEditor; - // How to go from user clicking Run Python --> Run selection/line via Python REPL -> IW opening - - // TODO: Find interactive window, or open it - - // TODO: Add new cell to interactive window document - - // TODO: Set REPL server on interactive window. Make sure REPL server is running - - // TODO: execute the cell + const code = await getSelectedTextToExecute(activeEditor); } - // workspace.onDidOpenNotebookDocument; + const ourResource = Uri.from({ scheme: 'untitled', path: 'repl.interactive' }); + // How to go from user clicking Run Python --> Run selection/line via Python REPL -> IW opening const notebookDocument = await workspace.openNotebookDocument(ourResource); - // We want to keep notebookEditor, whenever we want to run. + // Find interactive window, or open it. if (!ourNotebookEditor) { ourNotebookEditor = await window.showNotebookDocument(notebookDocument, { viewColumn: ViewColumn.Beside, }); } - // ourNotebookEditor = await window.showNotebookDocument(notebookDocument, { - // viewColumn: ViewColumn.Beside, - // }); ourController!.updateNotebookAffinity(notebookDocument, NotebookControllerAffinity.Default); // await commands.executeCommand( @@ -73,14 +88,15 @@ export function registerReplCommands(disposables: Disposable[], interpreterServi // 'Python REPL', // ); + // Auto-Select Python REPL Kernel await commands.executeCommand('notebook.selectKernel', { ourNotebookEditor, id: ourController?.id, extension: PVSC_EXTENSION_ID, }); - const notebookCellData = new NotebookCellData(NotebookCellKind.Code, 'x=5', 'python'); - // keep counter + // Add new cell to interactive window document + const notebookCellData = new NotebookCellData(NotebookCellKind.Code, 'x=5', 'python'); // this is manual atm but need to pass in user input here const { cellCount } = notebookDocument; const notebookEdit = NotebookEdit.insertCells(cellCount, [notebookCellData]); const workspaceEdit = new WorkspaceEdit(); @@ -93,6 +109,7 @@ export function registerReplCommands(disposables: Disposable[], interpreterServi // notebookCellExecution.start(Date.now()); // notebookCellExecution.end(true); + // Execute the cell commands.executeCommand('notebook.cell.execute', { ranges: [{ start: cellCount, end: cellCount + 1 }], document: ourResource, diff --git a/src/client/terminals/codeExecution/helper.ts b/src/client/terminals/codeExecution/helper.ts index 880da969d690..ff1c4f218f8d 100644 --- a/src/client/terminals/codeExecution/helper.ts +++ b/src/client/terminals/codeExecution/helper.ts @@ -200,7 +200,7 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { } } -function getSingleLineSelectionText(textEditor: TextEditor): string { +export function getSingleLineSelectionText(textEditor: TextEditor): string { const { selection } = textEditor; const selectionRange = new Range(selection.start, selection.end); const selectionText = textEditor.document.getText(selectionRange); @@ -227,7 +227,7 @@ function getSingleLineSelectionText(textEditor: TextEditor): string { return selectionText; } -function getMultiLineSelectionText(textEditor: TextEditor): string { +export function getMultiLineSelectionText(textEditor: TextEditor): string { const { selection } = textEditor; const selectionRange = new Range(selection.start, selection.end); const selectionText = textEditor.document.getText(selectionRange); From a2208ca9beab282caea29c8488332cd0a0b4f051 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 16 Apr 2024 23:47:57 -0700 Subject: [PATCH 10/39] user input feeds into IW --- src/client/repl/replCommands.ts | 169 ++++++++++++++++---------------- 1 file changed, 84 insertions(+), 85 deletions(-) diff --git a/src/client/repl/replCommands.ts b/src/client/repl/replCommands.ts index 916ca91afcc9..3510228e4304 100644 --- a/src/client/repl/replCommands.ts +++ b/src/client/repl/replCommands.ts @@ -65,94 +65,93 @@ export async function registerReplCommands(disposables: Disposable[], interprete const activeEditor = window.activeTextEditor as TextEditor; const code = await getSelectedTextToExecute(activeEditor); - } + const ourResource = Uri.from({ scheme: 'untitled', path: 'repl.interactive' }); + // How to go from user clicking Run Python --> Run selection/line via Python REPL -> IW opening + const notebookDocument = await workspace.openNotebookDocument(ourResource); + // We want to keep notebookEditor, whenever we want to run. + // Find interactive window, or open it. + if (!ourNotebookEditor) { + ourNotebookEditor = await window.showNotebookDocument(notebookDocument, { + viewColumn: ViewColumn.Beside, + }); + } - const ourResource = Uri.from({ scheme: 'untitled', path: 'repl.interactive' }); - // How to go from user clicking Run Python --> Run selection/line via Python REPL -> IW opening - const notebookDocument = await workspace.openNotebookDocument(ourResource); - // We want to keep notebookEditor, whenever we want to run. - // Find interactive window, or open it. - if (!ourNotebookEditor) { - ourNotebookEditor = await window.showNotebookDocument(notebookDocument, { - viewColumn: ViewColumn.Beside, + ourController!.updateNotebookAffinity(notebookDocument, NotebookControllerAffinity.Default); + // await commands.executeCommand( + // 'interactive.open', + // // Keep focus on the owning file if there is one + // { viewColum: 1, preserveFocus: true }, + // ourResource, + // ourController?.id, + // 'Python REPL', + // ); + + // Auto-Select Python REPL Kernel + await commands.executeCommand('notebook.selectKernel', { + ourNotebookEditor, + id: ourController?.id, + extension: PVSC_EXTENSION_ID, }); - } - ourController!.updateNotebookAffinity(notebookDocument, NotebookControllerAffinity.Default); - // await commands.executeCommand( - // 'interactive.open', - // // Keep focus on the owning file if there is one - // { viewColum: 1, preserveFocus: true }, - // ourResource, - // ourController?.id, - // 'Python REPL', - // ); - - // Auto-Select Python REPL Kernel - await commands.executeCommand('notebook.selectKernel', { - ourNotebookEditor, - id: ourController?.id, - extension: PVSC_EXTENSION_ID, - }); - - // Add new cell to interactive window document - const notebookCellData = new NotebookCellData(NotebookCellKind.Code, 'x=5', 'python'); // this is manual atm but need to pass in user input here - const { cellCount } = notebookDocument; - const notebookEdit = NotebookEdit.insertCells(cellCount, [notebookCellData]); - const workspaceEdit = new WorkspaceEdit(); - workspaceEdit.set(notebookDocument.uri, [notebookEdit]); - workspace.applyEdit(workspaceEdit); - - // const notebookCellExecution = ourController!.createNotebookCellExecution( - // notebookDocument.cellAt(cellCount), - // ); - // notebookCellExecution.start(Date.now()); - // notebookCellExecution.end(true); - - // Execute the cell - commands.executeCommand('notebook.cell.execute', { - ranges: [{ start: cellCount, end: cellCount + 1 }], - document: ourResource, - }); - - // event fire our executeHandler we made for notebook controller - - // NEED TO TELL TO EXECUTE THE CELL WHICH WILL CALL MY HANDLER - - // args: [ - // { - // name: 'showOptions', - // description: 'Show Options', - // schema: { - // type: 'object', - // properties: { - // 'viewColumn': { - // type: 'number', - // default: -1 - // }, - // 'preserveFocus': { - // type: 'boolean', - // default: true - // } - // }, - // } - // }, - // { - // name: 'resource', - // description: 'Interactive resource Uri', - // isOptional: true - // }, - // { - // name: 'controllerId', - // description: 'Notebook controller Id', - // isOptional: true - // }, - // { - // name: 'title', - // description: 'Notebook editor title', - // isOptional: true - // } - // ] + // Add new cell to interactive window document + const notebookCellData = new NotebookCellData(NotebookCellKind.Code, code as string, 'python'); // this is manual atm but need to pass in user input here + const { cellCount } = notebookDocument; + const notebookEdit = NotebookEdit.insertCells(cellCount, [notebookCellData]); + const workspaceEdit = new WorkspaceEdit(); + workspaceEdit.set(notebookDocument.uri, [notebookEdit]); + workspace.applyEdit(workspaceEdit); + + // const notebookCellExecution = ourController!.createNotebookCellExecution( + // notebookDocument.cellAt(cellCount), + // ); + // notebookCellExecution.start(Date.now()); + // notebookCellExecution.end(true); + + // Execute the cell + commands.executeCommand('notebook.cell.execute', { + ranges: [{ start: cellCount, end: cellCount + 1 }], + document: ourResource, + }); + + // event fire our executeHandler we made for notebook controller + + // NEED TO TELL TO EXECUTE THE CELL WHICH WILL CALL MY HANDLER + + // args: [ + // { + // name: 'showOptions', + // description: 'Show Options', + // schema: { + // type: 'object', + // properties: { + // 'viewColumn': { + // type: 'number', + // default: -1 + // }, + // 'preserveFocus': { + // type: 'boolean', + // default: true + // } + // }, + // } + // }, + // { + // name: 'resource', + // description: 'Interactive resource Uri', + // isOptional: true + // }, + // { + // name: 'controllerId', + // description: 'Notebook controller Id', + // isOptional: true + // }, + // { + // name: 'title', + // description: 'Notebook editor title', + // isOptional: true + // } + // ] + } }), ); } From c8a9b357f2a4caa520ba6264d8a2f5c035aa908d Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 17 Apr 2024 00:00:39 -0700 Subject: [PATCH 11/39] Remove comment, add TODO, cleanup --- src/client/repl/replCommands.ts | 84 +++++---------------------------- 1 file changed, 13 insertions(+), 71 deletions(-) diff --git a/src/client/repl/replCommands.ts b/src/client/repl/replCommands.ts index 3510228e4304..2deb52a6d77a 100644 --- a/src/client/repl/replCommands.ts +++ b/src/client/repl/replCommands.ts @@ -11,9 +11,7 @@ import { NotebookCellKind, WorkspaceEdit, NotebookEditor, - Range, TextEditor, - Position, } from 'vscode'; import { Disposable } from 'vscode-jsonrpc'; import { Commands, PVSC_EXTENSION_ID } from '../common/constants'; @@ -24,14 +22,16 @@ import { createReplController } from './replController'; let ourController: NotebookController | undefined; let ourNotebookEditor: NotebookEditor | undefined; +// TODO: Need to figure out making separate REPL for each file: // a.py in REPL. // b.py run in REPL -// Uri to notebookEditor if we want separate REPL for each file. +// MAPPING Uri to notebookEditor if we want separate REPL for each file. +// Currently: Everything gets sent into one single REPL. -// when you reload window, in REPL editor is it restored... -// cache binding uri to python..5 +// TODO: when you reload window, is the REPL still binded to same Python file? +// cache binding uri to to REPL instance or notebookEditor. -// figure out way to put markdown telling user kernel has been dead and need to pick again. +// TODO: figure out way to put markdown telling user kernel has been dead and need to pick again. async function getSelectedTextToExecute(textEditor: TextEditor): Promise { if (!textEditor) { @@ -52,7 +52,10 @@ async function getSelectedTextToExecute(textEditor: TextEditor): Promise { disposables.push( commands.registerCommand(Commands.Exec_In_REPL, async (uri: Uri) => { const interpreter = await interpreterService.getActiveInterpreter(uri); @@ -68,6 +71,7 @@ export async function registerReplCommands(disposables: Disposable[], interprete const ourResource = Uri.from({ scheme: 'untitled', path: 'repl.interactive' }); // How to go from user clicking Run Python --> Run selection/line via Python REPL -> IW opening const notebookDocument = await workspace.openNotebookDocument(ourResource); + // We want to keep notebookEditor, whenever we want to run. // Find interactive window, or open it. if (!ourNotebookEditor) { @@ -77,14 +81,6 @@ export async function registerReplCommands(disposables: Disposable[], interprete } ourController!.updateNotebookAffinity(notebookDocument, NotebookControllerAffinity.Default); - // await commands.executeCommand( - // 'interactive.open', - // // Keep focus on the owning file if there is one - // { viewColum: 1, preserveFocus: true }, - // ourResource, - // ourController?.id, - // 'Python REPL', - // ); // Auto-Select Python REPL Kernel await commands.executeCommand('notebook.selectKernel', { @@ -93,74 +89,20 @@ export async function registerReplCommands(disposables: Disposable[], interprete extension: PVSC_EXTENSION_ID, }); - // Add new cell to interactive window document - const notebookCellData = new NotebookCellData(NotebookCellKind.Code, code as string, 'python'); // this is manual atm but need to pass in user input here + 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]); workspace.applyEdit(workspaceEdit); - // const notebookCellExecution = ourController!.createNotebookCellExecution( - // notebookDocument.cellAt(cellCount), - // ); - // notebookCellExecution.start(Date.now()); - // notebookCellExecution.end(true); - // Execute the cell commands.executeCommand('notebook.cell.execute', { ranges: [{ start: cellCount, end: cellCount + 1 }], document: ourResource, }); - - // event fire our executeHandler we made for notebook controller - - // NEED TO TELL TO EXECUTE THE CELL WHICH WILL CALL MY HANDLER - - // args: [ - // { - // name: 'showOptions', - // description: 'Show Options', - // schema: { - // type: 'object', - // properties: { - // 'viewColumn': { - // type: 'number', - // default: -1 - // }, - // 'preserveFocus': { - // type: 'boolean', - // default: true - // } - // }, - // } - // }, - // { - // name: 'resource', - // description: 'Interactive resource Uri', - // isOptional: true - // }, - // { - // name: 'controllerId', - // description: 'Notebook controller Id', - // isOptional: true - // }, - // { - // name: 'title', - // description: 'Notebook editor title', - // isOptional: true - // } - // ] } }), ); } - -// debugger -// read code, use tools like hover, doc -// think through - -// ask questions -// write down what you know -// write down what you don't know -// write down what you think you know From 66515c6cdd4dd8d9f8a915f8c480c3f64af2442c Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 17 Apr 2024 00:25:00 -0700 Subject: [PATCH 12/39] rename variables --- src/client/repl/replCommands.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/client/repl/replCommands.ts b/src/client/repl/replCommands.ts index 2deb52a6d77a..1bf073f9ac3d 100644 --- a/src/client/repl/replCommands.ts +++ b/src/client/repl/replCommands.ts @@ -19,8 +19,8 @@ import { IInterpreterService } from '../interpreter/contracts'; import { getMultiLineSelectionText, getSingleLineSelectionText } from '../terminals/codeExecution/helper'; import { createReplController } from './replController'; -let ourController: NotebookController | undefined; -let ourNotebookEditor: NotebookEditor | undefined; +let notebookController: NotebookController | undefined; +let notebookEditor: NotebookEditor | undefined; // TODO: Need to figure out making separate REPL for each file: // a.py in REPL. @@ -62,8 +62,8 @@ export async function registerReplCommands( if (interpreter) { const interpreterPath = interpreter.path; // How do we get instance of interactive window from Python extension? - if (!ourController) { - ourController = createReplController(interpreterPath); + if (!notebookController) { + notebookController = createReplController(interpreterPath); } const activeEditor = window.activeTextEditor as TextEditor; @@ -74,18 +74,18 @@ export async function registerReplCommands( // We want to keep notebookEditor, whenever we want to run. // Find interactive window, or open it. - if (!ourNotebookEditor) { - ourNotebookEditor = await window.showNotebookDocument(notebookDocument, { + if (!notebookEditor) { + notebookEditor = await window.showNotebookDocument(notebookDocument, { viewColumn: ViewColumn.Beside, }); } - ourController!.updateNotebookAffinity(notebookDocument, NotebookControllerAffinity.Default); + notebookController!.updateNotebookAffinity(notebookDocument, NotebookControllerAffinity.Default); // Auto-Select Python REPL Kernel await commands.executeCommand('notebook.selectKernel', { - ourNotebookEditor, - id: ourController?.id, + notebookEditor, + id: notebookController?.id, extension: PVSC_EXTENSION_ID, }); From 2fede160a7cfdf8d11d7a1945fdc4ddf14e60317 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 1 May 2024 10:54:15 -0700 Subject: [PATCH 13/39] save comment of TODO --- src/client/repl/replCommands.ts | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/client/repl/replCommands.ts b/src/client/repl/replCommands.ts index 1bf073f9ac3d..49a420bf9951 100644 --- a/src/client/repl/replCommands.ts +++ b/src/client/repl/replCommands.ts @@ -21,6 +21,8 @@ import { createReplController } from './replController'; let notebookController: NotebookController | undefined; let notebookEditor: NotebookEditor | undefined; +let allNotebookEditors: [NotebookEditor] | undefined; +let mapUriToNotebookEditor: Map | undefined; // TODO: Need to figure out making separate REPL for each file: // a.py in REPL. @@ -33,6 +35,8 @@ let notebookEditor: NotebookEditor | undefined; // TODO: figure out way to put markdown telling user kernel has been dead and need to pick again. +// TODO: FIGURE OUT WHY INTELLISENSE IS NOT WORKING + async function getSelectedTextToExecute(textEditor: TextEditor): Promise { if (!textEditor) { return undefined; @@ -68,16 +72,27 @@ 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 ourResource2 = Uri.file(uri.path); + // How to go from user clicking Run Python --> Run selection/line via Python REPL -> IW opening - const notebookDocument = await workspace.openNotebookDocument(ourResource); + const notebookDocument = await workspace.openNotebookDocument(ourResource2); // We want to keep notebookEditor, whenever we want to run. // Find interactive window, or open it. - if (!notebookEditor) { + + // if (!notebookEditor) { + // notebookEditor = await window.showNotebookDocument(notebookDocument, { + // viewColumn: ViewColumn.Beside, + // }); + // } + // Instead we need to first check if notebookEditor for given file Uri exist. + // If it doesnt, we create notebookEditor and add to Map + if (!mapUriToNotebookEditor?.get(ourResource2) || !mapUriToNotebookEditor.get(ourResource2)) { notebookEditor = await window.showNotebookDocument(notebookDocument, { viewColumn: ViewColumn.Beside, }); + mapUriToNotebookEditor?.set(ourResource2, notebookEditor); } notebookController!.updateNotebookAffinity(notebookDocument, NotebookControllerAffinity.Default); @@ -100,7 +115,7 @@ export async function registerReplCommands( // Execute the cell commands.executeCommand('notebook.cell.execute', { ranges: [{ start: cellCount, end: cellCount + 1 }], - document: ourResource, + document: ourResource2, }); } }), From 0f1488cf1aa45212c3980a6c2383d88e754b3b22 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Sun, 5 May 2024 22:53:18 -0700 Subject: [PATCH 14/39] make Run REPL work again --- src/client/repl/replCommands.ts | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/client/repl/replCommands.ts b/src/client/repl/replCommands.ts index 49a420bf9951..439ac27e0d89 100644 --- a/src/client/repl/replCommands.ts +++ b/src/client/repl/replCommands.ts @@ -72,28 +72,28 @@ 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 ourResource2 = Uri.file(uri.path); + const ourResource = Uri.from({ scheme: 'untitled', path: 'repl.interactive' }); + // const ourResource2 = Uri.file(uri.path); // How to go from user clicking Run Python --> Run selection/line via Python REPL -> IW opening - const notebookDocument = await workspace.openNotebookDocument(ourResource2); + const notebookDocument = await workspace.openNotebookDocument(ourResource); // 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, + }); + } - // if (!notebookEditor) { + // Instead we need to first check if notebookEditor for given file Uri exist. + // If it doesnt, we create notebookEditor and add to Map + // if (!mapUriToNotebookEditor?.get(ourResource2) || !mapUriToNotebookEditor.get(ourResource2)) { // notebookEditor = await window.showNotebookDocument(notebookDocument, { // viewColumn: ViewColumn.Beside, // }); + // mapUriToNotebookEditor?.set(ourResource2, notebookEditor); // } - // Instead we need to first check if notebookEditor for given file Uri exist. - // If it doesnt, we create notebookEditor and add to Map - if (!mapUriToNotebookEditor?.get(ourResource2) || !mapUriToNotebookEditor.get(ourResource2)) { - notebookEditor = await window.showNotebookDocument(notebookDocument, { - viewColumn: ViewColumn.Beside, - }); - mapUriToNotebookEditor?.set(ourResource2, notebookEditor); - } notebookController!.updateNotebookAffinity(notebookDocument, NotebookControllerAffinity.Default); @@ -115,7 +115,7 @@ export async function registerReplCommands( // Execute the cell commands.executeCommand('notebook.cell.execute', { ranges: [{ start: cellCount, end: cellCount + 1 }], - document: ourResource2, + document: ourResource, }); } }), From c3a30286fc3c7643b1c1682f0687db62da6cf694 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Mon, 6 May 2024 19:47:55 -0700 Subject: [PATCH 15/39] save progress 05/06 --- python_files/python_server.py | 81 +++++++++++++++++++++++-------- src/client/common/types.ts | 1 + src/client/extensionActivation.ts | 11 ++++- src/client/repl/pythonServer.ts | 8 +++ src/client/repl/replController.ts | 8 +++ 5 files changed, 87 insertions(+), 22 deletions(-) diff --git a/python_files/python_server.py b/python_files/python_server.py index 7415fef1a3c0..3174fd63c4a8 100644 --- a/python_files/python_server.py +++ b/python_files/python_server.py @@ -1,18 +1,24 @@ -# import debugpy -# debugpy.connect(5678) +import debugpy +debugpy.connect(5678) import sys import json import contextlib import io +from threading import Thread import traceback +is_interrupted = False +EXECUTE_QUEUE = [] +STDIN = sys.stdin +STDOUT = sys.stdout +STDERR = sys.stderr def send_message(msg: str): length_msg = len(msg) - sys.stdout.buffer.write( + STDOUT.buffer.write( f"Content-Length: {length_msg}\r\n\r\n{msg}".encode(encoding="utf-8") ) - sys.stdout.buffer.flush() + STDOUT.buffer.flush() def print_log(msg: str): @@ -24,29 +30,38 @@ def send_response(response: dict, response_id: int): def exec_function(user_input): + try: compile(user_input, "", "eval") except SyntaxError: return exec return eval +# have to run execute in different thread +# interrupt will kill the thread. + +def execute(): -def execute(user_globals, request): - str_output = CustomIO("", encoding="utf-8") - str_error = CustomIO("", encoding="utf-8") + while EXECUTE_QUEUE: + request = EXECUTE_QUEUE.pop(0) - with redirect_io("stdout", str_output): - with redirect_io("stderr", str_error): - str_input = CustomIO("", encoding="utf-8", newline="\n") - with redirect_io("stdin", str_input): - user_output_globals = exec_user_input( - request["id"], request["params"], user_globals - ) - send_response(str_output.get_value(), request["id"]) - return user_output_globals + str_output = CustomIO("", encoding="utf-8") + str_error = CustomIO("", encoding="utf-8") + + with redirect_io("stdout", str_output): + with redirect_io("stderr", str_error): + str_input = CustomIO("", encoding="utf-8", newline="\n") + with redirect_io("stdin", str_input): + user_output_globals = exec_user_input( + request["id"], request["params"], user_globals + ) + send_response(str_output.get_value(), request["id"]) + user_globals.update(user_output_globals) def exec_user_input(request_id, user_input, user_globals): + + # have to do redirection user_input = user_input[0] if isinstance(user_input, list) else user_input user_globals = user_globals.copy() @@ -92,25 +107,49 @@ def redirect_io(stream: str, new_stream): def get_headers(): headers = {} - while line := sys.stdin.readline().strip(): + while line := STDIN.readline().strip(): name, value = line.split(":", 1) headers[name] = value.strip() return headers +# execute_queue.append({"id": 1, "params": "print('hello')"}) + if __name__ == "__main__": user_globals = {} - while not sys.stdin.closed: + thread = None + + while not STDIN.closed: try: headers = get_headers() content_length = int(headers.get("Content-Length", 0)) - +# just one execute thread +# queue execute items on that thread if content_length: - request_json = json.loads(sys.stdin.read(content_length)) + request_text = STDIN.read(content_length) # make sure Im getting right content + request_json = json.loads(request_text) if request_json["method"] == "execute": - user_globals = execute(user_globals, request_json) + EXECUTE_QUEUE.append(request_json) + if thread is None or not thread.is_alive(): + thread = Thread(target=execute) + thread.start() + # execute_queue.append(request_json) # instead of directly calling execute, create another thread and run execute inside that thread + elif request_json["method"] == "interrupt": + + # kill 'thread' + thread._stop() # THIS IS NOT WORKING + + + # set thread as empty + thread = None + # clear execute queue + EXECUTE_QUEUE.clear() + elif request_json["method"] == "exit": sys.exit(0) except Exception as e: print_log(str(e)) + dummy_var = 'I want to see previous' + +# problem is not able to send interrupt to right thread or kill the thread directly. diff --git a/src/client/common/types.ts b/src/client/common/types.ts index 67fcf5c7b700..8edc76ff2bff 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -200,6 +200,7 @@ export interface ITerminalSettings { export interface IREPLSettings { readonly enableREPLSmartSend: boolean; + readonly enableIWREPL: boolean; } export interface IExperiments { diff --git a/src/client/extensionActivation.ts b/src/client/extensionActivation.ts index 9b30609ad333..6ded3b5d0411 100644 --- a/src/client/extensionActivation.ts +++ b/src/client/extensionActivation.ts @@ -3,7 +3,7 @@ 'use strict'; -import { DebugConfigurationProvider, debug, languages, window } from 'vscode'; +import { DebugConfigurationProvider, debug, languages, window, workspace } from 'vscode'; import { registerTypes as activationRegisterTypes } from './activation/serviceRegistry'; import { IExtensionActivationManager } from './activation/types'; @@ -20,6 +20,7 @@ import { IInterpreterPathService, ILogOutputChannel, IPathUtils, + IREPLSettings, } from './common/types'; import { noop } from './common/utils/misc'; import { registerTypes as debugConfigurationRegisterTypes } from './debugger/extension/serviceRegistry'; @@ -106,6 +107,14 @@ export function activateFeatures(ext: ExtensionState, _components: Components): interpreterService, pathUtils, ); + // uncomment + // const config = workspace.getConfiguration(); + // const replSettings: IREPLSettings | undefined = config.get('python.REPL'); + + // if (replSettings && replSettings.enableIWREPL) { + // registerReplCommands(ext.disposables, interpreterService); + // } + // uncomment registerReplCommands(ext.disposables, interpreterService); } diff --git a/src/client/repl/pythonServer.ts b/src/client/repl/pythonServer.ts index 065742f4eb71..4636b1504ec5 100644 --- a/src/client/repl/pythonServer.ts +++ b/src/client/repl/pythonServer.ts @@ -11,6 +11,7 @@ const SERVER_PATH = path.join(EXTENSION_ROOT_DIR, 'python_files', 'python_server export interface PythonServer extends Disposable { execute(code: string): Promise; + interrupt(): Promise; } class PythonServerImpl implements Disposable { @@ -29,6 +30,12 @@ class PythonServerImpl implements Disposable { return this.connection.sendRequest('execute', code); } + public interrupt(): Promise { + // return this.connection.sendRequest('interrupt', 'interrupt'); + // pythonServer.kill('SIGINT'); + return this.connection.sendRequest('interrupt', 'blah'); + } + public dispose(): void { this.connection.sendNotification('exit'); this.connection.dispose(); @@ -37,6 +44,7 @@ class PythonServerImpl implements Disposable { export function createPythonServer(interpreter: string[]): PythonServer { const pythonServer = ch.spawn(interpreter[0], [...interpreter.slice(1), SERVER_PATH]); + pythonServer.stderr.on('data', (data) => { console.error(data.toString()); }); diff --git a/src/client/repl/replController.ts b/src/client/repl/replController.ts index 63f17b150d55..3ad588fbef3e 100644 --- a/src/client/repl/replController.ts +++ b/src/client/repl/replController.ts @@ -6,7 +6,14 @@ export function createReplController(interpreterPath: string): vscode.NotebookCo const controller = vscode.notebooks.createNotebookController('pythonREPL', 'interactive', 'Python REPL'); controller.supportedLanguages = ['python']; controller.supportsExecutionOrder = true; + controller.description = 'Python REPL'; + // let isInterrupted = false; + controller.interruptHandler = async () => { + // isInterrupted = true; + server.interrupt(); + }; + controller.executeHandler = async (cells) => { for (const cell of cells) { @@ -14,6 +21,7 @@ export function createReplController(interpreterPath: string): vscode.NotebookCo exec.start(Date.now()); try { const result = await server.execute(cell.document.getText()); + exec.replaceOutput([ new vscode.NotebookCellOutput([vscode.NotebookCellOutputItem.text(result, 'text/plain')]), ]); From 293a4763ce3bffe4fd3167bc2bbbaf627f07497f Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 7 May 2024 10:27:32 -0700 Subject: [PATCH 16/39] fix typescript test --- .vscode/settings.json | 3 ++- src/client/extensionActivation.ts | 2 +- src/client/repl/replCommands.ts | 4 ++-- src/test/terminals/codeExecution/helper.test.ts | 4 +++- src/test/terminals/codeExecution/smartSend.test.ts | 4 +++- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 89959f33b6b1..811c386ad20a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -67,5 +67,6 @@ "python_files/tests" ], "python.testing.unittestEnabled": false, - "python.testing.pytestEnabled": true + "python.testing.pytestEnabled": true, + } diff --git a/src/client/extensionActivation.ts b/src/client/extensionActivation.ts index 6ded3b5d0411..e1c292582d07 100644 --- a/src/client/extensionActivation.ts +++ b/src/client/extensionActivation.ts @@ -3,7 +3,7 @@ 'use strict'; -import { DebugConfigurationProvider, debug, languages, window, workspace } from 'vscode'; +import { DebugConfigurationProvider, debug, languages, window } from 'vscode'; import { registerTypes as activationRegisterTypes } from './activation/serviceRegistry'; import { IExtensionActivationManager } from './activation/types'; diff --git a/src/client/repl/replCommands.ts b/src/client/repl/replCommands.ts index 439ac27e0d89..277a05abdf19 100644 --- a/src/client/repl/replCommands.ts +++ b/src/client/repl/replCommands.ts @@ -21,8 +21,8 @@ import { createReplController } from './replController'; let notebookController: NotebookController | undefined; let notebookEditor: NotebookEditor | undefined; -let allNotebookEditors: [NotebookEditor] | undefined; -let mapUriToNotebookEditor: Map | undefined; +// let allNotebookEditors: [NotebookEditor] | undefined; +// let mapUriToNotebookEditor: Map | undefined; // TODO: Need to figure out making separate REPL for each file: // a.py in REPL. diff --git a/src/test/terminals/codeExecution/helper.test.ts b/src/test/terminals/codeExecution/helper.test.ts index 2ea00e77c925..9098455c968e 100644 --- a/src/test/terminals/codeExecution/helper.test.ts +++ b/src/test/terminals/codeExecution/helper.test.ts @@ -110,7 +110,9 @@ suite('Terminal - Code Execution Helper', () => { .setup((c) => c.get(TypeMoq.It.isValue(IActiveResourceService))) .returns(() => activeResourceService.object); activeResourceService.setup((a) => a.getActiveResource()).returns(() => resource); - pythonSettings.setup((s) => s.REPL).returns(() => ({ enableREPLSmartSend: false, REPLSmartSend: false })); + pythonSettings + .setup((s) => s.REPL) + .returns(() => ({ enableREPLSmartSend: false, REPLSmartSend: false, enableIWREPL: 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 f93df2ac11ed..ba5101332bf8 100644 --- a/src/test/terminals/codeExecution/smartSend.test.ts +++ b/src/test/terminals/codeExecution/smartSend.test.ts @@ -107,7 +107,9 @@ suite('REPL - Smart Send', () => { .returns(() => activeResourceService.object); activeResourceService.setup((a) => a.getActiveResource()).returns(() => resource); - pythonSettings.setup((s) => s.REPL).returns(() => ({ enableREPLSmartSend: true, REPLSmartSend: true })); + pythonSettings + .setup((s) => s.REPL) + .returns(() => ({ enableREPLSmartSend: true, REPLSmartSend: true, enableIWREPL: false })); configurationService.setup((x) => x.getSettings(TypeMoq.It.isAny())).returns(() => pythonSettings.object); From 3b380729708cda149afb967e95b38f8e5eadf4c7 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 7 May 2024 10:31:31 -0700 Subject: [PATCH 17/39] typescript please --- python_files/python_server.py | 6 +++--- src/client/extensionActivation.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/python_files/python_server.py b/python_files/python_server.py index 3174fd63c4a8..9e93880817ad 100644 --- a/python_files/python_server.py +++ b/python_files/python_server.py @@ -1,5 +1,5 @@ -import debugpy -debugpy.connect(5678) +# import debugpy +# debugpy.connect(5678) import sys import json import contextlib @@ -137,7 +137,7 @@ def get_headers(): elif request_json["method"] == "interrupt": # kill 'thread' - thread._stop() # THIS IS NOT WORKING + # thread._stop() # THIS IS NOT WORKING # set thread as empty diff --git a/src/client/extensionActivation.ts b/src/client/extensionActivation.ts index e1c292582d07..359a5fea9719 100644 --- a/src/client/extensionActivation.ts +++ b/src/client/extensionActivation.ts @@ -20,7 +20,7 @@ import { IInterpreterPathService, ILogOutputChannel, IPathUtils, - IREPLSettings, + // IREPLSettings, } from './common/types'; import { noop } from './common/utils/misc'; import { registerTypes as debugConfigurationRegisterTypes } from './debugger/extension/serviceRegistry'; From e248afd2904b357e7ab96a745147a7a1e4feb73b Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 7 May 2024 10:40:23 -0700 Subject: [PATCH 18/39] fix python error --- python_files/python_server.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python_files/python_server.py b/python_files/python_server.py index 9e93880817ad..9fcf179ef3dc 100644 --- a/python_files/python_server.py +++ b/python_files/python_server.py @@ -1,5 +1,5 @@ -# import debugpy -# debugpy.connect(5678) +import debugpy +debugpy.connect(5678) import sys import json import contextlib @@ -25,7 +25,7 @@ def print_log(msg: str): send_message(json.dumps({"jsonrpc": "2.0", "method": "log", "params": msg})) -def send_response(response: dict, response_id: int): +def send_response(response: str, response_id: int): send_message(json.dumps({"jsonrpc": "2.0", "id": response_id, "result": response})) @@ -150,6 +150,6 @@ def get_headers(): except Exception as e: print_log(str(e)) - dummy_var = 'I want to see previous' + # problem is not able to send interrupt to right thread or kill the thread directly. From f329dd1dcf42cb0095a1df76fb4656b6828885fe Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 7 May 2024 10:45:16 -0700 Subject: [PATCH 19/39] comment debugpy --- python_files/python_server.py | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/python_files/python_server.py b/python_files/python_server.py index 9fcf179ef3dc..9ae6f48b99e8 100644 --- a/python_files/python_server.py +++ b/python_files/python_server.py @@ -1,5 +1,5 @@ -import debugpy -debugpy.connect(5678) +# import debugpy +# debugpy.connect(5678) import sys import json import contextlib @@ -13,11 +13,10 @@ STDOUT = sys.stdout STDERR = sys.stderr + def send_message(msg: str): length_msg = len(msg) - STDOUT.buffer.write( - f"Content-Length: {length_msg}\r\n\r\n{msg}".encode(encoding="utf-8") - ) + STDOUT.buffer.write(f"Content-Length: {length_msg}\r\n\r\n{msg}".encode(encoding="utf-8")) STDOUT.buffer.flush() @@ -30,18 +29,18 @@ def send_response(response: str, response_id: int): def exec_function(user_input): - try: compile(user_input, "", "eval") except SyntaxError: return exec return eval + # have to run execute in different thread # interrupt will kill the thread. -def execute(): +def execute(): while EXECUTE_QUEUE: request = EXECUTE_QUEUE.pop(0) @@ -60,8 +59,6 @@ def execute(): def exec_user_input(request_id, user_input, user_globals): - - # have to do redirection user_input = user_input[0] if isinstance(user_input, list) else user_input user_globals = user_globals.copy() @@ -123,10 +120,10 @@ def get_headers(): try: headers = get_headers() content_length = int(headers.get("Content-Length", 0)) -# just one execute thread -# queue execute items on that thread + # just one execute thread + # queue execute items on that thread if content_length: - request_text = STDIN.read(content_length) # make sure Im getting right content + request_text = STDIN.read(content_length) # make sure Im getting right content request_json = json.loads(request_text) if request_json["method"] == "execute": EXECUTE_QUEUE.append(request_json) @@ -135,11 +132,9 @@ def get_headers(): thread.start() # execute_queue.append(request_json) # instead of directly calling execute, create another thread and run execute inside that thread elif request_json["method"] == "interrupt": - # kill 'thread' # thread._stop() # THIS IS NOT WORKING - # set thread as empty thread = None # clear execute queue From 0bb23cf226a84fcb884ed02e39d36e664d8d4cce Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 7 May 2024 10:47:22 -0700 Subject: [PATCH 20/39] stop modifying settings.json --- .vscode/settings.json | 1 - 1 file changed, 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 811c386ad20a..f18c57a6b723 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -68,5 +68,4 @@ ], "python.testing.unittestEnabled": false, "python.testing.pytestEnabled": true, - } From 19cee6631a0f9e281dceb5c837c3378016563b47 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 7 May 2024 10:53:09 -0700 Subject: [PATCH 21/39] try to fix lint again --- python_files/python_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python_files/python_server.py b/python_files/python_server.py index 9ae6f48b99e8..3c1e2e7f61ce 100644 --- a/python_files/python_server.py +++ b/python_files/python_server.py @@ -78,7 +78,7 @@ class CustomIO(io.TextIOWrapper): name = None - def __init__(self, name, encoding="utf-8", newline=None): + def __init__(self, name=None, encoding="utf-8", newline=None): self._buffer = io.BytesIO() self._buffer.name = name super().__init__(self._buffer, encoding=encoding, newline=newline) From a4f7783826542a6918145e6a0c27f4c9d52ebb48 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 7 May 2024 10:55:58 -0700 Subject: [PATCH 22/39] stop modifying setttings.json --- .vscode/settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index f18c57a6b723..89959f33b6b1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -67,5 +67,5 @@ "python_files/tests" ], "python.testing.unittestEnabled": false, - "python.testing.pytestEnabled": true, + "python.testing.pytestEnabled": true } From 7258a5f86de7ae121c0d9200355144837a7ac41a Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 7 May 2024 11:07:49 -0700 Subject: [PATCH 23/39] fix lint via name='' --- python_files/python_server.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/python_files/python_server.py b/python_files/python_server.py index 3c1e2e7f61ce..743b6b88d722 100644 --- a/python_files/python_server.py +++ b/python_files/python_server.py @@ -76,9 +76,10 @@ def exec_user_input(request_id, user_input, user_globals): class CustomIO(io.TextIOWrapper): """Custom stream object to replace stdio.""" - name = None + # BytesIO does not contain a "name" field. Setting to None gives Pyright warning. + name = "" - def __init__(self, name=None, encoding="utf-8", newline=None): + def __init__(self, name, encoding="utf-8", newline=None): self._buffer = io.BytesIO() self._buffer.name = name super().__init__(self._buffer, encoding=encoding, newline=newline) From b6a8229b95c465eeb857db93de32bd23223d84d9 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 7 May 2024 11:10:40 -0700 Subject: [PATCH 24/39] format via prettier --- src/client/repl/replController.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/client/repl/replController.ts b/src/client/repl/replController.ts index 3ad588fbef3e..9778f4d3afd7 100644 --- a/src/client/repl/replController.ts +++ b/src/client/repl/replController.ts @@ -14,7 +14,6 @@ export function createReplController(interpreterPath: string): vscode.NotebookCo server.interrupt(); }; - controller.executeHandler = async (cells) => { for (const cell of cells) { const exec = controller.createNotebookCellExecution(cell); From 9f5deae32d2b3e33f4402335d666c38ad14bb3a2 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 7 May 2024 12:05:00 -0700 Subject: [PATCH 25/39] MAKE INTERRUPT WORK --- python_files/python_server.py | 59 ++++++++----------------------- src/client/repl/pythonServer.ts | 25 +++++++------ src/client/repl/replController.ts | 3 +- 3 files changed, 28 insertions(+), 59 deletions(-) diff --git a/python_files/python_server.py b/python_files/python_server.py index 743b6b88d722..a4e9904879a4 100644 --- a/python_files/python_server.py +++ b/python_files/python_server.py @@ -4,11 +4,8 @@ import json import contextlib import io -from threading import Thread import traceback -is_interrupted = False -EXECUTE_QUEUE = [] STDIN = sys.stdin STDOUT = sys.stdout STDERR = sys.stderr @@ -36,29 +33,20 @@ def exec_function(user_input): return eval -# have to run execute in different thread -# interrupt will kill the thread. +def execute(request, user_globals): + str_output = CustomIO("", encoding="utf-8") + str_error = CustomIO("", encoding="utf-8") + with redirect_io("stdout", str_output): + with redirect_io("stderr", str_error): + str_input = CustomIO("", encoding="utf-8", newline="\n") + with redirect_io("stdin", str_input): + user_output_globals = exec_user_input(request["params"], user_globals) + send_response(str_output.get_value(), request["id"]) + user_globals.update(user_output_globals) -def execute(): - while EXECUTE_QUEUE: - request = EXECUTE_QUEUE.pop(0) - str_output = CustomIO("", encoding="utf-8") - str_error = CustomIO("", encoding="utf-8") - - with redirect_io("stdout", str_output): - with redirect_io("stderr", str_error): - str_input = CustomIO("", encoding="utf-8", newline="\n") - with redirect_io("stdin", str_input): - user_output_globals = exec_user_input( - request["id"], request["params"], user_globals - ) - send_response(str_output.get_value(), request["id"]) - user_globals.update(user_output_globals) - - -def exec_user_input(request_id, user_input, user_globals): +def exec_user_input(user_input, user_globals): # have to do redirection user_input = user_input[0] if isinstance(user_input, list) else user_input user_globals = user_globals.copy() @@ -68,6 +56,8 @@ def exec_user_input(request_id, user_input, user_globals): retval = callable(user_input, user_globals) if retval is not None: print(retval) + except KeyboardInterrupt: + print(traceback.format_exc()) except Exception: print(traceback.format_exc()) return user_globals @@ -111,41 +101,22 @@ def get_headers(): return headers -# execute_queue.append({"id": 1, "params": "print('hello')"}) - if __name__ == "__main__": user_globals = {} - thread = None while not STDIN.closed: try: headers = get_headers() content_length = int(headers.get("Content-Length", 0)) - # just one execute thread - # queue execute items on that thread + if content_length: request_text = STDIN.read(content_length) # make sure Im getting right content request_json = json.loads(request_text) if request_json["method"] == "execute": - EXECUTE_QUEUE.append(request_json) - if thread is None or not thread.is_alive(): - thread = Thread(target=execute) - thread.start() - # execute_queue.append(request_json) # instead of directly calling execute, create another thread and run execute inside that thread - elif request_json["method"] == "interrupt": - # kill 'thread' - # thread._stop() # THIS IS NOT WORKING - - # set thread as empty - thread = None - # clear execute queue - EXECUTE_QUEUE.clear() + execute(request_json, user_globals) elif request_json["method"] == "exit": sys.exit(0) except Exception as e: print_log(str(e)) - - -# problem is not able to send interrupt to right thread or kill the thread directly. diff --git a/src/client/repl/pythonServer.ts b/src/client/repl/pythonServer.ts index 4636b1504ec5..362ea7371761 100644 --- a/src/client/repl/pythonServer.ts +++ b/src/client/repl/pythonServer.ts @@ -3,19 +3,17 @@ import * as ch from 'child_process'; import * as rpc from 'vscode-jsonrpc/node'; import { Disposable } from 'vscode'; import { EXTENSION_ROOT_DIR } from '../constants'; +import { traceError, traceLog } from '../logging'; -// const SERVER_PATH = path.join(__dirname, '...', 'python_files', 'python_server.py'); const SERVER_PATH = path.join(EXTENSION_ROOT_DIR, 'python_files', 'python_server.py'); -// const SERVER_PATH = path.join(__dirname, '..', '..', '..', 'pythonFiles', 'python_server.py'); - export interface PythonServer extends Disposable { execute(code: string): Promise; - interrupt(): Promise; + interrupt(): void; } class PythonServerImpl implements Disposable { - constructor(private connection: rpc.MessageConnection) { + constructor(private connection: rpc.MessageConnection, private pythonServer: ch.ChildProcess) { this.initialize(); } @@ -30,10 +28,10 @@ class PythonServerImpl implements Disposable { return this.connection.sendRequest('execute', code); } - public interrupt(): Promise { - // return this.connection.sendRequest('interrupt', 'interrupt'); - // pythonServer.kill('SIGINT'); - return this.connection.sendRequest('interrupt', 'blah'); + public interrupt(): void { + if (this.pythonServer.kill('SIGINT')) { + traceLog('Python server interrupted'); + } } public dispose(): void { @@ -46,17 +44,18 @@ export function createPythonServer(interpreter: string[]): PythonServer { const pythonServer = ch.spawn(interpreter[0], [...interpreter.slice(1), SERVER_PATH]); pythonServer.stderr.on('data', (data) => { - console.error(data.toString()); + traceError(data.toString()); }); pythonServer.on('exit', (code) => { - console.error(`Python server exited with code ${code}`); + traceError(`Python server exited with code ${code}`); }); pythonServer.on('error', (err) => { - console.error(err); + traceError(err); }); const connection = rpc.createMessageConnection( new rpc.StreamMessageReader(pythonServer.stdout), new rpc.StreamMessageWriter(pythonServer.stdin), ); - return new PythonServerImpl(connection); + + return new PythonServerImpl(connection, pythonServer); } diff --git a/src/client/repl/replController.ts b/src/client/repl/replController.ts index 9778f4d3afd7..f7ee7e6d486c 100644 --- a/src/client/repl/replController.ts +++ b/src/client/repl/replController.ts @@ -8,9 +8,8 @@ export function createReplController(interpreterPath: string): vscode.NotebookCo controller.supportsExecutionOrder = true; controller.description = 'Python REPL'; - // let isInterrupted = false; + controller.interruptHandler = async () => { - // isInterrupted = true; server.interrupt(); }; From 03062bd402a1b2c476611f232d668d2736d627a7 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 7 May 2024 12:46:05 -0700 Subject: [PATCH 26/39] get rid of _buffer.name = name --- python_files/python_server.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/python_files/python_server.py b/python_files/python_server.py index a4e9904879a4..a66a1cd21c27 100644 --- a/python_files/python_server.py +++ b/python_files/python_server.py @@ -66,12 +66,8 @@ def exec_user_input(user_input, user_globals): class CustomIO(io.TextIOWrapper): """Custom stream object to replace stdio.""" - # BytesIO does not contain a "name" field. Setting to None gives Pyright warning. - name = "" - def __init__(self, name, encoding="utf-8", newline=None): self._buffer = io.BytesIO() - self._buffer.name = name super().__init__(self._buffer, encoding=encoding, newline=newline) def close(self): From bc9637c4ca1aecee9a8cc7d28e41177e5cd066d5 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 8 May 2024 11:52:29 -0700 Subject: [PATCH 27/39] show invalid interpreter when no interpreter is selected --- src/client/repl/replCommands.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/client/repl/replCommands.ts b/src/client/repl/replCommands.ts index 277a05abdf19..8000c0e12653 100644 --- a/src/client/repl/replCommands.ts +++ b/src/client/repl/replCommands.ts @@ -15,6 +15,7 @@ import { } 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 { createReplController } from './replController'; @@ -63,6 +64,10 @@ export async function registerReplCommands( disposables.push( commands.registerCommand(Commands.Exec_In_REPL, async (uri: Uri) => { const interpreter = await interpreterService.getActiveInterpreter(uri); + if (!interpreter) { + commands.executeCommand(Commands.TriggerEnvironmentSelection, uri).then(noop, noop); + return; + } if (interpreter) { const interpreterPath = interpreter.path; // How do we get instance of interactive window from Python extension? From fa76d001a5dd516fb7d981a614477a9f36424e8a Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Thu, 9 May 2024 13:35:41 -0700 Subject: [PATCH 28/39] wrap around experiment, context key --- package.json | 2 +- src/client/common/experiments/groups.ts | 5 +++++ src/client/extensionActivation.ts | 17 +++++++++++++++-- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 100756802e90..f27f1dc24d00 100644 --- a/package.json +++ b/package.json @@ -1439,7 +1439,7 @@ { "command": "python.execInREPL", "group": "Python", - "when": "editorFocus && editorLangId == python && !virtualWorkspace && shellExecutionSupported" + "when": "editorFocus && editorLangId == python && !virtualWorkspace && shellExecutionSupported && pythonRunREPL" } ], "editor/title": [ diff --git a/src/client/common/experiments/groups.ts b/src/client/common/experiments/groups.ts index d43f376ddc87..3402605cd1ca 100644 --- a/src/client/common/experiments/groups.ts +++ b/src/client/common/experiments/groups.ts @@ -24,3 +24,8 @@ export enum EnableTestAdapterRewrite { export enum RecommendTensobardExtension { experiment = 'pythonRecommendTensorboardExt', } + +// Experiment to enable running Python REPL using IW. +export enum EnableRunREPL { + experiment = 'pythonRunREPL', +} diff --git a/src/client/extensionActivation.ts b/src/client/extensionActivation.ts index 359a5fea9719..1fed0ffc802b 100644 --- a/src/client/extensionActivation.ts +++ b/src/client/extensionActivation.ts @@ -3,7 +3,7 @@ 'use strict'; -import { DebugConfigurationProvider, debug, languages, window } from 'vscode'; +import { DebugConfigurationProvider, debug, languages, window, commands } from 'vscode'; import { registerTypes as activationRegisterTypes } from './activation/serviceRegistry'; import { IExtensionActivationManager } from './activation/types'; @@ -16,6 +16,7 @@ import { IFileSystem } from './common/platform/types'; import { IConfigurationService, IDisposableRegistry, + IExperimentService, IExtensions, IInterpreterPathService, ILogOutputChannel, @@ -54,6 +55,7 @@ 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'; export async function activateComponents( // `ext` is passed to any extra activation funcs. @@ -115,7 +117,18 @@ export function activateFeatures(ext: ExtensionState, _components: Components): // registerReplCommands(ext.disposables, interpreterService); // } // uncomment - registerReplCommands(ext.disposables, interpreterService); + + // Only register if they are in experiment for pythonRunREPL. + 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); // Register regardless } /// ////////////////////////// From 6ff2dce9c1e180d421a72143996d36737c7aedac Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Fri, 10 May 2024 02:44:00 -0700 Subject: [PATCH 29/39] clean up --- src/client/repl/replCommands.ts | 28 ++-------------------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/src/client/repl/replCommands.ts b/src/client/repl/replCommands.ts index 8000c0e12653..fb1ac6d582c9 100644 --- a/src/client/repl/replCommands.ts +++ b/src/client/repl/replCommands.ts @@ -22,22 +22,8 @@ import { createReplController } from './replController'; let notebookController: NotebookController | undefined; let notebookEditor: NotebookEditor | undefined; -// let allNotebookEditors: [NotebookEditor] | undefined; -// let mapUriToNotebookEditor: Map | undefined; - -// TODO: Need to figure out making separate REPL for each file: -// a.py in REPL. -// b.py run in REPL -// MAPPING Uri to notebookEditor if we want separate REPL for each file. -// Currently: Everything gets sent into one single REPL. - -// TODO: when you reload window, is the REPL still binded to same Python file? -// cache binding uri to to REPL instance or notebookEditor. - // TODO: figure out way to put markdown telling user kernel has been dead and need to pick again. -// TODO: FIGURE OUT WHY INTELLISENSE IS NOT WORKING - async function getSelectedTextToExecute(textEditor: TextEditor): Promise { if (!textEditor) { return undefined; @@ -70,7 +56,7 @@ export async function registerReplCommands( } if (interpreter) { const interpreterPath = interpreter.path; - // How do we get instance of interactive window from Python extension? + if (!notebookController) { notebookController = createReplController(interpreterPath); } @@ -78,10 +64,9 @@ export async function registerReplCommands( const code = await getSelectedTextToExecute(activeEditor); const ourResource = Uri.from({ scheme: 'untitled', path: 'repl.interactive' }); - // const ourResource2 = Uri.file(uri.path); - // How to go from user clicking Run Python --> Run selection/line via Python REPL -> IW opening 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. @@ -91,15 +76,6 @@ export async function registerReplCommands( }); } - // Instead we need to first check if notebookEditor for given file Uri exist. - // If it doesnt, we create notebookEditor and add to Map - // if (!mapUriToNotebookEditor?.get(ourResource2) || !mapUriToNotebookEditor.get(ourResource2)) { - // notebookEditor = await window.showNotebookDocument(notebookDocument, { - // viewColumn: ViewColumn.Beside, - // }); - // mapUriToNotebookEditor?.set(ourResource2, notebookEditor); - // } - notebookController!.updateNotebookAffinity(notebookDocument, NotebookControllerAffinity.Default); // Auto-Select Python REPL Kernel From b8e8fb01c6ce00c89597c52aaa5d4c56e96b49ee Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Fri, 10 May 2024 02:55:00 -0700 Subject: [PATCH 30/39] fixing merge conflict --- src/client/common/experiments/groups.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/client/common/experiments/groups.ts b/src/client/common/experiments/groups.ts index 3402605cd1ca..543b1e27516f 100644 --- a/src/client/common/experiments/groups.ts +++ b/src/client/common/experiments/groups.ts @@ -25,6 +25,12 @@ export enum RecommendTensobardExtension { experiment = 'pythonRecommendTensorboardExt', } +// Experiment to enable triggering venv creation when users install with `pip` +// in a global environment +export enum CreateEnvOnPipInstallTrigger { + experiment = 'pythonCreateEnvOnPipInstall', +} + // Experiment to enable running Python REPL using IW. export enum EnableRunREPL { experiment = 'pythonRunREPL', From 1a378bb9efd61b7597e2dd93d137639d92582d74 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Fri, 10 May 2024 03:00:51 -0700 Subject: [PATCH 31/39] prettier remove blank space --- src/client/common/experiments/groups.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/client/common/experiments/groups.ts b/src/client/common/experiments/groups.ts index 3f856a1dfa2c..543b1e27516f 100644 --- a/src/client/common/experiments/groups.ts +++ b/src/client/common/experiments/groups.ts @@ -35,4 +35,3 @@ export enum CreateEnvOnPipInstallTrigger { export enum EnableRunREPL { experiment = 'pythonRunREPL', } - From a11c1c7dd140dacd38bd53142be8b5679c258959 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Fri, 10 May 2024 03:03:02 -0700 Subject: [PATCH 32/39] cleanup extensionActivation.ts --- src/client/extensionActivation.ts | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/client/extensionActivation.ts b/src/client/extensionActivation.ts index 1fed0ffc802b..5086830df77c 100644 --- a/src/client/extensionActivation.ts +++ b/src/client/extensionActivation.ts @@ -109,16 +109,8 @@ export function activateFeatures(ext: ExtensionState, _components: Components): interpreterService, pathUtils, ); - // uncomment - // const config = workspace.getConfiguration(); - // const replSettings: IREPLSettings | undefined = config.get('python.REPL'); - // if (replSettings && replSettings.enableIWREPL) { - // registerReplCommands(ext.disposables, interpreterService); - // } - // uncomment - - // Only register if they are in experiment for pythonRunREPL. + // Register native REPL context menu when in experiment const experimentService = ext.legacyIOC.serviceContainer.get(IExperimentService); commands.executeCommand('setContext', 'pythonRunREPL', false); if (experimentService) { @@ -128,7 +120,6 @@ export function activateFeatures(ext: ExtensionState, _components: Components): commands.executeCommand('setContext', 'pythonRunREPL', true); } } - // registerReplCommands(ext.disposables, interpreterService); // Register regardless } /// ////////////////////////// From d9ba156cc3021c82f0649217cf0317715cc6523c Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Fri, 10 May 2024 03:16:35 -0700 Subject: [PATCH 33/39] more cleanup --- src/client/extensionActivation.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/client/extensionActivation.ts b/src/client/extensionActivation.ts index 5086830df77c..7c582eb63239 100644 --- a/src/client/extensionActivation.ts +++ b/src/client/extensionActivation.ts @@ -21,7 +21,6 @@ import { IInterpreterPathService, ILogOutputChannel, IPathUtils, - // IREPLSettings, } from './common/types'; import { noop } from './common/utils/misc'; import { registerTypes as debugConfigurationRegisterTypes } from './debugger/extension/serviceRegistry'; From ea55d8b5d02b63b9f4962995b0a4def203f271d2 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 15 May 2024 13:29:48 -0700 Subject: [PATCH 34/39] update current status, start cleanup --- .vscode/launch.json | 2 +- python_files/python_server.py | 83 +++++++++++++++++++++++++++++---- src/client/repl/pythonServer.ts | 22 ++++++++- 3 files changed, 95 insertions(+), 12 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index a4a5104c2d22..4dc107853fc6 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -252,7 +252,7 @@ ], "compounds": [ { - "name": "Debug Test Discovery", + "name": "Debug Python and Extension", "configurations": ["Python: Attach Listen", "Extension"] } ] diff --git a/python_files/python_server.py b/python_files/python_server.py index a66a1cd21c27..9c57e2415de7 100644 --- a/python_files/python_server.py +++ b/python_files/python_server.py @@ -1,3 +1,5 @@ +from typing import Dict, List, Optional, Union + # import debugpy # debugpy.connect(5678) import sys @@ -5,10 +7,12 @@ import contextlib import io import traceback +import uuid STDIN = sys.stdin STDOUT = sys.stdout STDERR = sys.stderr +USER_GLOBALS = {} def send_message(msg: str): @@ -25,6 +29,59 @@ def send_response(response: str, response_id: int): send_message(json.dumps({"jsonrpc": "2.0", "id": response_id, "result": response})) +def send_request(params: Optional[Union[List, Dict]] = None): + request_id = uuid.uuid4().hex + if params is None: + send_message(json.dumps({"jsonrpc": "2.0", "id": request_id, "method": "input"})) + else: + send_message( + json.dumps({"jsonrpc": "2.0", "id": request_id, "method": "input", "params": params}) + ) + return request_id + + +original_input = input + + +def custom_input(prompt=""): + request_id = send_request({"prompt": prompt}) + try: + headers = get_headers() + content_length = int(headers.get("Content-Length", 0)) + + if content_length: + message_text = STDIN.read(content_length) # make sure Im getting right content + message_json = json.loads(message_text) + our_user_input = message_json["result"]["userInput"] + return our_user_input + except Exception: + print_log(traceback.format_exc()) + + +# Set input to our custom input +USER_GLOBALS["input"] = custom_input +input = custom_input + + +def handle_response(request_id): + while not STDIN.closed: + try: + headers = get_headers() + content_length = int(headers.get("Content-Length", 0)) + + if content_length: + message_text = STDIN.read(content_length) + message_json = json.loads(message_text) + our_user_input = message_json["result"]["userInput"] + if message_json["id"] == request_id: + send_response(our_user_input, message_json["id"]) + elif message_json["method"] == "exit": + sys.exit(0) + + except Exception: + print_log(traceback.format_exc()) + + def exec_function(user_input): try: compile(user_input, "", "eval") @@ -41,15 +98,13 @@ def execute(request, user_globals): with redirect_io("stderr", str_error): str_input = CustomIO("", encoding="utf-8", newline="\n") with redirect_io("stdin", str_input): - user_output_globals = exec_user_input(request["params"], user_globals) + exec_user_input(request["params"], user_globals) send_response(str_output.get_value(), request["id"]) - user_globals.update(user_output_globals) def exec_user_input(user_input, user_globals): # have to do redirection user_input = user_input[0] if isinstance(user_input, list) else user_input - user_globals = user_globals.copy() try: callable = exec_function(user_input) @@ -60,7 +115,6 @@ def exec_user_input(user_input, user_globals): print(traceback.format_exc()) except Exception: print(traceback.format_exc()) - return user_globals class CustomIO(io.TextIOWrapper): @@ -68,8 +122,19 @@ class CustomIO(io.TextIOWrapper): def __init__(self, name, encoding="utf-8", newline=None): self._buffer = io.BytesIO() + self._custom_name = name super().__init__(self._buffer, encoding=encoding, newline=newline) + def read(self, size: Optional[int] = None): + if self._custom_name == "": + request_id = send_request() + result = handle_response(request_id) + return result["result"][ + "userInput" + ] # Read from stdin, simply return user 'input' from TS side + else: + return super().read(size) + def close(self): """Provide this close method which is used by some tools.""" # This is intentionally empty. @@ -98,21 +163,19 @@ def get_headers(): if __name__ == "__main__": - user_globals = {} - while not STDIN.closed: try: headers = get_headers() content_length = int(headers.get("Content-Length", 0)) + # TODO: pull out to separate function if content_length: request_text = STDIN.read(content_length) # make sure Im getting right content request_json = json.loads(request_text) if request_json["method"] == "execute": - execute(request_json, user_globals) - + execute(request_json, USER_GLOBALS) elif request_json["method"] == "exit": sys.exit(0) - except Exception as e: - print_log(str(e)) + except Exception: + print_log(traceback.format_exc()) diff --git a/src/client/repl/pythonServer.ts b/src/client/repl/pythonServer.ts index 362ea7371761..e25ba3a25092 100644 --- a/src/client/repl/pythonServer.ts +++ b/src/client/repl/pythonServer.ts @@ -1,7 +1,7 @@ import * as path from 'path'; import * as ch from 'child_process'; import * as rpc from 'vscode-jsonrpc/node'; -import { Disposable } from 'vscode'; +import { Disposable, window } from 'vscode'; import { EXTENSION_ROOT_DIR } from '../constants'; import { traceError, traceLog } from '../logging'; @@ -10,11 +10,13 @@ const SERVER_PATH = path.join(EXTENSION_ROOT_DIR, 'python_files', 'python_server export interface PythonServer extends Disposable { execute(code: string): Promise; interrupt(): void; + input(): void; } class PythonServerImpl implements Disposable { constructor(private connection: rpc.MessageConnection, private pythonServer: ch.ChildProcess) { this.initialize(); + this.input(); } private initialize(): void { @@ -24,6 +26,24 @@ class PythonServerImpl implements Disposable { this.connection.listen(); } + // Register input handler + public input(): void { + // Register input request handler + this.connection.onRequest('input', async (request) => { + // Ask for user input via popup quick input, send it back to Python + let userPrompt = 'Enter your input here: '; + if (request && request.prompt) { + userPrompt = request.prompt; + } + const input = await window.showInputBox({ + title: 'Input Request', + prompt: userPrompt, + ignoreFocusOut: true, + }); + return { userInput: input }; + }); + } + public execute(code: string): Promise { return this.connection.sendRequest('execute', code); } From da930901acb4aeb2fb9cb0b6278b1bdc2bb71141 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 15 May 2024 13:43:04 -0700 Subject: [PATCH 35/39] remove overriding read() --- python_files/python_server.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/python_files/python_server.py b/python_files/python_server.py index 9c57e2415de7..9b50fe6dcc45 100644 --- a/python_files/python_server.py +++ b/python_files/python_server.py @@ -125,16 +125,6 @@ def __init__(self, name, encoding="utf-8", newline=None): self._custom_name = name super().__init__(self._buffer, encoding=encoding, newline=newline) - def read(self, size: Optional[int] = None): - if self._custom_name == "": - request_id = send_request() - result = handle_response(request_id) - return result["result"][ - "userInput" - ] # Read from stdin, simply return user 'input' from TS side - else: - return super().read(size) - def close(self): """Provide this close method which is used by some tools.""" # This is intentionally empty. From c7f5372dfa16b23743e1dafb5c7aadc07647f798 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 15 May 2024 13:47:58 -0700 Subject: [PATCH 36/39] fix lint --- python_files/python_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python_files/python_server.py b/python_files/python_server.py index 9b50fe6dcc45..0fd56028b48e 100644 --- a/python_files/python_server.py +++ b/python_files/python_server.py @@ -44,7 +44,7 @@ def send_request(params: Optional[Union[List, Dict]] = None): def custom_input(prompt=""): - request_id = send_request({"prompt": prompt}) + send_request({"prompt": prompt}) try: headers = get_headers() content_length = int(headers.get("Content-Length", 0)) From 289edb6407df74d446f50556758d16fcabb6b7d0 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 15 May 2024 18:30:28 -0700 Subject: [PATCH 37/39] more cleanup --- python_files/python_server.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/python_files/python_server.py b/python_files/python_server.py index 0fd56028b48e..446223459cdf 100644 --- a/python_files/python_server.py +++ b/python_files/python_server.py @@ -44,13 +44,13 @@ def send_request(params: Optional[Union[List, Dict]] = None): def custom_input(prompt=""): - send_request({"prompt": prompt}) try: + send_request({"prompt": prompt}) headers = get_headers() content_length = int(headers.get("Content-Length", 0)) if content_length: - message_text = STDIN.read(content_length) # make sure Im getting right content + message_text = STDIN.read(content_length) message_json = json.loads(message_text) our_user_input = message_json["result"]["userInput"] return our_user_input @@ -103,7 +103,6 @@ def execute(request, user_globals): def exec_user_input(user_input, user_globals): - # have to do redirection user_input = user_input[0] if isinstance(user_input, list) else user_input try: @@ -158,9 +157,8 @@ def get_headers(): headers = get_headers() content_length = int(headers.get("Content-Length", 0)) - # TODO: pull out to separate function if content_length: - request_text = STDIN.read(content_length) # make sure Im getting right content + request_text = STDIN.read(content_length) request_json = json.loads(request_text) if request_json["method"] == "execute": execute(request_json, USER_GLOBALS) From 19e73b1b2d417dce301e409a24aebc4ad5b043ed Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 15 May 2024 18:33:24 -0700 Subject: [PATCH 38/39] more cleanup --- python_files/python_server.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/python_files/python_server.py b/python_files/python_server.py index 446223459cdf..4d27a168bc4c 100644 --- a/python_files/python_server.py +++ b/python_files/python_server.py @@ -1,7 +1,5 @@ from typing import Dict, List, Optional, Union -# import debugpy -# debugpy.connect(5678) import sys import json import contextlib From 03d6ed310c3b9d2474f38f13977bbdab03c2e27a Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 15 May 2024 18:58:25 -0700 Subject: [PATCH 39/39] await workspace edit from feedback --- src/client/repl/replCommands.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/repl/replCommands.ts b/src/client/repl/replCommands.ts index fb1ac6d582c9..e7a40b01c6be 100644 --- a/src/client/repl/replCommands.ts +++ b/src/client/repl/replCommands.ts @@ -91,7 +91,7 @@ export async function registerReplCommands( const notebookEdit = NotebookEdit.insertCells(cellCount, [notebookCellData]); const workspaceEdit = new WorkspaceEdit(); workspaceEdit.set(notebookDocument.uri, [notebookEdit]); - workspace.applyEdit(workspaceEdit); + await workspace.applyEdit(workspaceEdit); // Execute the cell commands.executeCommand('notebook.cell.execute', {