4
4
*--------------------------------------------------------------------------------------------*/
5
5
6
6
import { ExtensionContext , NotebookCellKind , NotebookDocument , NotebookDocumentChangeEvent , NotebookEdit , workspace , WorkspaceEdit , type NotebookCell , type NotebookDocumentWillSaveEvent } from 'vscode' ;
7
- import { getCellMetadata , getVSCodeCellLanguageId , removeVSCodeCellLanguageId , setVSCodeCellLanguageId } from './serializers' ;
7
+ import { getCellMetadata , getVSCodeCellLanguageId , removeVSCodeCellLanguageId , setVSCodeCellLanguageId , sortObjectPropertiesRecursively } from './serializers' ;
8
8
import { CellMetadata , useCustomPropertyInMetadata } from './common' ;
9
9
import { getNotebookMetadata } from './notebookSerializer' ;
10
10
import type * as nbformat from '@jupyterlab/nbformat' ;
@@ -53,15 +53,25 @@ function cleanup(notebook: NotebookDocument, promise: PromiseLike<void>) {
53
53
}
54
54
}
55
55
}
56
- function trackAndUpdateCellMetadata ( notebook : NotebookDocument , cell : NotebookCell , metadata : CellMetadata & { vscode ?: { languageId : string } } ) {
56
+ function trackAndUpdateCellMetadata ( notebook : NotebookDocument , updates : { cell : NotebookCell ; metadata : CellMetadata & { vscode ?: { languageId : string } } } [ ] ) {
57
57
const pendingUpdates = pendingNotebookCellModelUpdates . get ( notebook ) ?? new Set < Thenable < void > > ( ) ;
58
58
pendingNotebookCellModelUpdates . set ( notebook , pendingUpdates ) ;
59
59
const edit = new WorkspaceEdit ( ) ;
60
- if ( useCustomPropertyInMetadata ( ) ) {
61
- edit . set ( cell . notebook . uri , [ NotebookEdit . updateCellMetadata ( cell . index , { ...( cell . metadata ) , custom : metadata } ) ] ) ;
62
- } else {
63
- edit . set ( cell . notebook . uri , [ NotebookEdit . updateCellMetadata ( cell . index , { ...cell . metadata , ...metadata } ) ] ) ;
64
- }
60
+ updates . forEach ( ( { cell, metadata } ) => {
61
+ let newMetadata : any = { } ;
62
+ if ( useCustomPropertyInMetadata ( ) ) {
63
+ newMetadata = { ...( cell . metadata ) , custom : metadata } ;
64
+ } else {
65
+ newMetadata = { ...cell . metadata , ...metadata } ;
66
+ if ( ! metadata . execution_count && newMetadata . execution_count ) {
67
+ delete newMetadata . execution_count ;
68
+ }
69
+ if ( ! metadata . attachments && newMetadata . attachments ) {
70
+ delete newMetadata . attachments ;
71
+ }
72
+ }
73
+ edit . set ( cell . notebook . uri , [ NotebookEdit . updateCellMetadata ( cell . index , sortObjectPropertiesRecursively ( newMetadata ) ) ] ) ;
74
+ } ) ;
65
75
const promise = workspace . applyEdit ( edit ) . then ( noop , noop ) ;
66
76
pendingUpdates . add ( promise ) ;
67
77
const clean = ( ) => cleanup ( notebook , promise ) ;
@@ -78,31 +88,41 @@ function onDidChangeNotebookCells(e: NotebookDocumentChangeEvent) {
78
88
79
89
// use the preferred language from document metadata or the first cell language as the notebook preferred cell language
80
90
const preferredCellLanguage = notebookMetadata . metadata ?. language_info ?. name ;
81
-
91
+ const updates : { cell : NotebookCell ; metadata : CellMetadata & { vscode ?: { languageId : string } } } [ ] = [ ] ;
82
92
// When we change the language of a cell,
83
93
// Ensure the metadata in the notebook cell has been updated as well,
84
94
// Else model will be out of sync with ipynb https://github.com/microsoft/vscode/issues/207968#issuecomment-2002858596
85
95
e . cellChanges . forEach ( e => {
86
96
if ( ! preferredCellLanguage || e . cell . kind !== NotebookCellKind . Code ) {
87
97
return ;
88
98
}
89
- const languageIdInMetadata = getVSCodeCellLanguageId ( getCellMetadata ( e . cell ) ) ;
90
- if ( e . cell . document . languageId !== preferredCellLanguage && e . cell . document . languageId !== languageIdInMetadata ) {
91
- const metadata : CellMetadata = JSON . parse ( JSON . stringify ( getCellMetadata ( e . cell ) ) ) ;
92
- metadata . metadata = metadata . metadata || { } ;
93
- setVSCodeCellLanguageId ( metadata , e . cell . document . languageId ) ;
94
- trackAndUpdateCellMetadata ( notebook , e . cell , metadata ) ;
99
+ const currentMetadata = e . metadata ? getCellMetadata ( { metadata : e . metadata } ) : getCellMetadata ( { cell : e . cell } ) ;
100
+ const languageIdInMetadata = getVSCodeCellLanguageId ( currentMetadata ) ;
101
+ const metadata : CellMetadata = JSON . parse ( JSON . stringify ( currentMetadata ) ) ;
102
+ metadata . metadata = metadata . metadata || { } ;
103
+ let metadataUpdated = false ;
104
+ if ( e . executionSummary ?. executionOrder && typeof e . executionSummary . success === 'boolean' && currentMetadata . execution_count !== e . executionSummary ?. executionOrder ) {
105
+ metadata . execution_count = e . executionSummary . executionOrder ;
106
+ metadataUpdated = true ;
107
+ } else if ( ! e . executionSummary && ! e . metadata && e . outputs ?. length === 0 && currentMetadata . execution_count ) {
108
+ // Clear all.
109
+ delete metadata . execution_count ;
110
+ metadataUpdated = true ;
111
+ }
95
112
96
- } else if ( e . cell . document . languageId === preferredCellLanguage && languageIdInMetadata ) {
97
- const metadata : CellMetadata = JSON . parse ( JSON . stringify ( getCellMetadata ( e . cell ) ) ) ;
98
- metadata . metadata = metadata . metadata || { } ;
113
+ if ( e . document ?. languageId && e . document ?. languageId !== preferredCellLanguage && e . document ?. languageId !== languageIdInMetadata ) {
114
+ setVSCodeCellLanguageId ( metadata , e . document . languageId ) ;
115
+ metadataUpdated = true ;
116
+ } else if ( e . document ?. languageId && e . document . languageId === preferredCellLanguage && languageIdInMetadata ) {
99
117
removeVSCodeCellLanguageId ( metadata ) ;
100
- trackAndUpdateCellMetadata ( notebook , e . cell , metadata ) ;
101
- } else if ( e . cell . document . languageId === preferredCellLanguage && e . cell . document . languageId === languageIdInMetadata ) {
102
- const metadata : CellMetadata = JSON . parse ( JSON . stringify ( getCellMetadata ( e . cell ) ) ) ;
103
- metadata . metadata = metadata . metadata || { } ;
118
+ metadataUpdated = true ;
119
+ } else if ( e . document ?. languageId && e . document . languageId === preferredCellLanguage && e . document . languageId === languageIdInMetadata ) {
104
120
removeVSCodeCellLanguageId ( metadata ) ;
105
- trackAndUpdateCellMetadata ( notebook , e . cell , metadata ) ;
121
+ metadataUpdated = true ;
122
+ }
123
+
124
+ if ( metadataUpdated ) {
125
+ updates . push ( { cell : e . cell , metadata } ) ;
106
126
}
107
127
} ) ;
108
128
@@ -112,7 +132,7 @@ function onDidChangeNotebookCells(e: NotebookDocumentChangeEvent) {
112
132
change . addedCells . forEach ( cell => {
113
133
// When ever a cell is added, always update the metadata
114
134
// as metadata is always an empty `{}` in ipynb JSON file
115
- const cellMetadata = getCellMetadata ( cell ) ;
135
+ const cellMetadata = getCellMetadata ( { cell } ) ;
116
136
117
137
// Avoid updating the metadata if it's not required.
118
138
if ( cellMetadata . metadata ) {
@@ -131,9 +151,13 @@ function onDidChangeNotebookCells(e: NotebookDocumentChangeEvent) {
131
151
if ( isCellIdRequired ( notebookMetadata ) && ! cellMetadata ?. id ) {
132
152
metadata . id = generateCellId ( e . notebook ) ;
133
153
}
134
- trackAndUpdateCellMetadata ( notebook , cell , metadata ) ;
154
+ updates . push ( { cell, metadata } ) ;
135
155
} ) ;
136
156
} ) ;
157
+
158
+ if ( updates . length ) {
159
+ trackAndUpdateCellMetadata ( notebook , updates ) ;
160
+ }
137
161
}
138
162
139
163
@@ -158,7 +182,7 @@ function generateCellId(notebook: NotebookDocument) {
158
182
let duplicate = false ;
159
183
for ( let index = 0 ; index < notebook . cellCount ; index ++ ) {
160
184
const cell = notebook . cellAt ( index ) ;
161
- const existingId = getCellMetadata ( cell ) ?. id ;
185
+ const existingId = getCellMetadata ( { cell } ) ?. id ;
162
186
if ( ! existingId ) {
163
187
continue ;
164
188
}
0 commit comments