diff --git a/package-lock.json b/package-lock.json index 87af2d83a205..f2034d927997 100644 --- a/package-lock.json +++ b/package-lock.json @@ -116,7 +116,7 @@ "yargs": "^15.3.1" }, "engines": { - "vscode": "^1.94.0-20240913" + "vscode": "^1.94.0-20240918" } }, "node_modules/@aashutoshrathi/word-wrap": { diff --git a/package.json b/package.json index f02bb2bf2e01..dc0887402b8c 100644 --- a/package.json +++ b/package.json @@ -25,8 +25,9 @@ "terminalDataWriteEvent", "terminalExecuteCommandEvent", "contribIssueReporter", - "notebookVariableProvider", - "codeActionAI" + "codeActionAI", + "notebookReplDocument", + "notebookVariableProvider" ], "author": { "name": "Microsoft Corporation" @@ -47,7 +48,7 @@ "theme": "dark" }, "engines": { - "vscode": "^1.94.0-20240913" + "vscode": "^1.94.0-20240918" }, "enableTelemetry": false, "keywords": [ @@ -1168,6 +1169,11 @@ { "command": "python.execInREPLEnter", "key": "enter", + "when": "!config.interactiveWindow.executeWithShiftEnter && activeEditor == 'workbench.editor.repl' && !inlineChatFocused" + }, + { + "command": "python.execInInteractiveWindowEnter", + "key": "enter", "when": "!config.interactiveWindow.executeWithShiftEnter && activeEditor == 'workbench.editor.interactive' && !inlineChatFocused" }, { diff --git a/src/client/common/application/commands.ts b/src/client/common/application/commands.ts index 36f8c6d2ac17..41bbe8b4f4ea 100644 --- a/src/client/common/application/commands.ts +++ b/src/client/common/application/commands.ts @@ -99,6 +99,7 @@ export interface ICommandNameArgumentTypeMapping extends ICommandNameWithoutArgu [Commands.Start_Native_REPL]: [undefined | Uri]; [Commands.Exec_In_REPL]: [undefined | Uri]; [Commands.Exec_In_REPL_Enter]: [undefined | Uri]; + [Commands.Exec_In_IW_Enter]: [undefined | Uri]; [Commands.Exec_In_Terminal]: [undefined, Uri]; [Commands.Exec_In_Terminal_Icon]: [undefined, Uri]; [Commands.Debug_In_Terminal]: [Uri]; diff --git a/src/client/common/constants.ts b/src/client/common/constants.ts index df585abe1653..51e38d5ef3e6 100644 --- a/src/client/common/constants.ts +++ b/src/client/common/constants.ts @@ -49,6 +49,7 @@ export namespace Commands { export const Exec_In_REPL = 'python.execInREPL'; export const Exec_Selection_In_Django_Shell = 'python.execSelectionInDjangoShell'; export const Exec_In_REPL_Enter = 'python.execInREPLEnter'; + export const Exec_In_IW_Enter = 'python.execInInteractiveWindowEnter'; export const Exec_Selection_In_Terminal = 'python.execSelectionInTerminal'; export const GetSelectedInterpreterPath = 'python.interpreterPath'; export const InstallJupyter = 'python.installJupyter'; diff --git a/src/client/repl/nativeRepl.ts b/src/client/repl/nativeRepl.ts index 8e0337f8d276..413c795e80d6 100644 --- a/src/client/repl/nativeRepl.ts +++ b/src/client/repl/nativeRepl.ts @@ -157,7 +157,7 @@ export class NativeRepl implements Disposable { this.replController.updateNotebookAffinity(this.notebookDocument, NotebookControllerAffinity.Default); await selectNotebookKernel(notebookEditor, this.replController.id, PVSC_EXTENSION_ID); if (code) { - await executeNotebookCell(this.notebookDocument, code); + await executeNotebookCell(notebookEditor, code); } } } diff --git a/src/client/repl/replCommandHandler.ts b/src/client/repl/replCommandHandler.ts index 599692a4300e..b8fe579647a1 100644 --- a/src/client/repl/replCommandHandler.ts +++ b/src/client/repl/replCommandHandler.ts @@ -12,6 +12,7 @@ import { workspace, } from 'vscode'; import { getExistingReplViewColumn } from './replUtils'; +import { PVSC_EXTENSION_ID } from '../common/constants'; /** * Function that opens/show REPL using IW UI. @@ -23,29 +24,24 @@ export async function openInteractiveREPL( notebookController: NotebookController, notebookDocument: NotebookDocument | undefined, ): Promise { - let notebookEditor: NotebookEditor | undefined; + let viewColumn = ViewColumn.Beside; // Case where NotebookDocument (REPL document already exists in the tab) if (notebookDocument) { const existingReplViewColumn = getExistingReplViewColumn(notebookDocument); - const replViewColumn = existingReplViewColumn ?? ViewColumn.Beside; - notebookEditor = await window.showNotebookDocument(notebookDocument!, { viewColumn: replViewColumn }); + viewColumn = existingReplViewColumn ?? viewColumn; } else if (!notebookDocument) { - // Case where NotebookDocument doesnt exist, open new REPL tab - const interactiveWindowObject = (await commands.executeCommand( - 'interactive.open', - { - preserveFocus: true, - viewColumn: ViewColumn.Beside, - }, - undefined, - notebookController.id, - 'Python REPL', - )) as { notebookEditor: NotebookEditor }; - notebookEditor = interactiveWindowObject.notebookEditor; - notebookDocument = interactiveWindowObject.notebookEditor.notebook; + // Case where NotebookDocument doesnt exist, create a blank one. + notebookDocument = await workspace.openNotebookDocument('jupyter-notebook'); } - return notebookEditor!; + const editor = window.showNotebookDocument(notebookDocument!, { viewColumn, asRepl: 'Python REPL' }); + await commands.executeCommand('notebook.selectKernel', { + editor, + id: notebookController.id, + extension: PVSC_EXTENSION_ID, + }); + + return editor; } /** @@ -73,13 +69,14 @@ export async function selectNotebookKernel( * @param code * @return Promise */ -export async function executeNotebookCell(notebookDocument: NotebookDocument, code: string): Promise { - const { cellCount } = notebookDocument; - await addCellToNotebook(notebookDocument, code); +export async function executeNotebookCell(notebookEditor: NotebookEditor, code: string): Promise { + const { notebook, replOptions } = notebookEditor; + const cellIndex = replOptions?.appendIndex ?? notebook.cellCount; + await addCellToNotebook(notebook, cellIndex, code); // Execute the cell commands.executeCommand('notebook.cell.execute', { - ranges: [{ start: cellCount, end: cellCount + 1 }], - document: notebookDocument.uri, + ranges: [{ start: cellIndex, end: cellIndex + 1 }], + document: notebook.uri, }); } @@ -89,11 +86,10 @@ export async function executeNotebookCell(notebookDocument: NotebookDocument, co * @param code * */ -async function addCellToNotebook(notebookDocument: NotebookDocument, code: string): Promise { +async function addCellToNotebook(notebookDocument: NotebookDocument, index: number, code: string): Promise { const notebookCellData = new NotebookCellData(NotebookCellKind.Code, code as string, 'python'); - const { cellCount } = notebookDocument!; // Add new cell to interactive window document - const notebookEdit = NotebookEdit.insertCells(cellCount, [notebookCellData]); + const notebookEdit = NotebookEdit.insertCells(index, [notebookCellData]); const workspaceEdit = new WorkspaceEdit(); workspaceEdit.set(notebookDocument!.uri, [notebookEdit]); await workspace.applyEdit(workspaceEdit); diff --git a/src/client/repl/replCommands.ts b/src/client/repl/replCommands.ts index 120ddf13effc..82b4aae4e5ee 100644 --- a/src/client/repl/replCommands.ts +++ b/src/client/repl/replCommands.ts @@ -98,29 +98,43 @@ export async function registerReplExecuteOnEnter( ): Promise { disposables.push( commandManager.registerCommand(Commands.Exec_In_REPL_Enter, async (uri: Uri) => { - const interpreter = await interpreterService.getActiveInterpreter(uri); - if (!interpreter) { - commands.executeCommand(Commands.TriggerEnvironmentSelection, uri).then(noop, noop); - return; - } + await onInputEnter(uri, 'repl.execute', interpreterService, disposables); + }), + ); + disposables.push( + commandManager.registerCommand(Commands.Exec_In_IW_Enter, async (uri: Uri) => { + await onInputEnter(uri, 'interactive.execute', interpreterService, disposables); + }), + ); +} - const nativeRepl = await getNativeRepl(interpreter, disposables); - const completeCode = await nativeRepl?.checkUserInputCompleteCode(window.activeTextEditor); - const editor = window.activeTextEditor; +async function onInputEnter( + uri: Uri, + commandName: string, + interpreterService: IInterpreterService, + disposables: Disposable[], +): Promise { + const interpreter = await interpreterService.getActiveInterpreter(uri); + if (!interpreter) { + commands.executeCommand(Commands.TriggerEnvironmentSelection, uri).then(noop, noop); + return; + } - if (editor) { - // Execute right away when complete code and Not multi-line - if (completeCode && !isMultiLineText(editor)) { - await commands.executeCommand('interactive.execute'); - } else { - insertNewLineToREPLInput(editor); + const nativeRepl = await getNativeRepl(interpreter, disposables); + const completeCode = await nativeRepl?.checkUserInputCompleteCode(window.activeTextEditor); + const editor = window.activeTextEditor; - // Handle case when user enters on blank line, just trigger interactive.execute - if (editor && editor.document.lineAt(editor.selection.active.line).text === '') { - await commands.executeCommand('interactive.execute'); - } - } + if (editor) { + // Execute right away when complete code and Not multi-line + if (completeCode && !isMultiLineText(editor)) { + await commands.executeCommand(commandName); + } else { + insertNewLineToREPLInput(editor); + + // Handle case when user enters on blank line, just trigger interactive.execute + if (editor && editor.document.lineAt(editor.selection.active.line).text === '') { + await commands.executeCommand(commandName); } - }), - ); + } + } } diff --git a/src/client/repl/replController.ts b/src/client/repl/replController.ts index 7c1f8fd0c6b2..08c2a27066a1 100644 --- a/src/client/repl/replController.ts +++ b/src/client/repl/replController.ts @@ -9,7 +9,7 @@ export function createReplController( const server = createPythonServer([interpreterPath], cwd); disposables.push(server); - const controller = vscode.notebooks.createNotebookController('pythonREPL', 'interactive', 'Python REPL'); + const controller = vscode.notebooks.createNotebookController('pythonREPL', 'jupyter-notebook', 'Python REPL'); controller.supportedLanguages = ['python']; controller.supportsExecutionOrder = true; diff --git a/types/vscode.proposed.notebookReplDocument.d.ts b/types/vscode.proposed.notebookReplDocument.d.ts new file mode 100644 index 000000000000..d78450e944a8 --- /dev/null +++ b/types/vscode.proposed.notebookReplDocument.d.ts @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + export interface NotebookDocumentShowOptions { + /** + * The notebook should be opened in a REPL editor, + * where the last cell of the notebook is an input box and the other cells are the read-only history. + * When the value is a string, it will be used as the label for the editor tab. + */ + readonly asRepl?: boolean | string | { + /** + * The label to be used for the editor tab. + */ + readonly label: string; + }; + } + + export interface NotebookEditor { + /** + * Information about the REPL editor if the notebook was opened as a repl. + */ + replOptions?: { + /** + * The index where new cells should be appended. + */ + appendIndex: number; + }; + } +}