1
1
import { WidgetAdapter } from '../adapter' ;
2
2
import { Notebook , NotebookPanel } from '@jupyterlab/notebook' ;
3
3
import { until_ready } from '../../utils' ;
4
- import { Cell } from '@jupyterlab/cells' ;
4
+ import { Cell , ICellModel } from '@jupyterlab/cells' ;
5
5
import * as nbformat from '@jupyterlab/nbformat' ;
6
6
import ILanguageInfoMetadata = nbformat . ILanguageInfoMetadata ;
7
7
import { Session } from '@jupyterlab/services' ;
@@ -17,13 +17,15 @@ import { VirtualDocument } from '../../virtual/document';
17
17
export class NotebookAdapter extends WidgetAdapter < NotebookPanel > {
18
18
editor : Notebook ;
19
19
private ce_editor_to_cell : Map < IEditor , Cell > ;
20
+ private known_editors_ids : Set < string > ;
20
21
21
22
private _language_info : ILanguageInfoMetadata ;
22
23
23
24
constructor ( extension : LSPExtension , editor_widget : NotebookPanel ) {
24
25
super ( extension , editor_widget ) ;
25
26
this . ce_editor_to_cell = new Map ( ) ;
26
27
this . editor = editor_widget . content ;
28
+ this . known_editors_ids = new Set ( ) ;
27
29
this . init_once_ready ( ) . catch ( console . warn ) ;
28
30
}
29
31
@@ -136,32 +138,57 @@ export class NotebookAdapter extends WidgetAdapter<NotebookPanel> {
136
138
) ;
137
139
138
140
this . widget . content . activeCellChanged . connect ( this . activeCellChanged , this ) ;
139
- this . widget . model . cells . changed . connect ( ( cells , change ) => {
140
- console . log ( change ) ;
141
+ this . widget . model . cells . changed . connect ( async ( cells , change ) => {
142
+ let cellsAdded : ICellModel [ ] = [ ] ;
143
+ let cellsRemoved : ICellModel [ ] = [ ] ;
144
+
145
+ if ( change . type === 'set' ) {
146
+ // handling of conversions is important, because the editors get re-used and their handlers inherited,
147
+ // so we need to clear our handlers from editors of e.g. markdown cells which previously were code cells.
148
+ let convertedToMarkdownOrRaw = [ ] ;
149
+ let convertedToCode = [ ] ;
150
+
151
+ if ( change . newValues . length !== change . oldValues . length ) {
152
+ // during conversion the cells should not get deleted nor added
153
+ return ;
154
+ }
141
155
142
- if ( change . type !== 'set' ) {
143
- return ;
144
- }
145
- let convertedToMarkdown = [ ] ;
156
+ for ( let i = 0 ; i < change . newValues . length ; i ++ ) {
157
+ if (
158
+ change . oldValues [ i ] . type === 'code' &&
159
+ change . newValues [ i ] . type !== 'code'
160
+ ) {
161
+ convertedToMarkdownOrRaw . push ( change . newValues [ i ] ) ;
162
+ } else if (
163
+ change . oldValues [ i ] . type !== 'code' &&
164
+ change . newValues [ i ] . type === 'code'
165
+ ) {
166
+ convertedToCode . push ( change . newValues [ i ] ) ;
167
+ }
168
+ }
169
+ cellsAdded = convertedToCode ;
170
+ cellsRemoved = convertedToMarkdownOrRaw ;
146
171
147
- if ( change . newValues . length !== change . oldValues . length ) {
148
- // during conversion the cells should not get deleted nor added
149
- return ;
172
+ } else if ( change . type == 'add' ) {
173
+ cellsAdded = change . newValues . filter ( ( cellModel ) => cellModel . type === 'code' )
150
174
}
151
-
152
- for ( let i = 0 ; i < change . newValues . length ; i ++ ) {
153
- if (
154
- change . oldValues [ i ] . type === 'code' &&
155
- change . newValues [ i ] . type === 'markdown'
156
- ) {
157
- convertedToMarkdown . push ( change . newValues [ i ] ) ;
158
- }
175
+ // note: editorRemoved is not emitted for removal of cells by change of type 'remove' (but only during cell type conversion)
176
+ // because there is no easy way to get the widget associated with the removed cell(s) - because it is no
177
+ // longer in the notebook widget list! It would need to be tracked on our side, but it is not necessary
178
+ // as (except for a tiny memory leak) it should not impact the functionality in any way
179
+
180
+ if ( cellsRemoved . length || cellsAdded . length || change . type === 'move' || change . type === 'remove' ) {
181
+ // in contrast to the file editor document which can be only changed by the modification of the editor content,
182
+ // the notebook document cna also get modified by a change in the number or arrangement of editors themselves;
183
+ // for this reason each change has to trigger documents update (so that LSP mirror is in sync).
184
+ await this . update_documents ( ) ;
159
185
}
160
186
161
- for ( let cellModel of convertedToMarkdown ) {
187
+ for ( let cellModel of cellsRemoved ) {
162
188
let cellWidget = this . widget . content . widgets . find (
163
189
cell => cell . model . id === cellModel . id
164
190
) ;
191
+ this . known_editors_ids . delete ( cellWidget . editor . uuid )
165
192
166
193
// for practical purposes this editor got removed from our consideration;
167
194
// it might seem that we should instead look for the editor indicated by
@@ -171,6 +198,18 @@ export class NotebookAdapter extends WidgetAdapter<NotebookPanel> {
171
198
editor : cellWidget . editor
172
199
} ) ;
173
200
}
201
+
202
+ for ( let cellModel of cellsAdded ) {
203
+ let cellWidget = this . widget . content . widgets . find (
204
+ cell => cell . model . id === cellModel . id
205
+ ) ;
206
+ this . known_editors_ids . add ( cellWidget . editor . uuid )
207
+
208
+ this . editorAdded . emit ( {
209
+ editor : cellWidget . editor
210
+ } ) ;
211
+ }
212
+
174
213
} ) ;
175
214
}
176
215
@@ -214,6 +253,15 @@ export class NotebookAdapter extends WidgetAdapter<NotebookPanel> {
214
253
}
215
254
216
255
private activeCellChanged ( notebook : Notebook , cell : Cell ) {
256
+ if ( cell . model . type !== 'code' ) {
257
+ return ;
258
+ }
259
+ if ( ! this . known_editors_ids . has ( cell . editor . uuid ) ) {
260
+ this . known_editors_ids . add ( cell . editor . uuid ) ;
261
+ this . editorAdded . emit ( {
262
+ editor : cell . editor
263
+ } ) ;
264
+ }
217
265
this . activeEditorChanged . emit ( {
218
266
editor : cell . editor
219
267
} ) ;
0 commit comments