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,16 @@ 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 ;
23
+ private type : nbformat . CellType = 'code' ;
22
24
23
25
constructor ( extension : LSPExtension , editor_widget : NotebookPanel ) {
24
26
super ( extension , editor_widget ) ;
25
27
this . ce_editor_to_cell = new Map ( ) ;
26
28
this . editor = editor_widget . content ;
29
+ this . known_editors_ids = new Set ( ) ;
27
30
this . init_once_ready ( ) . catch ( console . warn ) ;
28
31
}
29
32
@@ -136,6 +139,83 @@ export class NotebookAdapter extends WidgetAdapter<NotebookPanel> {
136
139
) ;
137
140
138
141
this . widget . content . activeCellChanged . connect ( this . activeCellChanged , this ) ;
142
+ this . widget . model . cells . changed . connect ( async ( cells , change ) => {
143
+ let cellsAdded : ICellModel [ ] = [ ] ;
144
+ let cellsRemoved : ICellModel [ ] = [ ] ;
145
+ const type = this . type ;
146
+
147
+ if ( change . type === 'set' ) {
148
+ // handling of conversions is important, because the editors get re-used and their handlers inherited,
149
+ // so we need to clear our handlers from editors of e.g. markdown cells which previously were code cells.
150
+ let convertedToMarkdownOrRaw = [ ] ;
151
+ let convertedToCode = [ ] ;
152
+
153
+ if ( change . newValues . length === change . oldValues . length ) {
154
+ // during conversion the cells should not get deleted nor added
155
+ for ( let i = 0 ; i < change . newValues . length ; i ++ ) {
156
+ if (
157
+ change . oldValues [ i ] . type === type &&
158
+ change . newValues [ i ] . type !== type
159
+ ) {
160
+ convertedToMarkdownOrRaw . push ( change . newValues [ i ] ) ;
161
+ } else if (
162
+ change . oldValues [ i ] . type !== type &&
163
+ change . newValues [ i ] . type === type
164
+ ) {
165
+ convertedToCode . push ( change . newValues [ i ] ) ;
166
+ }
167
+ }
168
+ cellsAdded = convertedToCode ;
169
+ cellsRemoved = convertedToMarkdownOrRaw ;
170
+ }
171
+ } else if ( change . type == 'add' ) {
172
+ cellsAdded = change . newValues . filter (
173
+ cellModel => cellModel . type === type
174
+ ) ;
175
+ }
176
+ // note: editorRemoved is not emitted for removal of cells by change of type 'remove' (but only during cell type conversion)
177
+ // because there is no easy way to get the widget associated with the removed cell(s) - because it is no
178
+ // longer in the notebook widget list! It would need to be tracked on our side, but it is not necessary
179
+ // as (except for a tiny memory leak) it should not impact the functionality in any way
180
+
181
+ if (
182
+ cellsRemoved . length ||
183
+ cellsAdded . length ||
184
+ change . type === 'move' ||
185
+ change . type === 'remove'
186
+ ) {
187
+ // in contrast to the file editor document which can be only changed by the modification of the editor content,
188
+ // the notebook document cna also get modified by a change in the number or arrangement of editors themselves;
189
+ // for this reason each change has to trigger documents update (so that LSP mirror is in sync).
190
+ await this . update_documents ( ) ;
191
+ }
192
+
193
+ for ( let cellModel of cellsRemoved ) {
194
+ let cellWidget = this . widget . content . widgets . find (
195
+ cell => cell . model . id === cellModel . id
196
+ ) ;
197
+ this . known_editors_ids . delete ( cellWidget . editor . uuid ) ;
198
+
199
+ // for practical purposes this editor got removed from our consideration;
200
+ // it might seem that we should instead look for the editor indicated by
201
+ // the oldValues[i] cellModel, but this one got already transferred to the
202
+ // markdown cell in newValues[i]
203
+ this . editorRemoved . emit ( {
204
+ editor : cellWidget . editor
205
+ } ) ;
206
+ }
207
+
208
+ for ( let cellModel of cellsAdded ) {
209
+ let cellWidget = this . widget . content . widgets . find (
210
+ cell => cell . model . id === cellModel . id
211
+ ) ;
212
+ this . known_editors_ids . add ( cellWidget . editor . uuid ) ;
213
+
214
+ this . editorAdded . emit ( {
215
+ editor : cellWidget . editor
216
+ } ) ;
217
+ }
218
+ } ) ;
139
219
}
140
220
141
221
get editors ( ) : CodeEditor . IEditor [ ] {
@@ -178,13 +258,29 @@ export class NotebookAdapter extends WidgetAdapter<NotebookPanel> {
178
258
}
179
259
180
260
private activeCellChanged ( notebook : Notebook , cell : Cell ) {
261
+ if ( cell . model . type !== this . type ) {
262
+ return ;
263
+ }
264
+ if ( ! this . known_editors_ids . has ( cell . editor . uuid ) ) {
265
+ this . known_editors_ids . add ( cell . editor . uuid ) ;
266
+ this . editorAdded . emit ( {
267
+ editor : cell . editor
268
+ } ) ;
269
+ }
181
270
this . activeEditorChanged . emit ( {
182
271
editor : cell . editor
183
272
} ) ;
184
273
}
185
274
186
275
context_from_active_document ( ) : ICommandContext | null {
187
276
let cell = this . widget . content . activeCell ;
277
+ if ( cell . model . type !== this . type ) {
278
+ // context will be sought on all cells to verify if the context menu should be visible,
279
+ // thus it is ok to just return null; it seems to stem from the implementation detail
280
+ // upstream, i.e. the markdown cells appear to be created by transforming the code cells
281
+ // but do not quote me on that.
282
+ return null ;
283
+ }
188
284
let editor = cell . editor ;
189
285
let ce_cursor = editor . getCursorPosition ( ) ;
190
286
let cm_cursor = PositionConverter . ce_to_cm ( ce_cursor ) as IEditorPosition ;
0 commit comments