Skip to content

Commit 9abfd30

Browse files
anthonykim1amunger
andauthored
Refactor Native REPL code (#23550)
Refactor Native REPL code. Resolves: #23632 --------- Co-authored-by: Aaron Munger <[email protected]>
1 parent 6af3d03 commit 9abfd30

File tree

4 files changed

+362
-162
lines changed

4 files changed

+362
-162
lines changed

src/client/repl/nativeRepl.ts

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// Native Repl class that holds instance of pythonServer and replController
2+
3+
import { NotebookController, NotebookControllerAffinity, NotebookDocument, TextEditor, workspace } from 'vscode';
4+
import { Disposable } from 'vscode-jsonrpc';
5+
import { PVSC_EXTENSION_ID } from '../common/constants';
6+
import { PythonEnvironment } from '../pythonEnvironments/info';
7+
import { createPythonServer, PythonServer } from './pythonServer';
8+
import { executeNotebookCell, openInteractiveREPL, selectNotebookKernel } from './replCommandHandler';
9+
import { createReplController } from './replController';
10+
11+
export class NativeRepl implements Disposable {
12+
private pythonServer: PythonServer;
13+
14+
private interpreter: PythonEnvironment;
15+
16+
private disposables: Disposable[] = [];
17+
18+
private replController: NotebookController;
19+
20+
private notebookDocument: NotebookDocument | undefined;
21+
22+
// TODO: In the future, could also have attribute of URI for file specific REPL.
23+
constructor(interpreter: PythonEnvironment) {
24+
this.interpreter = interpreter;
25+
26+
this.pythonServer = createPythonServer([interpreter.path as string]);
27+
this.replController = this.setReplController();
28+
29+
this.watchNotebookClosed();
30+
}
31+
32+
dispose(): void {
33+
this.disposables.forEach((d) => d.dispose());
34+
}
35+
36+
/**
37+
* Function that watches for Notebook Closed event.
38+
* This is for the purposes of correctly updating the notebookEditor and notebookDocument on close.
39+
*/
40+
private watchNotebookClosed(): void {
41+
this.disposables.push(
42+
workspace.onDidCloseNotebookDocument((nb) => {
43+
if (this.notebookDocument && nb.uri.toString() === this.notebookDocument.uri.toString()) {
44+
this.notebookDocument = undefined;
45+
}
46+
}),
47+
);
48+
}
49+
50+
/**
51+
* Function that check if NotebookController for REPL exists, and returns it in Singleton manner.
52+
* @returns NotebookController
53+
*/
54+
public setReplController(): NotebookController {
55+
if (!this.replController) {
56+
return createReplController(this.interpreter.path, this.disposables);
57+
}
58+
return this.replController;
59+
}
60+
61+
/**
62+
* Function that checks if native REPL's text input box contains complete code.
63+
* @param activeEditor
64+
* @param pythonServer
65+
* @returns Promise<boolean> - True if complete/Valid code is present, False otherwise.
66+
*/
67+
public async checkUserInputCompleteCode(activeEditor: TextEditor | undefined): Promise<boolean> {
68+
let completeCode = false;
69+
let userTextInput;
70+
if (activeEditor) {
71+
const { document } = activeEditor;
72+
userTextInput = document.getText();
73+
}
74+
75+
// Check if userTextInput is a complete Python command
76+
if (userTextInput) {
77+
completeCode = await this.pythonServer.checkValidCommand(userTextInput);
78+
}
79+
80+
return completeCode;
81+
}
82+
83+
/**
84+
* Function that opens interactive repl, selects kernel, and send/execute code to the native repl.
85+
* @param code
86+
*/
87+
public async sendToNativeRepl(code: string): Promise<void> {
88+
const notebookEditor = await openInteractiveREPL(this.replController, this.notebookDocument);
89+
this.notebookDocument = notebookEditor.notebook;
90+
91+
if (this.notebookDocument) {
92+
this.replController.updateNotebookAffinity(this.notebookDocument, NotebookControllerAffinity.Default);
93+
await selectNotebookKernel(notebookEditor, this.replController.id, PVSC_EXTENSION_ID);
94+
await executeNotebookCell(this.notebookDocument, code);
95+
}
96+
}
97+
}
98+
99+
let nativeRepl: NativeRepl | undefined; // In multi REPL scenario, hashmap of URI to Repl.
100+
101+
/**
102+
* Get Singleton Native REPL Instance
103+
* @param interpreter
104+
* @returns Native REPL instance
105+
*/
106+
export function getNativeRepl(interpreter: PythonEnvironment, disposables: Disposable[]): NativeRepl {
107+
if (!nativeRepl) {
108+
nativeRepl = new NativeRepl(interpreter);
109+
disposables.push(nativeRepl);
110+
}
111+
return nativeRepl;
112+
}

src/client/repl/replCommandHandler.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import {
2+
commands,
3+
window,
4+
NotebookController,
5+
NotebookEditor,
6+
ViewColumn,
7+
NotebookDocument,
8+
NotebookCellData,
9+
NotebookCellKind,
10+
NotebookEdit,
11+
WorkspaceEdit,
12+
workspace,
13+
} from 'vscode';
14+
import { getExistingReplViewColumn } from './replUtils';
15+
16+
/**
17+
* Function that opens/show REPL using IW UI.
18+
* @param notebookController
19+
* @param notebookEditor
20+
* @returns notebookEditor
21+
*/
22+
export async function openInteractiveREPL(
23+
notebookController: NotebookController,
24+
notebookDocument: NotebookDocument | undefined,
25+
): Promise<NotebookEditor> {
26+
let notebookEditor: NotebookEditor | undefined;
27+
28+
// Case where NotebookDocument (REPL document already exists in the tab)
29+
if (notebookDocument) {
30+
const existingReplViewColumn = getExistingReplViewColumn(notebookDocument);
31+
const replViewColumn = existingReplViewColumn ?? ViewColumn.Beside;
32+
notebookEditor = await window.showNotebookDocument(notebookDocument!, { viewColumn: replViewColumn });
33+
} else if (!notebookDocument) {
34+
// Case where NotebookDocument doesnt exist, open new REPL tab
35+
const interactiveWindowObject = (await commands.executeCommand(
36+
'interactive.open',
37+
{
38+
preserveFocus: true,
39+
viewColumn: ViewColumn.Beside,
40+
},
41+
undefined,
42+
notebookController.id,
43+
'Python REPL',
44+
)) as { notebookEditor: NotebookEditor };
45+
notebookEditor = interactiveWindowObject.notebookEditor;
46+
notebookDocument = interactiveWindowObject.notebookEditor.notebook;
47+
}
48+
return notebookEditor!;
49+
}
50+
51+
/**
52+
* Function that selects notebook Kernel.
53+
* @param notebookEditor
54+
* @param notebookControllerId
55+
* @param extensionId
56+
* @return Promise<void>
57+
*/
58+
export async function selectNotebookKernel(
59+
notebookEditor: NotebookEditor,
60+
notebookControllerId: string,
61+
extensionId: string,
62+
): Promise<void> {
63+
await commands.executeCommand('notebook.selectKernel', {
64+
notebookEditor,
65+
id: notebookControllerId,
66+
extension: extensionId,
67+
});
68+
}
69+
70+
/**
71+
* Function that executes notebook cell given code.
72+
* @param notebookDocument
73+
* @param code
74+
* @return Promise<void>
75+
*/
76+
export async function executeNotebookCell(notebookDocument: NotebookDocument, code: string): Promise<void> {
77+
const { cellCount } = notebookDocument;
78+
await addCellToNotebook(notebookDocument, code);
79+
// Execute the cell
80+
commands.executeCommand('notebook.cell.execute', {
81+
ranges: [{ start: cellCount, end: cellCount + 1 }],
82+
document: notebookDocument.uri,
83+
});
84+
}
85+
86+
/**
87+
* Function that adds cell to notebook.
88+
* This function will only get called when notebook document is defined.
89+
* @param code
90+
*
91+
*/
92+
async function addCellToNotebook(notebookDocument: NotebookDocument, code: string): Promise<void> {
93+
const notebookCellData = new NotebookCellData(NotebookCellKind.Code, code as string, 'python');
94+
const { cellCount } = notebookDocument!;
95+
// Add new cell to interactive window document
96+
const notebookEdit = NotebookEdit.insertCells(cellCount, [notebookCellData]);
97+
const workspaceEdit = new WorkspaceEdit();
98+
workspaceEdit.set(notebookDocument!.uri, [notebookEdit]);
99+
await workspace.applyEdit(workspaceEdit);
100+
}

0 commit comments

Comments
 (0)