Skip to content

Commit 538d913

Browse files
authored
Model is in charge of updates to model & cells (#12640)
For #10496 Part 2 of #12604 Move code related to updates to INotebookModel into the corresponding INotebookModel class.
1 parent 9891723 commit 538d913

File tree

7 files changed

+187
-164
lines changed

7 files changed

+187
-164
lines changed

src/client/datascience/notebook/cellEditSyncService.ts

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,19 @@
44
import { inject, injectable } from 'inversify';
55
import { TextDocument, TextDocumentChangeEvent } from 'vscode';
66
import type { NotebookCell, NotebookDocument } from '../../../../typings/vscode-proposed';
7-
import { splitMultilineString } from '../../../datascience-ui/common';
87
import { IExtensionSingleActivationService } from '../../activation/types';
98
import { IDocumentManager, IVSCodeNotebook } from '../../common/application/types';
109
import { traceError } from '../../common/logger';
1110
import { IDisposable, IDisposableRegistry } from '../../common/types';
1211
import { isNotebookCell } from '../../common/utils/misc';
13-
import { INotebookEditorProvider, INotebookModel } from '../types';
12+
import { VSCodeNotebookModel } from '../notebookStorage/vscNotebookModel';
13+
import { INotebookEditorProvider } from '../types';
1414
import { getOriginalCellId } from './helpers/cellMappers';
1515

1616
@injectable()
1717
export class CellEditSyncService implements IExtensionSingleActivationService, IDisposable {
1818
private readonly disposables: IDisposable[] = [];
19-
private mappedDocuments = new WeakMap<TextDocument, { cellId: string; model: INotebookModel }>();
19+
private mappedDocuments = new WeakMap<TextDocument, { cellId: string; model: VSCodeNotebookModel }>();
2020
private nonJupyterNotebookDocuments = new WeakSet<TextDocument>();
2121
constructor(
2222
@inject(IDocumentManager) private readonly documentManager: IDocumentManager,
@@ -45,16 +45,7 @@ export class CellEditSyncService implements IExtensionSingleActivationService, I
4545
return;
4646
}
4747

48-
const cell = details.model.cells.find((item) => item.id === details.cellId);
49-
if (!cell) {
50-
traceError(
51-
`Syncing Cell Editor aborted, Unable to find corresponding ICell for ${e.document.uri.toString()}`,
52-
new Error('ICell not found')
53-
);
54-
return;
55-
}
56-
57-
cell.data.source = splitMultilineString(e.document.getText());
48+
details.model.updateCellSource(details.cellId, e.document.getText());
5849
}
5950

6051
private getEditorsAndCell(cellDocument: TextDocument) {
@@ -105,6 +96,10 @@ export class CellEditSyncService implements IExtensionSingleActivationService, I
10596
return;
10697
}
10798

99+
if (!(editor.model instanceof VSCodeNotebookModel)) {
100+
throw new Error('Notebook Model is not of type VSCodeNotebookModel');
101+
}
102+
108103
this.mappedDocuments.set(cellDocument, { model: editor.model, cellId: getOriginalCellId(cell)! });
109104
return this.mappedDocuments.get(cellDocument);
110105
}

src/client/datascience/notebook/executionService.ts

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,8 @@ import { IServiceContainer } from '../../ioc/types';
1919
import { captureTelemetry, sendTelemetryEvent } from '../../telemetry';
2020
import { Commands, Telemetry, VSCodeNativeTelemetry } from '../constants';
2121
import { INotebookStorageProvider } from '../interactive-ipynb/notebookStorageProvider';
22-
import {
23-
IDataScienceErrorHandler,
24-
INotebook,
25-
INotebookEditorProvider,
26-
INotebookModel,
27-
INotebookProvider
28-
} from '../types';
22+
import { VSCodeNotebookModel } from '../notebookStorage/vscNotebookModel';
23+
import { IDataScienceErrorHandler, INotebook, INotebookEditorProvider, INotebookProvider } from '../types';
2924
import { findMappedNotebookCellModel } from './helpers/cellMappers';
3025
import {
3126
handleUpdateDisplayDataMessage,
@@ -145,7 +140,9 @@ export class NotebookExecutionService implements INotebookExecutionService {
145140
public cancelPendingExecutions(document: NotebookDocument): void {
146141
this.pendingExecutionCancellations.get(document.uri.fsPath)?.forEach((cancellation) => cancellation.cancel()); // NOSONAR
147142
}
148-
private async getNotebookAndModel(document: NotebookDocument): Promise<{ model: INotebookModel; nb: INotebook }> {
143+
private async getNotebookAndModel(
144+
document: NotebookDocument
145+
): Promise<{ model: VSCodeNotebookModel; nb: INotebook }> {
149146
const model = await this.notebookStorage.load(document.uri, undefined, undefined, true);
150147
const nb = await this.notebookProvider.getOrCreateNotebook({
151148
identity: document.uri,
@@ -157,6 +154,9 @@ export class NotebookExecutionService implements INotebookExecutionService {
157154
if (!nb) {
158155
throw new Error('Unable to get Notebook object to run cell');
159156
}
157+
if (!(model instanceof VSCodeNotebookModel)) {
158+
throw new Error('Notebook Model is not of type VSCodeNotebookModel');
159+
}
160160
return { model, nb };
161161
}
162162
private sendPerceivedCellExecute(runningStopWatch: StopWatch) {
@@ -170,7 +170,7 @@ export class NotebookExecutionService implements INotebookExecutionService {
170170
}
171171

172172
private async executeIndividualCell(
173-
notebookAndModel: Promise<{ model: INotebookModel; nb: INotebook }>,
173+
notebookAndModel: Promise<{ model: VSCodeNotebookModel; nb: INotebook }>,
174174
document: NotebookDocument,
175175
cell: NotebookCell,
176176
token: CancellationToken,
@@ -267,12 +267,12 @@ export class NotebookExecutionService implements INotebookExecutionService {
267267
typeof cells[0].data.execution_count === 'number'
268268
) {
269269
const executionCount = cells[0].data.execution_count as number;
270-
if (updateCellExecutionCount(cell, notebookCellModel, executionCount)) {
270+
if (updateCellExecutionCount(model, cell, notebookCellModel, executionCount)) {
271271
this.contentProvider.notifyChangesToDocument(document);
272272
}
273273
}
274274

275-
if (updateCellOutput(cell, notebookCellModel, rawCellOutput)) {
275+
if (updateCellOutput(model, cell, notebookCellModel, rawCellOutput)) {
276276
this.contentProvider.notifyChangesToDocument(document);
277277
}
278278
},
@@ -290,9 +290,10 @@ export class NotebookExecutionService implements INotebookExecutionService {
290290
cell.metadata.statusMessage = '';
291291

292292
// Update metadata in our model.
293-
const notebookCellModel = findMappedNotebookCellModel(cell, model.cells);
293+
const cellModel = findMappedNotebookCellModel(cell, model.cells);
294294
updateCellExecutionTimes(
295-
notebookCellModel,
295+
model,
296+
cellModel,
296297
cell.metadata.runStartTime,
297298
cell.metadata.lastRunDuration
298299
);
@@ -302,11 +303,11 @@ export class NotebookExecutionService implements INotebookExecutionService {
302303
cell.metadata.runState = vscodeNotebookEnums.NotebookCellRunState.Error;
303304
cell.metadata.statusMessage = getCellStatusMessageBasedOnFirstErrorOutput(
304305
// tslint:disable-next-line: no-any
305-
notebookCellModel.data.outputs as any
306+
cellModel.data.outputs as any
306307
);
307308
}
308309

309-
updateVSCNotebookCellMetadata(cell.metadata, notebookCellModel);
310+
updateVSCNotebookCellMetadata(cell.metadata, cellModel);
310311
this.contentProvider.notifyChangesToDocument(document);
311312
deferred.resolve(cell.metadata.runState);
312313
}
@@ -332,7 +333,7 @@ export class NotebookExecutionService implements INotebookExecutionService {
332333
/**
333334
* Ensure we handle display data messages that can result in updates to other cells.
334335
*/
335-
private handleDisplayDataMessages(model: INotebookModel, document: NotebookDocument, nb?: INotebook) {
336+
private handleDisplayDataMessages(model: VSCodeNotebookModel, document: NotebookDocument, nb?: INotebook) {
336337
if (nb && !this.registeredIOPubListeners.has(nb)) {
337338
this.registeredIOPubListeners.add(nb);
338339
//tslint:disable-next-line:no-require-imports

src/client/datascience/notebook/helpers/cellUpdateHelpers.ts

Lines changed: 18 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,18 @@
1010
*/
1111

1212
import * as assert from 'assert';
13-
import { createCellFrom } from '../../../../datascience-ui/common/cellFactory';
1413
import {
1514
NotebookCellLanguageChangeEvent,
1615
NotebookCellOutputsChangeEvent,
1716
NotebookCellsChangeEvent
1817
} from '../../../common/application/types';
19-
import { MARKDOWN_LANGUAGE } from '../../../common/constants';
2018
import { traceError } from '../../../common/logger';
2119
import { sendTelemetryEvent } from '../../../telemetry';
2220
import { VSCodeNativeTelemetry } from '../../constants';
23-
import { INotebookModel } from '../../types';
21+
import { VSCodeNotebookModel } from '../../notebookStorage/vscNotebookModel';
2422
import { findMappedNotebookCellModel } from './cellMappers';
25-
import {
26-
createCellFromVSCNotebookCell,
27-
createVSCCellOutputsFromOutputs,
28-
updateVSCNotebookCellMetadata
29-
} from './helpers';
23+
import { createCellFromVSCNotebookCell, updateVSCNotebookCellMetadata } from './helpers';
3024
// tslint:disable-next-line: no-var-requires no-require-imports
31-
const vscodeNotebookEnums = require('vscode') as typeof import('vscode-proposed');
3225

3326
/**
3427
* If a VS Code cell changes, then ensure we update the corresponding cell in our INotebookModel.
@@ -37,13 +30,17 @@ const vscodeNotebookEnums = require('vscode') as typeof import('vscode-proposed'
3730
*/
3831
export function updateCellModelWithChangesToVSCCell(
3932
change: NotebookCellsChangeEvent | NotebookCellOutputsChangeEvent | NotebookCellLanguageChangeEvent,
40-
model: INotebookModel
33+
model: VSCodeNotebookModel
4134
): boolean | undefined | void {
4235
switch (change.type) {
4336
case 'changeCellOutputs':
4437
return clearCellOutput(change, model);
4538
case 'changeCellLanguage':
46-
return changeCellLanguage(change, model);
39+
// VSC Fires this event only when changing code cells from one language to another.
40+
// If you change markdown to code &/or vice versa, thats treated as a cell being deleted and added.
41+
// In the case of Jupyter cells, we don't care of the language changes from python to csharp.
42+
// Why? Because today its not possible, hence there's nothing we need to do for now.
43+
return false;
4744
case 'changeCells':
4845
return handleChangesToCells(change, model);
4946
default:
@@ -58,7 +55,7 @@ export function updateCellModelWithChangesToVSCCell(
5855
* However we are interested in cell output being cleared (when user clears output).
5956
* @returns {boolean} Return `true` if NotebookDocument was updated/edited.
6057
*/
61-
function clearCellOutput(change: NotebookCellOutputsChangeEvent, model: INotebookModel): boolean {
58+
function clearCellOutput(change: NotebookCellOutputsChangeEvent, model: VSCodeNotebookModel): boolean {
6259
if (!change.cells.every((cell) => cell.outputs.length === 0)) {
6360
return false;
6461
}
@@ -73,65 +70,14 @@ function clearCellOutput(change: NotebookCellOutputsChangeEvent, model: INoteboo
7370
// If a cell has been cleared, then clear the corresponding ICell (cell in INotebookModel).
7471
change.cells.forEach((vscCell) => {
7572
const cell = findMappedNotebookCellModel(vscCell, model.cells);
76-
// tslint:disable-next-line: no-console
77-
console.log(cell);
78-
if (vscCell.cellKind === vscodeNotebookEnums.CellKind.Code) {
79-
cell.data.execution_count = null;
80-
}
81-
if (cell.data.metadata.vscode) {
82-
cell.data.metadata.vscode.start_execution_time = undefined;
83-
cell.data.metadata.vscode.end_execution_time = undefined;
84-
}
85-
cell.data.outputs = [];
73+
model.clearCellOutput(cell);
8674
updateVSCNotebookCellMetadata(vscCell.metadata, cell);
87-
model.update({
88-
source: 'user',
89-
kind: 'clear',
90-
oldDirty: model.isDirty,
91-
newDirty: true,
92-
oldCells: [cell]
93-
});
9475
});
9576

9677
return true;
9778
}
9879

99-
/**
100-
* VS Code doesn't seem to fire this when changing between markdown & code.
101-
* Its only fired when changing the language from python to csharp.
102-
* https://github.com/microsoft/vscode/issues/100042
103-
*/
104-
function changeCellLanguage(change: NotebookCellLanguageChangeEvent, model: INotebookModel) {
105-
const cellModel = findMappedNotebookCellModel(change.cell, model.cells);
106-
if (
107-
(change.cell.cellKind === vscodeNotebookEnums.CellKind.Markdown && cellModel.data.cell_type === 'markdown') ||
108-
(change.cell.cellKind === vscodeNotebookEnums.CellKind.Code && cellModel.data.cell_type === 'code')
109-
) {
110-
// This is when user changes from python to csharp or similar.
111-
return;
112-
}
113-
// Here we have changed from a code cell to markdown or vice versa.
114-
const cellData = createCellFrom(cellModel.data, change.language === MARKDOWN_LANGUAGE ? 'markdown' : 'code');
115-
// tslint:disable-next-line: no-any
116-
change.cell.outputs = createVSCCellOutputsFromOutputs(cellData.outputs as any);
117-
change.cell.metadata.executionOrder = undefined;
118-
change.cell.metadata.hasExecutionOrder = change.language !== MARKDOWN_LANGUAGE; // Do not check for Python, to support other languages
119-
change.cell.metadata.runnable = change.language !== MARKDOWN_LANGUAGE; // Do not check for Python, to support other languages
120-
121-
// Create a new cell & replace old one.
122-
const oldCellIndex = model.cells.indexOf(cellModel);
123-
// tslint:disable-next-line: no-suspicious-comment
124-
// TODO: CHANGE.
125-
// tslint:disable-next-line: no-any
126-
(model.cells as any)[oldCellIndex] = createCellFromVSCNotebookCell(change.cell, model);
127-
sendTelemetryEvent(
128-
change.cell.cellKind === vscodeNotebookEnums.CellKind.Markdown
129-
? VSCodeNativeTelemetry.ChangeToMarkdown
130-
: VSCodeNativeTelemetry.ChangeToCode
131-
);
132-
}
133-
134-
function handleChangesToCells(change: NotebookCellsChangeEvent, model: INotebookModel) {
80+
function handleChangesToCells(change: NotebookCellsChangeEvent, model: VSCodeNotebookModel) {
13581
if (isCellMoveChange(change)) {
13682
handleCellMove(change, model);
13783
sendTelemetryEvent(VSCodeNativeTelemetry.MoveCell);
@@ -171,59 +117,26 @@ function isCellInsertion(change: NotebookCellsChangeEvent) {
171117
return change.changes.length === 1 && change.changes[0].deletedCount === 0 && change.changes[0].items.length > 0;
172118
}
173119

174-
function handleCellMove(change: NotebookCellsChangeEvent, model: INotebookModel) {
120+
function handleCellMove(change: NotebookCellsChangeEvent, model: VSCodeNotebookModel) {
175121
assert.equal(change.changes.length, 2, 'When moving cells we must have only 2 changes');
176122
const [, insertChange] = change.changes;
177123
const cellToSwap = findMappedNotebookCellModel(insertChange.items[0]!, model.cells);
178124
const cellToSwapWith = model.cells[insertChange.start];
179125
assert.notEqual(cellToSwap, cellToSwapWith, 'Cannot swap cell with the same cell');
180-
181-
const indexOfCellToSwap = model.cells.indexOf(cellToSwap);
182-
// tslint:disable-next-line: no-any
183-
(model.cells as any)[insertChange.start] = cellToSwap;
184-
// tslint:disable-next-line: no-any
185-
(model.cells as any)[indexOfCellToSwap] = cellToSwapWith;
186-
// Get model to fire events.
187-
model.update({
188-
source: 'user',
189-
kind: 'swap',
190-
firstCellId: cellToSwap.id,
191-
secondCellId: cellToSwapWith.id,
192-
oldDirty: model.isDirty,
193-
newDirty: true
194-
});
126+
model.swapCells(cellToSwap, cellToSwapWith);
195127
}
196-
function handleCellInsertion(change: NotebookCellsChangeEvent, model: INotebookModel) {
128+
function handleCellInsertion(change: NotebookCellsChangeEvent, model: VSCodeNotebookModel) {
197129
assert.equal(change.changes.length, 1, 'When inserting cells we must have only 1 change');
198130
assert.equal(change.changes[0].items.length, 1, 'Insertion of more than 1 cell is not supported');
199131
const insertChange = change.changes[0];
200132
const cell = change.changes[0].items[0];
201133
const newCell = createCellFromVSCNotebookCell(cell, model);
202-
// tslint:disable-next-line: no-any
203-
(model.cells as any).splice(insertChange.start, 0, newCell);
204-
// Get model to fire events.
205-
model.update({
206-
source: 'user',
207-
kind: 'insert',
208-
cell: newCell,
209-
index: insertChange.start,
210-
oldDirty: model.isDirty,
211-
newDirty: true
212-
});
134+
model.addCell(newCell, insertChange.start);
213135
}
214-
function handleCellDelete(change: NotebookCellsChangeEvent, model: INotebookModel) {
136+
function handleCellDelete(change: NotebookCellsChangeEvent, model: VSCodeNotebookModel) {
215137
assert.equal(change.changes.length, 1, 'When deleting cells we must have only 1 change');
216138
const deletionChange = change.changes[0];
217139
assert.equal(deletionChange.deletedCount, 1, 'Deleting more than one cell is not supported');
218-
// tslint:disable-next-line: no-any
219-
const cellToRemove = (model.cells as any).splice(deletionChange.start, 1);
220-
// Get model to fire events.
221-
model.update({
222-
source: 'user',
223-
kind: 'remove',
224-
cell: cellToRemove[0],
225-
index: deletionChange.start,
226-
oldDirty: model.isDirty,
227-
newDirty: true
228-
});
140+
const cellToRemove = model.cells[deletionChange.start];
141+
model.deleteCell(cellToRemove);
229142
}

0 commit comments

Comments
 (0)