|
| 1 | +/*--------------------------------------------------------------------------------------------- |
| 2 | + * Copyright (c) Microsoft Corporation. All rights reserved. |
| 3 | + * Licensed under the MIT License. See License.txt in the project root for license information. |
| 4 | + *--------------------------------------------------------------------------------------------*/ |
| 5 | + |
| 6 | +import { ExtensionContext, NotebookCellsChangeEvent, NotebookDocument, notebooks, workspace, WorkspaceEdit } from 'vscode'; |
| 7 | +import { v4 as uuid } from 'uuid'; |
| 8 | +import { getCellMetadata } from './serializers'; |
| 9 | +import { CellMetadata } from './common'; |
| 10 | +import { getNotebookMetadata } from './notebookSerializer'; |
| 11 | +import { nbformat } from '@jupyterlab/coreutils'; |
| 12 | + |
| 13 | +/** |
| 14 | + * Ensure all new cells in notebooks with nbformat >= 4.5 have an id. |
| 15 | + * Details of the spec can be found here https://jupyter.org/enhancement-proposals/62-cell-id/cell-id.html# |
| 16 | + */ |
| 17 | +export function ensureAllNewCellsHaveCellIds(context: ExtensionContext) { |
| 18 | + notebooks.onDidChangeNotebookCells(onDidChangeNotebookCells, undefined, context.subscriptions); |
| 19 | +} |
| 20 | + |
| 21 | +function onDidChangeNotebookCells(e: NotebookCellsChangeEvent) { |
| 22 | + const nbMetadata = getNotebookMetadata(e.document); |
| 23 | + if (!isCellIdRequired(nbMetadata)) { |
| 24 | + return; |
| 25 | + } |
| 26 | + e.changes.forEach(change => { |
| 27 | + change.items.forEach(cell => { |
| 28 | + const cellMetadata = getCellMetadata(cell); |
| 29 | + if (cellMetadata?.id) { |
| 30 | + return; |
| 31 | + } |
| 32 | + const id = generateCellId(e.document); |
| 33 | + const edit = new WorkspaceEdit(); |
| 34 | + // Don't edit the metadata directly, always get a clone (prevents accidental singletons and directly editing the objects). |
| 35 | + const updatedMetadata: CellMetadata = { ...JSON.parse(JSON.stringify(cellMetadata || {})) }; |
| 36 | + updatedMetadata.id = id; |
| 37 | + edit.replaceNotebookCellMetadata(cell.notebook.uri, cell.index, { ...(cell.metadata), custom: updatedMetadata }); |
| 38 | + workspace.applyEdit(edit); |
| 39 | + }); |
| 40 | + }); |
| 41 | +} |
| 42 | + |
| 43 | +/** |
| 44 | + * Cell ids are required in notebooks only in notebooks with nbformat >= 4.5 |
| 45 | + */ |
| 46 | +function isCellIdRequired(metadata: Pick<Partial<nbformat.INotebookContent>, 'nbformat' | 'nbformat_minor'>) { |
| 47 | + if ((metadata.nbformat || 0) >= 5) { |
| 48 | + return true; |
| 49 | + } |
| 50 | + if ((metadata.nbformat || 0) === 4 && (metadata.nbformat_minor || 0) >= 5) { |
| 51 | + return true; |
| 52 | + } |
| 53 | + return false; |
| 54 | +} |
| 55 | + |
| 56 | +function generateCellId(notebook: NotebookDocument) { |
| 57 | + while (true) { |
| 58 | + // Details of the id can be found here https://jupyter.org/enhancement-proposals/62-cell-id/cell-id.html#adding-an-id-field, |
| 59 | + // & here https://jupyter.org/enhancement-proposals/62-cell-id/cell-id.html#updating-older-formats |
| 60 | + const id = uuid().replace(/-/g, '').substring(0, 8); |
| 61 | + let duplicate = false; |
| 62 | + for (let index = 0; index < notebook.cellCount; index++) { |
| 63 | + const cell = notebook.cellAt(index); |
| 64 | + const existingId = getCellMetadata(cell)?.id; |
| 65 | + if (!existingId) { |
| 66 | + continue; |
| 67 | + } |
| 68 | + if (existingId === id) { |
| 69 | + duplicate = true; |
| 70 | + break; |
| 71 | + } |
| 72 | + } |
| 73 | + if (!duplicate) { |
| 74 | + return id; |
| 75 | + } |
| 76 | + } |
| 77 | +} |
0 commit comments