Skip to content

Commit

Permalink
Preliminary python support
Browse files Browse the repository at this point in the history
  • Loading branch information
rameshvarun committed Sep 2, 2023
1 parent 03f7738 commit fd1e376
Show file tree
Hide file tree
Showing 8 changed files with 166 additions and 16 deletions.
48 changes: 45 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
},
"homepage": "https://rameshvarun.github.io/blog-cells/",
"devDependencies": {
"@codemirror/lang-python": "^6.1.3",
"@types/jest": "^29.5.4",
"@types/react": "^18.2.20",
"@types/react-dom": "^18.2.7",
Expand Down
17 changes: 14 additions & 3 deletions src/blog-cells.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,19 @@ const editors: any[] = [];
const events = new EventTarget();

import { JavaScriptKernel } from "./javascript-kernel";
const kernel = new JavaScriptKernel();
import { PythonKernel } from "./python-kernel";
import { Kernel } from "./kernel";

const KERNELS: Map<string, Kernel> = new Map();
KERNELS.set("javascript", new JavaScriptKernel());
KERNELS.set("python", new PythonKernel());

class Cell extends React.Component<
{
code: string;
autoRun: boolean;
hideable: boolean;
kernel: Kernel;
onMount?: () => void;
},
any
Expand Down Expand Up @@ -129,7 +135,7 @@ class Cell extends React.Component<
extensions: [
history(),
lineNumbers(),
kernel.getSyntaxHighlighter(),
this.props.kernel.getSyntaxHighlighter(),
oneDark,
keymap.of([...defaultKeymap, ...historyKeymap]),
],
Expand Down Expand Up @@ -160,7 +166,7 @@ class Cell extends React.Component<
setTimeout(() => resolve(), 500);
});

kernel.run(
this.props.kernel.run(
code,
(line) => {
this.setState((state) => {
Expand Down Expand Up @@ -196,6 +202,10 @@ domLoaded.then(() => {

for (const script of scripts) {
const code = script.textContent?.trim() || "";

const kernelName = script.dataset.kernel || "javascript";
const kernel = KERNELS.get(kernelName)!;

const autoRun = script.dataset.autorun === "true";
const hidden = script.dataset.hidden === "true";

Expand All @@ -208,6 +218,7 @@ domLoaded.then(() => {
code={code}
autoRun={autoRun}
hideable={hidden}
kernel={kernel}
onMount={() => {
// Remove the script tag once the cell succesfully mounts.
script.remove();
Expand Down
4 changes: 4 additions & 0 deletions src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ <h1><a href="https://github.com/rameshvarun/blog-cells">blog-cells</a> demo</h1>
console.assert(3 == 4, "3 does not equal 4.");
</script>

<p>You can use other languages like Python.</p>
<script type="text/notebook-cell" data-autorun="true" data-kernel="python">print("Hello World from Python!")
</script>

<link rel="stylesheet" href="./blog-cells.css" />
<script type="module" src="./blog-cells.js"></script>

Expand Down
7 changes: 1 addition & 6 deletions src/javascript-kernel/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,9 @@ import { javascript } from "@codemirror/lang-javascript";
import { Kernel, OutputLine } from "../kernel";
import { ExecutionRequest, ExecutionResponse } from "./types";

export class JavaScriptKernel implements Kernel {
export class JavaScriptKernel extends Kernel {
worker: Worker = new Worker(URL.createObjectURL(blob));

requestID: number = 0;
getRequestID() {
return this.requestID++;
}

run(code: string, onOutput: (line: OutputLine) => void, onDone: () => void) {
// Generate a unique ID to track this execution request.
const requestID = this.getRequestID();
Expand Down
13 changes: 9 additions & 4 deletions src/kernel.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { LanguageSupport } from "@codemirror/language";

export type OutputLine = {
type: string;
type: 'log' | 'error' | 'warn';
line: string;
};

export interface Kernel {
run(code: string, onOutput: (line: OutputLine) => void, onDone: () => void);
getSyntaxHighlighter(): LanguageSupport;
export abstract class Kernel {
requestID: number = 0;
getRequestID() {
return this.requestID++;
}

abstract run(code: string, onOutput: (line: OutputLine) => void, onDone: () => void);
abstract getSyntaxHighlighter(): LanguageSupport;
}
40 changes: 40 additions & 0 deletions src/python-kernel/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// @ts-ignore - Load worker source code.
import WORKER_SRC from "!raw-loader!ts-loader!./worker.ts";
const blob = new Blob([WORKER_SRC], { type: "application/javascript" });

import { python } from "@codemirror/lang-python";
import { Kernel, OutputLine } from "../kernel";

export class PythonKernel extends Kernel {
worker: Worker = new Worker(URL.createObjectURL(blob));

run(code: string, onOutput: (line: OutputLine) => void, onDone: () => void) {
// Generate a unique ID to track this execution request.
const requestID = this.getRequestID();

const messageHandler = (e: MessageEvent) => {
if (e.data.requestID != requestID) return;

if (e.data.kind === "run-code-output") {
onOutput(e.data.output);
} else if (e.data.kind === "run-code-done") {
this.worker.removeEventListener("message", messageHandler);
onDone();
}
};

this.worker.addEventListener("message", messageHandler);

// Post the code to the worker.
this.worker.postMessage({
kind: "run-code",
code: code,
requestID: requestID,
});
}


getSyntaxHighlighter() {
return python();
}
}
52 changes: 52 additions & 0 deletions src/python-kernel/worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
importScripts("https://cdn.jsdelivr.net/npm/pyodide@0.23.2/pyodide.min.js");
declare var loadPyodide;

let onStdout: ((str) => void) | null = null;

// Start loading Pyodide asynchronously.
const loadPython = (async () => {
return await loadPyodide({
stdout: (msg) => {
if (msg === "Python initialization complete") return;
if (onStdout) onStdout(msg);
},
});
})();

self.onmessage = async (e: MessageEvent) => {
console.log("Worker received message: %o", e);
const requestID = e.data.requestID;

if (e.data.kind === "run-code") {
// Register stdout callback.
onStdout = (msg) => {
self.postMessage({
kind: "run-code-output",
requestID: requestID,
output: {type: "log", line: msg},
});
};

try {
// Wait for Pyodide to load.
const pyodide = await loadPython;

// Run code in a new namespace.
pyodide.runPython(e.data.code);
} catch (error) {
self.postMessage({
kind: "run-code-output",
requestID: requestID,
output: {type: "error", line: error.toString()},
});
} finally {
// Unregister stdout callback.
onStdout = null;

self.postMessage({
kind: "run-code-done",
requestID: requestID,
});
}
}
};

0 comments on commit fd1e376

Please sign in to comment.