Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 71 additions & 30 deletions renderer/components/PlutoOutput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
useEffect,
setup_mathjax,
useState,
useErrorBoundary,
useMemo,
} from "@plutojl/rainbow/ui";
import { type RendererContext } from "vscode-notebook-renderer";

Expand Down Expand Up @@ -40,21 +42,30 @@ const cutMime = (s: { msg: string }, l = 88) => {

export function PlutoOutput({ state, context }: PlutoOutputProps) {
useMathjaxEffect();
const [error, resetError] = useErrorBoundary();

const [localState, setLocalState] = useState(state);
const [progress, setProgress] = useState<any>(null);
const [terminal, setTerminal] = useState<any>(null);
const [logs, setLogs] = useState<any>(null);

useEffect(() => {
if (error) {
setTimeout(resetError);
}
}, [error]);

useEffect(() => {
// Listen for messages from the controller
const d = context.onDidReceiveMessage?.((message) => {
if (message.cell_id !== state.cell_id) {
if (message.cell_id !== localState.cell_id) {
return;
}
// Placeholder: Handle different message types from controller
switch (message.type) {
case "setState": {
const state = message.state as CellResultData;
setLocalState(state);
setLocalState({ ...state });

const logs = state.logs.filter((log) => {
return (
Expand Down Expand Up @@ -83,44 +94,74 @@ export function PlutoOutput({ state, context }: PlutoOutputProps) {
return () => d?.dispose();
}, [state.cell_id, context]);

return html`
${
state.running && progress
? html`<div>
<label for=${`progress_${state.cell_id}`}> ${progress}% </label
><progress
style="width: 240px;"
id=${`progress_${state.cell_id}`}
max="100"
value=${progress}
></progress>
</div>`
: null
}
<${OutputBody}
persist_js_state="${true}"
const OUTPUT = useMemo(() => {
// This is probably a bug in the immer bundling; the mime edits don't propagate ;/
// TODO: @pankgeorg investigate pls
const { mime, body } = localState.output ?? {};
const fixedMime =
(mime === "application/vnd.pluto.stacktrace+object" &&
(typeof body !== "object" ||
!("stacktrace" in localState.output.body))) ||
(mime === "application/vnd.pluto.tree+object" &&
(typeof body !== "object" || !("type" in localState.output.body)))
? "text/plain"
: localState.output.mime;
if (localState.output?.mime)
return html`<${OutputBody}
persist_js_state="${localState.output.persist_js_state}"
body="${localState.output?.body}"
mime="${localState.output?.mime}"
mime="${fixedMime}"
sanitize_html="${false /* Maybe reconsider */}"
></${OutputBody}>
${
terminal?.length
? html`<details>
></${OutputBody}>`;
return "Loading...";
}, [
localState,
localState.cell_id,
localState.output.mime,
localState.output.body,
localState.running,
localState.errored,
]);

if (error) {
console.error(error);
return html`<div onclick=${resetError}>
An error occured. Click <button onClick=${resetError}>here</button> to
reset the view
<details>
<summary>View error</summary>
(Thank you for using a pre-release. This is on us. Please copy-paste
this and send it our way! Sorry again!)
<pre>${JSON.stringify(error)}</pre>
</details>
</div>`;
}
return html` ${localState.running && progress
? html`<div>
<label for=${`progress_${localState.cell_id}`}> ${progress}% </label
><progress
style="width: 240px;"
id=${`progress_${localState.cell_id}`}
max="100"
value=${progress}
></progress>
</div>`
: null}
${OUTPUT}
${terminal?.length
? html`<details>
<summary>stdout</summary>
<${ANSITextOutput}
body="${terminal.map(cutMime).join("\n")}"
></${ANSITextOutput}>
</details>`
: null
}
${
logs?.length
? html`<details open>
: null}
${logs?.length
? html`<details open>
<summary>Logs</summary>
<${ANSITextOutput}
body="${logs.map(cutMime).join("\n")}"
></${ANSITextOutput}>
</details>`
: null
}`;
: null}`;
}
4 changes: 4 additions & 0 deletions renderer/styles/pluto-output.css
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,7 @@ details[open] {
var(--vscode-textBlockQuote-border, var(--vscode-panel-border));
padding-left: 0.5rem;
}

.fix-with-ai {
display: none;
}
3 changes: 2 additions & 1 deletion renderer/styles/tree.css
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ jlerror > section .frame-source > a {
padding: 1px 7px;
text-decoration: none;
border-left: 3px solid var(--vscode-textLink-foreground);
color: var(--vscode-textLink-foreground);
color: var(--vscode-editor-foreground);
}
jlerror > section .frame-source > a:hover {
background: var(--vscode-textLink-activeForeground, rgba(14, 99, 156, 0.3));
Expand Down Expand Up @@ -539,6 +539,7 @@ table.pluto-table tbody th:first-child {
white-space: nowrap;
}


table.pluto-table .pluto-tree-more-td {
text-align: left;
overflow: unset;
Expand Down
44 changes: 35 additions & 9 deletions src/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
interface Patch {
op: "add" | "remove" | "replace" | "move" | "copy" | "test";
path: Array<string | number>;
value?: any;

Check warning on line 31 in src/controller.ts

View workflow job for this annotation

GitHub Actions / Lint

Unexpected any. Specify a different type

Check warning on line 31 in src/controller.ts

View workflow job for this annotation

GitHub Actions / Build

Unexpected any. Specify a different type
from?: Array<string | number>;
}
// --- END: Merged Interfaces ---
Expand Down Expand Up @@ -115,7 +115,7 @@
*/
private async handleRendererMessage(event: {
editor: vscode.NotebookEditor;
message: any;

Check warning on line 118 in src/controller.ts

View workflow job for this annotation

GitHub Actions / Lint

Unexpected any. Specify a different type

Check warning on line 118 in src/controller.ts

View workflow job for this annotation

GitHub Actions / Build

Unexpected any. Specify a different type
}): Promise<void> {
const { editor, message } = event;

Expand Down Expand Up @@ -151,17 +151,20 @@
*/
public sendMessageToRenderer(
notebook: vscode.NotebookDocument,
message: any

Check warning on line 154 in src/controller.ts

View workflow job for this annotation

GitHub Actions / Lint

Unexpected any. Specify a different type

Check warning on line 154 in src/controller.ts

View workflow job for this annotation

GitHub Actions / Build

Unexpected any. Specify a different type
): void {
// Find the active editor for this notebook
const editor = vscode.window.visibleNotebookEditors.find(
// Find ALL editors for this notebook (handles split views)
const editors = vscode.window.visibleNotebookEditors.filter(
(e) => e.notebook === notebook
);

if (editor && this.rendererMessaging) {
this.rendererMessaging.postMessage(message, editor);
if (editors.length > 0 && this.rendererMessaging) {
// Send message to all editors displaying this notebook
for (const editor of editors) {
this.rendererMessaging.postMessage(message, editor);
}
this.outputChannel.appendLine(
`[CONTROLLER MESSAGE] Sent: ${JSON.stringify(message)}`
`[CONTROLLER MESSAGE] Sent to ${editors.length} editor(s): ${JSON.stringify(message)}`
);
}
}
Expand Down Expand Up @@ -270,7 +273,7 @@
if (segment2 === "output") {
// Handle final output/result update
const execution = this.startExecution(cellId, notebook);
execution.replaceOutput([formatCellOutput(currentCellState)]);
// execution.replaceOutput([formatCellOutput(currentCellState)]);

this.outputChannel.appendLine(
`[OUTPUT] Cell ${cellId} for notebook ${notebook.uri} output updated.`
Expand Down Expand Up @@ -383,12 +386,31 @@
// );
// }
}
private updateAllCellsFromState = (
notebook: vscode.NotebookDocument,
update: UpdateEvent
) => {
// Optimistically send data. May be ignored.
// If not ignored, this makes sure logs, stdout and progress
// are properly propagated to state object
const fullNotebookState = update.notebook;
Object.entries(fullNotebookState?.cell_results ?? {}).forEach(
([cell_id, state]) => {
this.sendMessageToRenderer(notebook, {
type: "setState",
state,
cell_id,
});
}
);
};

/**
* Handles streaming updates from the Pluto worker via patches.
*/
private onPlutoNotebookUpdate = (notebook: vscode.NotebookDocument) => {
return (event: UpdateEvent) => {
console.log({ event });
try {
const patches = event.data?.patches as Patch[] | undefined;
const fullNotebookState = event.notebook;
Expand All @@ -399,7 +421,7 @@
);
return;
}

let anyWeird = false;
for (const patch of patches) {
const path = patch.path;
const [action, ...rest] = path;
Expand Down Expand Up @@ -462,12 +484,18 @@
case "last_save_time":
break;
default:
anyWeird = true;
this.outputChannel.appendLine(
`[UNHANDLED] ${patch.path.join(".")} action ${patch.op}`
);
}
}
if (anyWeird) {
console.log("Not sure if all ok, updating everything");
this.updateAllCellsFromState(notebook, event);
}
} catch (e: unknown) {
this.updateAllCellsFromState(notebook, event);
this.outputChannel.appendLine(
`Failed to process patch update: ${
e instanceof Error ? e.message : String(e)
Expand All @@ -494,8 +522,6 @@

// Subscribe to updates from this worker
worker.onUpdate(this.onPlutoNotebookUpdate(notebook));

// Fetch existing cell results from Pluto server
}
} catch (error) {
const errorMessage =
Expand Down
17 changes: 17 additions & 0 deletions src/plutoManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,15 @@ export class PlutoManager {
this.taskManager.onStop(() => {
this.onServerStopped();
});

// Register callback to update server URL when port changes
this.taskManager.onPortChanged((newPort: number) => {
this.serverUrl = `http://localhost:${newPort}`;
// Update host with new URL
if (this.host) {
this.host = new Host(this.serverUrl);
}
});
}

/**
Expand Down Expand Up @@ -325,6 +334,14 @@ export class PlutoManager {
return this.serverUrl;
}

/**
* Get the actual port being used by the server
* This may differ from the configured port if the configured port was unavailable
*/
public getActualPort(): number {
return this.taskManager.getActualPort();
}

/**
* Close connection to a notebook
* const notebookPath = notebookUri.fsPath;
Expand Down
47 changes: 36 additions & 11 deletions src/plutoSerializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { CellInputData, NotebookData } from "@plutojl/rainbow";
import { parse, serialize } from "./rainbowAdapter.ts";
import * as vscode from "vscode";
import { formatCellOutput } from "./serializer.ts";
import { v4 as uuidv4, validate } from "uuid";
import { v4 as uuidv4 } from "uuid";
import { isDefined, isNotDefined } from "./helpers.ts";

/**
Expand All @@ -19,15 +19,23 @@ export interface ParsedNotebook {
notebook_id: string;
pluto_version?: string;
}

const fakeRegexTest = new RegExp(
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-7][0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$/i
);
export function createVsCodeCellFromPlutoCell(
notebookData: NotebookData,
plutoCellId: string
): vscode.NotebookCellData | null {
// Validate UUID format and check cell exists
const cellInput = notebookData.cell_inputs[plutoCellId];

if (!validate(plutoCellId) || isNotDefined(cellInput)) {
// Proper regex is, but Julia doesn't care for [089ab] and there are "invalid" uuids out there :')
//const m = new RegExp(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/i)
// Example UUID: b2d786ec-7f73-11ea-1a0c-f38d7b6bbc1e from the `Simple math.jl` notebook (from 2020)

// TODO:
// If you find a UUID that doesn't comform, update to a conforming one on the fly
// without crashing 馃ゲ
if (!fakeRegexTest.test(plutoCellId.trim()) || isNotDefined(cellInput)) {
throw new Error(
`Invalid or missing cell ID: ${plutoCellId} fix in the code`
);
Expand All @@ -51,17 +59,34 @@ export function createVsCodeCellFromPlutoCell(
code,
isMarkdown ? "markdown" : "julia"
);
const results = notebookData.cell_results[plutoCellId] ?? null;
if (results !== null) {
// Add output if available
cellData.outputs = [formatCellOutput(results)];
}

const results = notebookData.cell_results[plutoCellId] ?? {
cell_id: plutoCellId,
output: {
body: undefined,
has_pluto_hook_features: false,
last_run_timestamp: 0,
mime: "text/plain",
persist_js_state: false,
rootassignee: "",
},
running: false,
errored: false,
queued: true,
runtime: 0,
depends_on_disabled_cells: false,
depends_on_skipped_cells: false,
downstream_cells_map: {},
precedence_heuristic: 0,
logs: [],
published_object_keys: [""],
upstream_cells_map: {},
};
cellData.outputs = [formatCellOutput(results)];
cellData.metadata = {
pluto_cell_id: plutoCellId,
...cellInput.metadata,
};
// TODO add outputs
// cellData.outputs = [];
return cellData;
}
/**
Expand Down
Loading
Loading