diff --git a/packages/server/src/server.ts b/packages/server/src/server.ts index 6f6bd98..f97f031 100644 --- a/packages/server/src/server.ts +++ b/packages/server/src/server.ts @@ -341,7 +341,7 @@ class VitaleDevServer { path: string; cellId: string; language: string; - code: string; + code?: string; }[] ) { let dirtyCells: { path: string; cellId: string }[] = []; @@ -349,7 +349,9 @@ class VitaleDevServer { for (const { path, cellId, language, code } of cells) { const ext = extOfLanguage(language); const id = `${path}-cellId=${cellId}.${ext}`; - this.cells.set(id, { cellId, code, language }); + if (code) { + this.cells.set(id, { cellId, code, language }); + } const mod = this.viteServer.moduleGraph.getModuleById(id); if (mod) { diff --git a/packages/server/src/types.ts b/packages/server/src/types.ts index 933ccee..000e0c6 100644 --- a/packages/server/src/types.ts +++ b/packages/server/src/types.ts @@ -25,7 +25,7 @@ export type ServerFunctions = { path: string; cellId: string; language: string; - code: string; + code?: string; }[] ) => void; }; diff --git a/packages/vscode/src/cellStatusBarItemProvider.ts b/packages/vscode/src/cellStatusBarItemProvider.ts index 911bd88..e3b19ad 100644 --- a/packages/vscode/src/cellStatusBarItemProvider.ts +++ b/packages/vscode/src/cellStatusBarItemProvider.ts @@ -17,12 +17,9 @@ export class NotebookCellStatusBarItemProvider }; items.push(idItem); - // TODO(jaked) - // provideCellStatusBarItems is called on cell edits - // but the cell.document.dirty flag is always false - if (cell.metadata.dirty || cell.document.isDirty) { + if (cell.metadata.dirty || cell.metadata.docDirty) { const dirtyItem = new vscode.NotebookCellStatusBarItem( - "$(circle-filled)", + cell.metadata.docDirty ? "$(circle-filled)" : "$(circle-outline)", vscode.NotebookCellStatusBarAlignment.Right ); // TODO(jaked) diff --git a/packages/vscode/src/controller.ts b/packages/vscode/src/controller.ts index d0917a6..05480dc 100644 --- a/packages/vscode/src/controller.ts +++ b/packages/vscode/src/controller.ts @@ -77,7 +77,7 @@ export class NotebookController { this._controller.supportedLanguages = this.supportedLanguages; this._controller.supportsExecutionOrder = true; - this._controller.executeHandler = this.executeCells.bind(this); + this._controller.executeHandler = this.executeHandler.bind(this); getPort({ port: 51205 }).then((port) => { this._port = port; @@ -288,12 +288,21 @@ export class NotebookController { } private setCellDirty(cell: vscode.NotebookCell, dirty: boolean) { - const metadata = { ...(cell.metadata ?? {}), dirty }; + const metadata = { ...cell.metadata, dirty }; const edit = new vscode.WorkspaceEdit(); edit.set(cell.notebook.uri, [ vscode.NotebookEdit.updateCellMetadata(cell.index, metadata), ]); - vscode.workspace.applyEdit(edit); + return vscode.workspace.applyEdit(edit); + } + + private setCellDocDirty(cell: vscode.NotebookCell, docDirty: boolean) { + const metadata = { ...cell.metadata, docDirty }; + const edit = new vscode.WorkspaceEdit(); + edit.set(cell.notebook.uri, [ + vscode.NotebookEdit.updateCellMetadata(cell.index, metadata), + ]); + return vscode.workspace.applyEdit(edit); } private async markCellsDirty(cells: { path: string; cellId: string }[]) { @@ -310,11 +319,11 @@ export class NotebookController { } } - for (const cell of notebookCells) { - this.setCellDirty(cell, true); - } + await Promise.all( + notebookCells.map((cell) => this.setCellDirty(cell, true)) + ); if (getRerunCellsWhenDirty()) { - this.executeCells(notebookCells); + this.executeCells(notebookCells, false); } } @@ -365,7 +374,14 @@ export class NotebookController { } } - private async executeCells(notebookCells: vscode.NotebookCell[]) { + private executeHandler(notebookCells: vscode.NotebookCell[]) { + this.executeCells(notebookCells); + } + + private async executeCells( + notebookCells: vscode.NotebookCell[], + sendDirtyDocs = true + ) { if (notebookCells.length === 0) { return; } @@ -373,9 +389,15 @@ export class NotebookController { path: cell.notebook.uri.fsPath, cellId: cell.metadata.id, language: cell.document.languageId, - code: cell.document.getText(), + code: sendDirtyDocs ? cell.document.getText() : undefined, })); + if (sendDirtyDocs) { + await Promise.all( + notebookCells.map((cell) => this.setCellDocDirty(cell, false)) + ); + } + const client = await this.getClient(); client.executeCells(cells); } diff --git a/packages/vscode/src/extension.ts b/packages/vscode/src/extension.ts index cf31de4..e1d63f1 100644 --- a/packages/vscode/src/extension.ts +++ b/packages/vscode/src/extension.ts @@ -3,6 +3,7 @@ import { NotebookSerializer } from "./serializer"; import { NotebookController } from "./controller"; import { NotebookCellStatusBarItemProvider } from "./cellStatusBarItemProvider"; import { makeHandleDidChangeNotebookDocument } from "./handleDidChangeNotebookDocument"; +import { handleDidChangeTextDocument } from "./handleDidChangeTextDocument"; export function activate(context: vscode.ExtensionContext) { const controller = new NotebookController( @@ -30,6 +31,7 @@ export function activate(context: vscode.ExtensionContext) { vscode.workspace.onDidChangeNotebookDocument( makeHandleDidChangeNotebookDocument(controller) ), + vscode.workspace.onDidChangeTextDocument(handleDidChangeTextDocument), controller, vscode.notebooks.registerNotebookCellStatusBarItemProvider( "vitale-notebook", diff --git a/packages/vscode/src/handleDidChangeNotebookDocument.ts b/packages/vscode/src/handleDidChangeNotebookDocument.ts index 67530fd..6dc98ad 100644 --- a/packages/vscode/src/handleDidChangeNotebookDocument.ts +++ b/packages/vscode/src/handleDidChangeNotebookDocument.ts @@ -20,7 +20,7 @@ export function makeHandleDidChangeNotebookDocument( for (const contentChange of e.contentChanges) { for (const cell of contentChange.addedCells) { const id = uniqueId(e.notebook); - const metadata = { ...(cell.metadata ?? {}), id }; + const metadata = { ...cell.metadata, id }; edits.push(NotebookEdit.updateCellMetadata(cell.index, metadata)); } removedCells.push(...contentChange.removedCells); diff --git a/packages/vscode/src/handleDidChangeTextDocument.ts b/packages/vscode/src/handleDidChangeTextDocument.ts new file mode 100644 index 0000000..0d59371 --- /dev/null +++ b/packages/vscode/src/handleDidChangeTextDocument.ts @@ -0,0 +1,27 @@ +import type { TextDocumentChangeEvent } from "vscode"; +import { NotebookEdit, WorkspaceEdit, workspace } from "vscode"; + +export function handleDidChangeTextDocument(e: TextDocumentChangeEvent) { + if (e.document.uri.scheme !== "vscode-notebook-cell") { + return; + } + const notebook = workspace.notebookDocuments.find( + (notebook) => notebook.uri.path === e.document.uri.path + ); + if (!notebook) { + return; + } + const cell = notebook + .getCells() + .find((cell) => cell.document.uri.fragment === e.document.uri.fragment); + if (!cell) { + return; + } + + const metadata = { ...cell.metadata, docDirty: true }; + const edit = new WorkspaceEdit(); + edit.set(notebook.uri, [ + NotebookEdit.updateCellMetadata(cell.index, metadata), + ]); + workspace.applyEdit(edit); +} diff --git a/packages/vscode/src/serializer.ts b/packages/vscode/src/serializer.ts index fa14505..3935785 100644 --- a/packages/vscode/src/serializer.ts +++ b/packages/vscode/src/serializer.ts @@ -3,7 +3,8 @@ import JSON5 from "json5"; interface NotebookCellMetadata { id: string; - dirty: boolean; + dirty: boolean; // a dependency of cell has changed + docDirty: boolean; // cell document has changed } interface NotebookCell { @@ -59,6 +60,7 @@ export class NotebookSerializer implements vscode.NotebookSerializer { for (const cell of data.cells) { if (cell.metadata) { delete cell.metadata.dirty; + delete cell.metadata.docDirty; } contents.cells.push({ kind: cell.kind,