-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Ability to clear cell output in NativeNotebook #12307
Changes from 5 commits
a8f7fa2
7d2dd65
5b6aa35
a1bafde
17d87ff
37fd7cb
0bfcc0e
b51f71c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -44,12 +44,6 @@ export class NotebookContentProvider implements VSCodeNotebookContentProvider { | |
} | ||
if (model.isUntitled) { | ||
await this.commandManager.executeCommand('workbench.action.files.saveAs', document.uri); | ||
// tslint:disable-next-line: no-suspicious-comment | ||
//TODO: VSC doesn't handle this well. | ||
// What if user doesn't save it. | ||
if (model.isUntitled) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. VSC Have fixed upstream issues. |
||
throw new Error('Not saved'); | ||
} | ||
} else { | ||
await this.notebookStorage.save(model, cancellation); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,7 @@ | |
|
||
import { nbformat } from '@jupyterlab/coreutils'; | ||
import { inject, injectable } from 'inversify'; | ||
import { IDisposable } from 'monaco-editor'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I don't believe you want this one. |
||
import { Subscription } from 'rxjs'; | ||
import { CancellationToken, CancellationTokenSource } from 'vscode'; | ||
import type { NotebookCell, NotebookDocument } from 'vscode-proposed'; | ||
|
@@ -152,6 +153,7 @@ export class NotebookExecutionService implements INotebookExecutionService { | |
cell.metadata.runState = vscodeNotebookEnums.NotebookCellRunState.Running; | ||
|
||
let subscription: Subscription | undefined; | ||
let modelClearedEventHandler: IDisposable | undefined; | ||
try { | ||
nb.clear(cell.uri.fsPath); // NOSONAR | ||
const observable = nb.executeObservable( | ||
|
@@ -163,6 +165,15 @@ export class NotebookExecutionService implements INotebookExecutionService { | |
); | ||
subscription = observable?.subscribe( | ||
(cells) => { | ||
if (!modelClearedEventHandler) { | ||
modelClearedEventHandler = model.changed((e) => { | ||
if (e.kind === 'clear') { | ||
// If cell output has been cleared, then clear the output in the observed executable cell. | ||
// Else if user clears output while execuitng a cell, we add it back. | ||
cells.forEach((c) => (c.data.outputs = [])); | ||
} | ||
}); | ||
} | ||
const rawCellOutput = cells | ||
.filter((item) => item.id === cell.uri.fsPath) | ||
.flatMap((item) => (item.data.outputs as unknown) as nbformat.IOutput[]) | ||
|
@@ -198,7 +209,6 @@ export class NotebookExecutionService implements INotebookExecutionService { | |
const notebookCellModel = findMappedNotebookCellModel(document, cell, model.cells); | ||
updateCellExecutionTimes( | ||
notebookCellModel, | ||
model, | ||
cell.metadata.runStartTime, | ||
cell.metadata.lastRunDuration | ||
); | ||
|
@@ -221,6 +231,7 @@ export class NotebookExecutionService implements INotebookExecutionService { | |
updateCellWithErrorStatus(cell, ex); | ||
this.errorHandler.handleError(ex).ignoreErrors(); | ||
} finally { | ||
modelClearedEventHandler?.dispose(); // NOSONAR | ||
subscription?.unsubscribe(); // NOSONAR | ||
// Ensure we remove the cancellation. | ||
const cancellations = this.pendingExecutionCancellations.get(document.uri.fsPath); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,9 +18,13 @@ import { | |
} from '../../../common/application/types'; | ||
import { MARKDOWN_LANGUAGE, PYTHON_LANGUAGE } from '../../../common/constants'; | ||
import { traceError } from '../../../logging'; | ||
import { ICell, INotebookModel } from '../../types'; | ||
import { INotebookModel } from '../../types'; | ||
import { findMappedNotebookCellModel } from './cellMappers'; | ||
import { createCellFromVSCNotebookCell, createVSCCellOutputsFromOutputs } from './helpers'; | ||
import { | ||
createCellFromVSCNotebookCell, | ||
createVSCCellOutputsFromOutputs, | ||
updateVSCNotebookCellMetadata | ||
} from './helpers'; | ||
|
||
/** | ||
* If a VS Code cell changes, then ensure we update the corresponding cell in our INotebookModel. | ||
|
@@ -32,9 +36,7 @@ export function updateCellModelWithChangesToVSCCell( | |
) { | ||
switch (change.type) { | ||
case 'changeCellOutputs': | ||
// We're not interested in changes to cell output as this happens as a result of us pushing changes to the notebook. | ||
// I.e. cell output is already in our INotebookModel. | ||
return; | ||
return clearCellOutput(change, model); | ||
case 'changeCellLanguage': | ||
return changeCellLanguage(change, model); | ||
case 'changeCells': | ||
|
@@ -45,6 +47,31 @@ export function updateCellModelWithChangesToVSCCell( | |
} | ||
} | ||
|
||
/** | ||
* We're not interested in changes to cell output as this happens as a result of us pushing changes to the notebook. | ||
* I.e. cell output is already in our INotebookModel. | ||
* However we are interested in cell output being cleared (when user clears output). | ||
*/ | ||
function clearCellOutput(change: NotebookCellOutputsChangeEvent, model: INotebookModel) { | ||
if (!change.cells.every((cell) => cell.outputs.length === 0)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just a quick question on this check. If an output change comes in, and it had two cells in the output change and one cell cleared the output and another cell still had output, it looks like this check would skip the output clear for the cell. But I'm not sure if that is a situation that could happen. Instead of this check do we want to just perform the operation below on any cells in the change that do have output length === 0? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We only clear the cells that were cleared by the user. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yup, I can tell that it's just the cells involved with the change. It just looks to me like this NotebookCellOutputsChangeEvent can be more general than just the user clearing output for a cell. Just making sure that VSCode would not do something like batch up calls to it (like update output in cell A and clear output in cell B) then just call OutputsChanged once. If that wouldn't happen then the check seems fine to me, and you would have the best judgement on if that's the case or not. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yup got it, at this stage that cannot be handled not the way i have written this. |
||
return; | ||
} | ||
|
||
// If a cell has been cleared, then clear the corresponding ICell (cell in INotebookModel). | ||
change.cells.forEach((vscCell) => { | ||
const cell = findMappedNotebookCellModel(change.document, vscCell, model.cells); | ||
cell.data.outputs = []; | ||
updateVSCNotebookCellMetadata(vscCell.metadata, cell); | ||
model.update({ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We want to trigger an event. |
||
source: 'user', | ||
kind: 'clear', | ||
oldDirty: model.isDirty, | ||
newDirty: true, | ||
oldCells: [cell] | ||
}); | ||
}); | ||
} | ||
|
||
function changeCellLanguage(change: NotebookCellLanguageChangeEvent, model: INotebookModel) { | ||
const cellModel = findMappedNotebookCellModel(change.document, change.cell, model.cells); | ||
|
||
|
@@ -57,26 +84,16 @@ function changeCellLanguage(change: NotebookCellLanguageChangeEvent, model: INot | |
return; | ||
} | ||
|
||
const newCellData = createCellFrom(cellModel.data, change.language === MARKDOWN_LANGUAGE ? 'markdown' : 'code'); | ||
const newCell: ICell = { | ||
...cellModel, | ||
data: newCellData | ||
}; | ||
|
||
model.update({ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We will mutate the cells in place, we don't want to change the cells as the cell mapping stops working |
||
source: 'user', | ||
kind: 'modify', | ||
newCells: [newCell], | ||
oldCells: [cellModel], | ||
oldDirty: model.isDirty, | ||
newDirty: true | ||
}); | ||
|
||
const cellData = createCellFrom(cellModel.data, change.language === MARKDOWN_LANGUAGE ? 'markdown' : 'code'); | ||
// tslint:disable-next-line: no-any | ||
change.cell.outputs = createVSCCellOutputsFromOutputs(newCellData.outputs as any); | ||
change.cell.outputs = createVSCCellOutputsFromOutputs(cellData.outputs as any); | ||
change.cell.metadata.executionOrder = undefined; | ||
change.cell.metadata.hasExecutionOrder = change.language !== MARKDOWN_LANGUAGE; // Do not check for Python, to support other languages | ||
change.cell.metadata.runnable = change.language !== MARKDOWN_LANGUAGE; // Do not check for Python, to support other languages | ||
|
||
// Create a new cell & replace old one. | ||
const oldCellIndex = model.cells.indexOf(cellModel); | ||
model.cells[oldCellIndex] = createCellFromVSCNotebookCell(change.document, change.cell, model); | ||
} | ||
|
||
function handleChangesToCells(change: NotebookCellsChangeEvent, model: INotebookModel) { | ||
|
@@ -135,42 +152,22 @@ function handleCellMove(change: NotebookCellsChangeEvent, model: INotebookModel) | |
const cellToSwap = findMappedNotebookCellModel(change.document, insertChange.items[0]!, model.cells); | ||
const cellToSwapWith = model.cells[insertChange.start]; | ||
assert.notEqual(cellToSwap, cellToSwapWith, 'Cannot swap cell with the same cell'); | ||
model.update({ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We will mutate the cells in place, we don't want to change the cells as the cell mapping stops working |
||
source: 'user', | ||
kind: 'swap', | ||
oldDirty: model.isDirty, | ||
newDirty: true, | ||
firstCellId: cellToSwap.id, | ||
secondCellId: cellToSwapWith.id | ||
}); | ||
|
||
const indexOfCellToSwap = model.cells.indexOf(cellToSwap); | ||
model.cells[insertChange.start] = cellToSwap; | ||
model.cells[indexOfCellToSwap] = cellToSwapWith; | ||
} | ||
function handleCellInsertion(change: NotebookCellsChangeEvent, model: INotebookModel) { | ||
assert.equal(change.changes.length, 1, 'When inserting cells we must have only 1 change'); | ||
assert.equal(change.changes[0].items.length, 1, 'Insertion of more than 1 cell is not supported'); | ||
const insertChange = change.changes[0]; | ||
const cell = change.changes[0].items[0]; | ||
const newCell = createCellFromVSCNotebookCell(change.document, cell, model); | ||
|
||
model.update({ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We will mutate the cells in place, we don't want to change the cells as the cell mapping stops working |
||
source: 'user', | ||
kind: 'insert', | ||
newDirty: true, | ||
oldDirty: model.isDirty, | ||
index: insertChange.start, | ||
cell: newCell | ||
}); | ||
model.cells.splice(insertChange.start, 0, newCell); | ||
} | ||
function handleCellDelete(change: NotebookCellsChangeEvent, model: INotebookModel) { | ||
assert.equal(change.changes.length, 1, 'When deleting cells we must have only 1 change'); | ||
const deletionChange = change.changes[0]; | ||
assert.equal(deletionChange.deletedCount, 1, 'Deleting more than one cell is not supported'); | ||
const cellToDelete = model.cells[deletionChange.start]; | ||
model.update({ | ||
source: 'user', | ||
kind: 'remove', | ||
oldDirty: model.isDirty, | ||
newDirty: true, | ||
cell: cellToDelete, | ||
index: deletionChange.start | ||
}); | ||
model.cells.splice(deletionChange.start, 1); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,7 +8,6 @@ import type { KernelMessage } from '@jupyterlab/services'; | |
import { NotebookCell, NotebookCellRunState, NotebookDocument } from 'vscode'; | ||
import { IBaseCellVSCodeMetadata } from '../../../../../types/@jupyterlab_coreutils_nbformat'; | ||
import { createErrorOutput } from '../../../../datascience-ui/common/cellFactory'; | ||
import { INotebookModelModifyChange } from '../../interactive-common/interactiveWindowTypes'; | ||
import { ICell, INotebookModel } from '../../types'; | ||
import { findMappedNotebookCell } from './cellMappers'; | ||
import { createVSCCellOutputsFromOutputs, translateErrorOutput, updateVSCNotebookCellMetadata } from './helpers'; | ||
|
@@ -118,51 +117,23 @@ export function updateCellOutput(vscCell: NotebookCell, cell: ICell, outputs: nb | |
/** | ||
* Store execution start and end times in ISO format for portability. | ||
*/ | ||
export function updateCellExecutionTimes( | ||
notebookCellModel: ICell, | ||
model: INotebookModel, | ||
startTime?: number, | ||
duration?: number | ||
) { | ||
export function updateCellExecutionTimes(notebookCellModel: ICell, startTime?: number, duration?: number) { | ||
const startTimeISO = startTime ? new Date(startTime).toISOString() : undefined; | ||
const endTimeISO = duration && startTime ? new Date(startTime + duration).toISOString() : undefined; | ||
updateCellMetadata( | ||
notebookCellModel, | ||
{ | ||
end_execution_time: endTimeISO, | ||
start_execution_time: startTimeISO | ||
}, | ||
model | ||
); | ||
updateCellMetadata(notebookCellModel, { | ||
end_execution_time: endTimeISO, | ||
start_execution_time: startTimeISO | ||
}); | ||
} | ||
|
||
export function updateCellMetadata( | ||
notebookCellModel: ICell, | ||
metadata: Partial<IBaseCellVSCodeMetadata>, | ||
model: INotebookModel | ||
) { | ||
export function updateCellMetadata(notebookCellModel: ICell, metadata: Partial<IBaseCellVSCodeMetadata>) { | ||
const originalVscodeMetadata: IBaseCellVSCodeMetadata = notebookCellModel.data.metadata.vscode || {}; | ||
// Update our model with the new metadata stored in jupyter. | ||
const newCell: ICell = { | ||
...notebookCellModel, | ||
data: { | ||
...notebookCellModel.data, | ||
metadata: { | ||
...notebookCellModel.data.metadata, | ||
vscode: { | ||
...originalVscodeMetadata, | ||
...metadata | ||
} | ||
} | ||
notebookCellModel.data.metadata = { | ||
...notebookCellModel.data.metadata, | ||
vscode: { | ||
...originalVscodeMetadata, | ||
...metadata | ||
} | ||
}; | ||
const updateCell: INotebookModelModifyChange = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We will mutate the cells in place, we don't want to change the cells as the cell mapping stops working |
||
kind: 'modify', | ||
newCells: [newCell], | ||
oldCells: [notebookCellModel], | ||
newDirty: true, | ||
oldDirty: model.isDirty === true, | ||
source: 'user' | ||
}; | ||
model.update(updateCell); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feels rather crappy. Logic seems like it could be handled by a sub class?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need to complicate this by adding a sub class, most of this code will get much simpler when we use native editor. Else using sub classes we need to look at child class and parent class what's going on.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will make the change.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No I think its simpler this way, now i need to create another class and make private methods protected. I think this is simpler there's barely much going on here.
Again, when we use NativeNotebook majority of the code will go away (no undo/redo, etc).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds okay to me. Just wanted to mention it.
In reply to: 439513672 [](ancestors = 439513672)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh it is a valid suggestion, if there was a bit more code I'd do it, i did want to do it here. Started it out & realized it wasn't worth it.