History: Disable slicing across kernel session

This commit is contained in:
Andrew Head 2019-03-04 21:44:19 -08:00
Родитель 9598ec9606
Коммит e33d788317
17 изменённых файлов: 198 добавлений и 239 удалений

Просмотреть файл

@ -79,8 +79,7 @@ export interface ILocation {
export interface ILocatable {
location: ILocation;
cellPersistentId?: string;
executionCount?: number;
cellExecutionEventId?: string;
}
export const MODULE = 'module';

Просмотреть файл

@ -15,13 +15,12 @@ export class DataflowAnalyzer {
}
private _statementLocationKey(statement: ast.ISyntaxNode) {
if (statement.cellPersistentId != undefined && statement.executionCount != undefined) {
if (statement.cellExecutionEventId != undefined) {
return statement.location.first_line + "," +
statement.location.first_column + "," +
statement.location.last_line + "," +
statement.location.last_column + "," +
statement.cellPersistentId + "," +
statement.executionCount;
statement.cellExecutionEventId;
}
return null;
}

Просмотреть файл

@ -37,18 +37,17 @@ export class SlicedExecution {
) { }
merge(...slicedExecutions: SlicedExecution[]): SlicedExecution {
let cellSlices: { [ cellPersistentId: string ]: { [ executionCount: number ]: CellSlice }} = {};
let cellSlices: { [ cellExecutionEventId: string ]: CellSlice } = {};
let mergedCellSlices = [];
for (let slicedExecution of slicedExecutions.concat(this)) {
for (let cellSlice of slicedExecution.cellSlices) {
let cell = cellSlice.cell;
if (!cellSlices.hasOwnProperty(cell.persistentId)) cellSlices[cell.persistentId] = {};
if (!cellSlices[cell.persistentId].hasOwnProperty(cell.executionCount)) {
if (!cellSlices[cell.executionEventId]) {
let newCellSlice = new CellSlice(cell.deepCopy(), new LocationSet(), cellSlice.executionTime);
cellSlices[cell.persistentId][cell.executionCount] = newCellSlice;
cellSlices[cell.executionEventId] = newCellSlice;
mergedCellSlices.push(newCellSlice);
}
let mergedCellSlice = cellSlices[cell.persistentId][cell.executionCount];
let mergedCellSlice = cellSlices[cell.executionEventId];
mergedCellSlice.slice = mergedCellSlice.slice.union(cellSlice.slice);
}
}
@ -126,19 +125,18 @@ export class ExecutionLogSlicer {
public sliceAllExecutions(cell: ICell, pSeedLocations?: LocationSet): SlicedExecution[] {
// Make a map from cells to their execution times.
let cellExecutionTimes: { [cellPersistentId: string]: { [executionCount: number]: Date } } = {};
let cellExecutionTimes: { [cellExecutionEventId: string]: Date } = {};
for (let execution of this._executionLog) {
if (!cellExecutionTimes[execution.cell.persistentId]) cellExecutionTimes[execution.cell.persistentId] = {};
cellExecutionTimes[execution.cell.persistentId][execution.cell.executionCount] = execution.executionTime;
cellExecutionTimes[execution.cell.executionEventId] = execution.executionTime;
}
return this._executionLog
.filter(execution => execution.cell.persistentId == cell.persistentId)
.filter(execution => execution.cell.executionEventId == cell.executionEventId)
.filter(execution => execution.cell.executionCount != undefined)
.map(execution => {
// Build the program up to that cell.
let program = this._programBuilder.buildTo(execution.cell.persistentId, execution.cell.kernelId, execution.cell.executionCount);
let program = this._programBuilder.buildTo(execution.cell.executionEventId);
if (program == null) return null;
// Set the seed locations for the slice.
@ -154,7 +152,7 @@ export class ExecutionLogSlicer {
}
// Set seed locations were specified relative to the last cell's position in program.
let lastCellLines = program.cellToLineMap[execution.cell.persistentId][execution.cell.executionCount];
let lastCellLines = program.cellToLineMap[execution.cell.executionEventId];
let lastCellStart = Math.min(...lastCellLines.items);
seedLocations = new LocationSet(
...seedLocations.items.map(loc => {
@ -172,11 +170,11 @@ export class ExecutionLogSlicer {
.sort((loc1, loc2) => loc1.first_line - loc2.first_line);
// Get the relative offsets of slice lines in each cell.
let cellSliceLocations: { [cellId: string]: { [executionCount: number]: LocationSet } } = {};
let cellSliceLocations: { [ executionEventId: string ]: LocationSet } = {};
let cellOrder = new Array<ICell>();
sliceLocations.forEach(location => {
let sliceCell = program.lineToCellMap[location.first_line];
let sliceCellLines = program.cellToLineMap[sliceCell.persistentId][sliceCell.executionCount];
let sliceCellLines = program.cellToLineMap[sliceCell.executionEventId];
let sliceCellStart = Math.min(...sliceCellLines.items);
if (cellOrder.indexOf(sliceCell) == -1) {
cellOrder.push(sliceCell);
@ -187,20 +185,19 @@ export class ExecutionLogSlicer {
last_line: location.last_line - sliceCellStart + 1,
last_column: location.last_column
};
if (!cellSliceLocations[sliceCell.persistentId]) cellSliceLocations[sliceCell.persistentId] = {};
if (!cellSliceLocations[sliceCell.persistentId][sliceCell.executionCount]) {
cellSliceLocations[sliceCell.persistentId][sliceCell.executionCount] = new LocationSet();
}
cellSliceLocations[sliceCell.persistentId][sliceCell.executionCount].add(adjustedLocation);
if (!cellSliceLocations[sliceCell.executionEventId]) {
cellSliceLocations[sliceCell.executionEventId] = new LocationSet();
}
cellSliceLocations[sliceCell.executionEventId].add(adjustedLocation);
});
let cellSlices = cellOrder.map((sliceCell): CellSlice => {
let executionTime = undefined;
if (cellExecutionTimes[sliceCell.persistentId] && cellExecutionTimes[sliceCell.persistentId][sliceCell.executionCount]) {
executionTime = cellExecutionTimes[sliceCell.persistentId][sliceCell.executionCount];
if (cellExecutionTimes[sliceCell.executionEventId]) {
executionTime = cellExecutionTimes[sliceCell.executionEventId];
}
return new CellSlice(sliceCell,
cellSliceLocations[sliceCell.persistentId][sliceCell.executionCount],
cellSliceLocations[sliceCell.executionEventId],
executionTime);
});
return new SlicedExecution(execution.executionTime, cellSlices);

Просмотреть файл

@ -7,8 +7,8 @@ import { NumberSet } from "./set";
/**
* Maps to find out what line numbers over a program correspond to what cells.
*/
export type CellToLineMap = { [cellId: string]: { [executionCount: number]: NumberSet } };
export type LineToCellMap = { [line: number]: ICell };
export type CellToLineMap = { [ cellExecutionEventId: string ]: NumberSet };
export type LineToCellMap = { [ line: number ]: ICell };
/**
* A program built from cells.
@ -84,8 +84,7 @@ export class ProgramBuilder {
for (let node of ast.walk(tree)) {
// Sanity check that this is actually a node.
if (node.hasOwnProperty("type")) {
node.executionCount = cell.executionCount;
node.cellPersistentId = cell.persistentId;
node.cellExecutionEventId = cell.executionEventId;
}
}
// By querying for defs and uses right when a cell is added to the log, we
@ -120,23 +119,34 @@ export class ProgramBuilder {
/**
* Build a program from the list of cells. Program will include the cells' contents in
* execution order. It will omit cells that raised errors (syntax or runtime).
* the order they were added to the log. It will omit cells that raised errors (syntax or
* runtime, except for the last cell).
*/
buildTo(cellPersistentId: string, kernelId: string, executionCount: number): Program {
buildTo(cellExecutionEventId: string): Program {
let cellVersions = this._cellPrograms
.filter(cp => cp.cell.persistentId == cellPersistentId)
.map(cp => cp.cell);
let lastCell = cellVersions
.filter(cell => cell.executionCount == executionCount)
.filter(cell => cell.kernelId !== undefined && cell.kernelId === kernelId)[0];
if (!lastCell) return null;
let addingPrograms = false;
let lastExecutionCountSeen;
let cellPrograms = new Array<CellProgram>();
let sortedCellPrograms = this._cellPrograms
.filter(cp => cp.cell.kernelId !== undefined && cp.cell.kernelId === kernelId)
.filter(cp => cp.cell == lastCell || !cp.hasError) // can have error only if it's the last cell
.filter(cp => cp.cell.executionCount != null && cp.cell.executionCount <= lastCell.executionCount)
.sort((cp1, cp2) => cp1.cell.executionCount - cp2.cell.executionCount);
for (let i = this._cellPrograms.length - 1; i >= 0; i--) {
let cellProgram = this._cellPrograms[i];
let cell = cellProgram.cell;
if (!addingPrograms && cell.executionEventId === cellExecutionEventId) {
addingPrograms = true;
lastExecutionCountSeen = cell.executionCount;
cellPrograms.unshift(cellProgram);
continue;
}
if (addingPrograms) {
if (cell.executionCount >= lastExecutionCountSeen) {
break;
}
if (!cellProgram.hasError) {
cellPrograms.unshift(cellProgram);
}
lastExecutionCountSeen = cell.executionCount;
}
}
let code = "";
let currentLine = 1;
@ -150,7 +160,7 @@ export class ProgramBuilder {
location: undefined
};
sortedCellPrograms.forEach(cp => {
cellPrograms.forEach(cp => {
let cell = cp.cell;
let cellCode = cell.text;
@ -162,11 +172,8 @@ export class ProgramBuilder {
for (let l = 0; l < cellLength; l++) { cellLines.push(currentLine + l); }
cellLines.forEach(l => {
lineToCellMap[l] = cell;
if (!cellToLineMap[cell.persistentId]) cellToLineMap[cell.persistentId] = {};
if (!cellToLineMap[cell.persistentId][cell.executionCount]) {
cellToLineMap[cell.persistentId][cell.executionCount] = new NumberSet();
}
cellToLineMap[cell.persistentId][cell.executionCount].add(l);
if (!cellToLineMap[cell.executionEventId]) cellToLineMap[cell.executionEventId] = new NumberSet();
cellToLineMap[cell.executionEventId].add(l);
});
// Accumulate the code text.
@ -199,11 +206,10 @@ export class ProgramBuilder {
return new Program(code, tree, cellToLineMap, lineToCellMap);
}
getCellProgram(cell: ICell): CellProgram {
let matchingPrograms = this._cellPrograms.filter(
(cp) => cp.cell.persistentId == cell.persistentId &&
cp.cell.executionCount == cell.executionCount &&
cp.cell.kernelId !== undefined && cp.cell.kernelId === cell.kernelId);
(cp) => cp.cell.executionEventId == cell.executionEventId);
if (matchingPrograms.length >= 1) return matchingPrograms.pop();
return null;
}

Просмотреть файл

@ -158,7 +158,7 @@ function getCellsJsonForSlice(slice: SlicedExecution, outputSelections?: OutputS
if (originalOutputs) {
for (let i = 0; i < originalOutputs.length; i++) {
let output = originalOutputs[i];
if (outputSelections.some(s => s.cell.persistentId == slicedCell.persistentId && s.outputIndex == i)) {
if (outputSelections.some(s => s.cell.executionEventId == slicedCell.executionEventId && s.outputIndex == i)) {
cellJson.outputs.push(output);
}
}

Просмотреть файл

@ -13,12 +13,6 @@ export interface ICell {
* due to the implementation of Jupyter Lab.
*/
readonly id: string;
/**
* A persistent ID for this cell, that will stay the same even as the notebook is closed and
* re-opened. In general, all gathering functionality should refer to cells using this ID.
*/
readonly persistentId: string;
/**
* Whether this cell was created by gathering code.
@ -36,14 +30,16 @@ export interface ICell {
*/
text: string;
/**
* ID of the session that was last used to execute this cell. Note that unfortunately these
* session IDs reset whenever someone opens a new notebook---let's keep an eye out for ways
* to tie together sessions ID that should be merged.
*/
kernelId: string;
executionCount: number;
/**
* A unique ID generated each time a cell is executed. This lets us disambiguate between two
* runs of a cell that have the same ID *and* execution count, if the kernel was restarted.
* This ID should also be programmed to be *persistent*, so that even after a notebook is
* reloaded, the cell in the same position will still have this ID.
*/
readonly executionEventId: string;
outputs: nbformat.IOutput[];
/**
@ -85,9 +81,8 @@ export abstract class AbstractCell implements ICell {
abstract is_cell: boolean;
abstract id: string;
abstract persistentId: string;
abstract executionCount: number;
abstract kernelId: string;
abstract executionEventId: string;
abstract hasError: boolean;
abstract isCode: boolean;
abstract text: string;
@ -112,7 +107,6 @@ export abstract class AbstractCell implements ICell {
toJSON(): any {
return {
id: this.id,
persistentId: this.persistentId,
executionCount: this.executionCount,
lineCount: this.text.split("\n").length,
isCode: this.isCode,
@ -145,7 +139,7 @@ export abstract class AbstractCell implements ICell {
outputs: this.outputs,
metadata: {
gathered: this.gathered,
persistent_id: this.persistentId,
execution_event_id: this.executionEventId,
}
}
}
@ -157,15 +151,14 @@ export abstract class AbstractCell implements ICell {
export class LogCell extends AbstractCell {
constructor(data: {
id?: string, persistentId?: string, executionCount?: number, kernelId?: string,
id?: string, executionCount?: number, executionEventId?: string,
hasError?: boolean, text?: string, outputs?: nbformat.IOutput[]
}) {
super();
this.is_cell = true;
this.id = data.id || UUID.uuid4();
this.persistentId = data.persistentId || UUID.uuid4();
this.executionCount = data.executionCount || undefined;
this.kernelId = data.kernelId;
this.executionEventId = data.executionEventId || UUID.uuid4();
this.hasError = data.hasError || false;
this.text = data.text || "";
this.lastExecutedText = this.text;
@ -179,9 +172,8 @@ export class LogCell extends AbstractCell {
readonly is_cell: boolean;
readonly id: string;
readonly persistentId: string;
readonly executionCount: number;
readonly kernelId: string;
readonly executionEventId: string;
readonly hasError: boolean;
readonly isCode: boolean;
readonly text: string;
@ -209,11 +201,12 @@ export class LabCell extends AbstractCell {
return this._model.id;
}
get persistentId(): string {
if (!this._model.metadata.has("persistent_id")) {
this._model.metadata.set("persistent_id", UUID.uuid4());
}
return this._model.metadata.get("persistent_id") as string;
get executionEventId(): string {
return this._model.metadata.get("execution_event_id") as string;
}
set executionEventId(id: string) {
this._model.metadata.set('execution_event_id', id);
}
get text(): string {
@ -232,14 +225,6 @@ export class LabCell extends AbstractCell {
this._model.metadata.set('last_executed_text', text);
}
get kernelId(): string {
return this._model.metadata.get('kernel_id') as string;
}
set kernelId(kernelId: string) {
this._model.metadata.set('kernel_id', kernelId);
}
get executionCount(): number {
return this._model.executionCount;
}

Просмотреть файл

@ -163,10 +163,10 @@ export class GatherModel {
/**
* Remove the editor def from the list of editor definitions.
*/
removeEditorDefsForCell(cellPersistentId: string) {
removeEditorDefsForCell(cellExecutionEventId: string) {
for (let i = this._editorDefs.length - 1; i >= 0; i--) {
let editorDef = this._editorDefs[i];
if (editorDef.cell.persistentId == cellPersistentId) {
if (editorDef.cell.executionEventId == cellExecutionEventId) {
this._editorDefs.splice(i, 1);
this.notifyObservers(GatherModelEvent.EDITOR_DEF_REMOVED, editorDef);
}
@ -353,10 +353,10 @@ export class GatherModel {
/**
* Deselect all outputs.
*/
deselectOutputsForCell(cellPersistentId: string) {
deselectOutputsForCell(cellExecutionEventId: string) {
for (let i = this._selectedOutputs.length - 1; i >= 0; i--) {
let output = this._selectedOutputs[i];
if (output.cell.persistentId == cellPersistentId) {
if (output.cell.executionEventId == cellExecutionEventId) {
this._selectedOutputs.splice(i, 1);
this.notifyObservers(GatherModelEvent.OUTPUT_DESELECTED, output);
}

Просмотреть файл

@ -3,6 +3,7 @@ import { NotebookPanel } from "@jupyterlab/notebook";
import { IObservableList } from "@jupyterlab/observables";
import { GatherModel } from "../model";
import { LabCell } from "../model/cell";
import { UUID } from "@phosphor/coreutils";
/**
* Listens to cell executions and edits.
@ -13,7 +14,6 @@ export class CellChangeListener {
constructor(gatherModel: GatherModel, notebook: NotebookPanel) {
this._gatherModel = gatherModel;
this._notebook = notebook;
this._registerCurrentCells(notebook);
notebook.content.model.cells.changed.connect((_, change) => this._registerAddedCells(change), this);
}
@ -29,10 +29,7 @@ export class CellChangeListener {
*/
private _annotateCellWithExecutionInformation(cell: LabCell) {
cell.lastExecutedText = cell.text;
cell.kernelId = this._notebook.session.kernel.model.id;
this._notebook.session.kernel.requestHistory({ output: false, raw: true, hist_access_type: 'tail' }).then((res) => {
console.log("Gotta reply");
});
cell.executionEventId = UUID.uuid4();
}
private _registerCell(cell: ICellModel) {
@ -71,6 +68,4 @@ export class CellChangeListener {
}
}
}
private _notebook: NotebookPanel;
}

Просмотреть файл

@ -1,8 +1,8 @@
import { Cell, CodeCell, isCodeCellModel } from "@jupyterlab/cells";
import { ICell, LabCell } from "../model/cell";
import { Cell, isCodeCellModel } from "@jupyterlab/cells";
import { CodeMirrorEditor } from "@jupyterlab/codemirror";
import { NotebookPanel } from "@jupyterlab/notebook";
import CodeMirror from "codemirror";
import { ICell, LabCell } from "../model/cell";
/**
* Finds the HTML elements in a notebook corresponding to a cell. Useful for looking up HTML
@ -14,41 +14,28 @@ export class NotebookElementFinder {
this._notebook = notebook;
}
getCellWithPersistentId(persistentId: string): Cell | null {
for (let cell of this._notebook.content.widgets) {
if (isCodeCellModel(cell.model)) {
let labCell = new LabCell(cell.model);
if (labCell.persistentId == persistentId) {
return cell;
/**
* Look up cells in the notebook using the ID of the execution event that executed it last.
* This is the only way to make sure we get the right cell if a cell has been executed
* with the same exeuction count twice in two separate notebook sessions.
*/
getCellWidget(cell: ICell): Cell | null {
for (let cellWidget of this._notebook.content.widgets) {
if (isCodeCellModel(cellWidget.model)) {
let labCell = new LabCell(cellWidget.model);
if (labCell.executionEventId == cell.executionEventId) {
return cellWidget;
}
}
}
return null;
}
/**
* Get a cell from the notebook.
* (Don't call this right after a cell execution event, as it takes a while for the
* execution count to update in an executed cell).
*/
getCell(persistentId: string, executionCount?: number): Cell | null {
let cell = this.getCellWithPersistentId(persistentId);
if (cell != null && (cell as CodeCell).model.executionCount == executionCount) {
return cell;
}
return null;
}
/**
* Get the element for the code editor for a cell.
*/
getEditor(cell: ICell): CodeMirror.Editor | null {
let widget = this.getCellWithPersistentId(cell.persistentId);
return this._getEditor(widget);
}
getEditorWithExecutionCount(cell: ICell): CodeMirror.Editor | null {
let widget = this.getCell(cell.persistentId, cell.executionCount);
let widget = this.getCellWidget(cell);
return this._getEditor(widget);
}
@ -63,7 +50,7 @@ export class NotebookElementFinder {
* Finds HTML elements for cell outputs in a notebook.
*/
getOutputs(cell: ICell): HTMLElement[] {
let cellWidget = this.getCell(cell.persistentId, cell.executionCount);
let cellWidget = this.getCellWidget(cell);
let outputElements: HTMLElement[] = [];
if (cellWidget == null) {
return outputElements;

Просмотреть файл

@ -177,7 +177,7 @@ export class MarkerManager implements IGatherObserver {
let defSelection = eventData as DefSelection;
this._defMarkers.filter(marker => {
return defSelection.editorDef.def.location == marker.location &&
defSelection.cell.persistentId == marker.cell.persistentId;
defSelection.cell.executionEventId == marker.cell.executionEventId;
}).forEach(marker => marker.deselect());
let editorDef = defSelection.editorDef;
@ -195,7 +195,7 @@ export class MarkerManager implements IGatherObserver {
let outputSelection = eventData as OutputSelection;
this._outputMarkers.filter(marker => {
return marker.outputIndex == outputSelection.outputIndex &&
marker.cell.persistentId == outputSelection.cell.persistentId;
marker.cell.executionEventId == outputSelection.cell.executionEventId;
}).forEach(marker => marker.deselect());
}
@ -261,8 +261,8 @@ export class MarkerManager implements IGatherObserver {
* Clear all def markers that belong to this editor.
*/
clearSelectablesForCell(cell: ICell) {
this._model.removeEditorDefsForCell(cell.persistentId);
this._model.deselectOutputsForCell(cell.persistentId);
this._model.removeEditorDefsForCell(cell.executionEventId);
this._model.deselectOutputsForCell(cell.executionEventId);
}
/**
@ -304,8 +304,8 @@ export class MarkerManager implements IGatherObserver {
slice.cellSlices.forEach(cellSlice => {
let loggedCell = cellSlice.cell;
let sliceLocations = cellSlice.slice;
let liveCellWidget = this._elementFinder.getCell(loggedCell.persistentId, loggedCell.executionCount);
let editor = this._elementFinder.getEditorWithExecutionCount(loggedCell);
let liveCellWidget = this._elementFinder.getCellWidget(loggedCell);
let editor = this._elementFinder.getEditor(loggedCell);
if (liveCellWidget && editor) {
let liveCell = new LabCell(liveCellWidget.model as ICodeCellModel);
@ -333,8 +333,8 @@ export class MarkerManager implements IGatherObserver {
}
private _updateDependenceHighlightsForCell(cell: ICell) {
let editor = this._elementFinder.getEditorWithExecutionCount(cell);
let liveCellWidget = this._elementFinder.getCell(cell.persistentId, cell.executionCount);
let editor = this._elementFinder.getEditor(cell);
let liveCellWidget = this._elementFinder.getCellWidget(cell);
let liveCell = new LabCell(liveCellWidget.model as ICodeCellModel);
this._dependencyLineMarkers
.filter((marker) => marker.editor == editor)

Просмотреть файл

@ -61,24 +61,24 @@ export class RevisionBrowser extends Widget {
let defSelections = model.selectedDefs;
let outputSelections = model.selectedOutputs;
let slices;
let cellPersistentId;
let cellExecutionEventId;
if (defSelections.length > 0) {
slices = model.getSelectedDefSlices(defSelections[0]);
cellPersistentId = defSelections[0].cell.persistentId;
cellExecutionEventId = defSelections[0].cell.executionEventId;
} else if (outputSelections.length > 0) {
slices = model.getSelectedOutputSlices(outputSelections[0]);
cellPersistentId = outputSelections[0].cell.persistentId;
cellExecutionEventId = outputSelections[0].cell.executionEventId;
}
log("Bringing up the revision browser for selection", {
cellPersistendId: cellPersistentId, slices,
cellPersistendId: cellExecutionEventId, slices,
selectedDefs: model.selectedDefs,
selectedOutputs: model.selectedOutputs
});
if (slices && cellPersistentId) {
if (slices && cellExecutionEventId) {
// Only show output if the selection was output.
let includeOutput = model.selectedOutputs.length >= 1;
let historyModel = buildHistoryModel(
model, cellPersistentId, slices, includeOutput);
model, cellExecutionEventId, slices, includeOutput);
let historyViewer = new HistoryViewer({
model: historyModel,
outputRenderer: this._outputRenderer

Просмотреть файл

@ -89,9 +89,8 @@ function _loadExecutionFromJson(executionJson: JSONObject): CellExecution {
let cellJson = executionJson['cell'] as JSONObject;
let id = _getString(cellJson, 'id');
let persistentId = _getString(cellJson, 'persistentId');
let executionCount = _getNumber(cellJson, 'executionCount');
let kernelId = _getString(cellJson, 'kernelId');
let executionEventId = _getString(cellJson, 'executionEventId');
let hasError = _getBoolean(cellJson, 'hasError');
let text = _getString(cellJson, 'text');
@ -99,7 +98,7 @@ function _loadExecutionFromJson(executionJson: JSONObject): CellExecution {
let executionTime = new Date(executionTimeString);
if (id == null || executionCount == null || hasError == null ||
kernelId == null || text == null || executionTime == null) {
executionEventId == null || text == null || executionTime == null) {
log("Cell could not be loaded, as it's missing a critical field.");
return null;
}
@ -107,6 +106,6 @@ function _loadExecutionFromJson(executionJson: JSONObject): CellExecution {
/**
* TODO(andrewhead): Update with Kunal's code for serializing and deserializing outputs.
*/
let cell = new LogCell({ id, executionCount, hasError, text, persistentId });
let cell = new LogCell({ id, executionCount, hasError, text, executionEventId });
return new CellExecution(cell, executionTime);
}

Просмотреть файл

@ -10,9 +10,8 @@ interface CellExecutionJson extends JSONObject {
interface CellJson extends JSONObject {
id: string;
persistentId: string;
executionEventId: string;
executionCount: number;
kernelId: string;
hasError: boolean;
isCode: boolean;
text: string;
@ -32,9 +31,8 @@ export function storeHistory(notebookModel: INotebookModel, executionLog: Execut
let cell = cellExecution.cell;
let cellJson = new Object(null) as CellJson;
cellJson.id = cell.id;
cellJson.persistentId = cell.persistentId;
cellJson.executionEventId = cell.executionEventId;
cellJson.executionCount = cell.executionCount;
cellJson.kernelId = cell.kernelId;
cellJson.hasError = cell.hasError;
cellJson.text = cell.text;

Просмотреть файл

@ -6,8 +6,9 @@ import { CellSlice } from "../model/cellslice";
describe('SlicedExecution', () => {
function cell(persistentId: string, executionCount: number, ...codeLines: string[]): ICell {
return new LogCell({ executionCount, text: codeLines.join("\n"), persistentId });
function cell(executionEventId: string, text: string, executionCount?: number, id?: string): ICell {
if (executionCount === undefined) executionCount = 1;
return new LogCell({ executionCount, text, executionEventId, id });
}
function cellSlice(cell: ICell, slice: LocationSet): CellSlice {
@ -35,28 +36,28 @@ describe('SlicedExecution', () => {
it('unions slices with different cells', () => {
let slice1 = slicedExecution(
cellSlice(
cell("1", 1, "a = 1"),
cell("1", "a = 1", 1),
new LocationSet(location(1, 0, 1, 5))
));
let slice2 = slicedExecution(
cellSlice(
cell("2", 2, "b = 2"),
cell("2", "b = 2", 2),
new LocationSet(location(1, 0, 1, 5))
));
let merged = slice1.merge(slice2);
expect(merged.cellSlices[0].cell.persistentId).to.equal("1");
expect(merged.cellSlices[1].cell.persistentId).to.equal("2");
expect(merged.cellSlices[0].cell.executionEventId).to.equal("1");
expect(merged.cellSlices[1].cell.executionEventId).to.equal("2");
});
it('will not include the same locations from the same cell twice', () => {
let slice1 = slicedExecution(
cellSlice(
cell("1", 1, "a = 1"),
cell("1", "a = 1"),
new LocationSet(location(1, 0, 1, 5))
));
let slice2 = slicedExecution(
cellSlice(
cell("1", 1, "a = 1"),
cell("1", "a = 1"),
new LocationSet(location(1, 0, 1, 5))
));
let merged = slice1.merge(slice2);
@ -64,15 +65,16 @@ describe('SlicedExecution', () => {
expect(merged.cellSlices[0].slice.size).to.equal(1);
});
it('considers the same cell with different execution counts to be different', () => {
it('considers two cells sharing ID and execution count but differing in execution event ' +
'ID to be different', () => {
let slice1 = slicedExecution(
cellSlice(
cell("1", 1, "a = 1"),
cell("1", "a = 1", 1, "id1"),
new LocationSet(location(1, 0, 1, 5))
));
let slice2 = slicedExecution(
cellSlice(
cell("1", 2, "a = 1"),
cell("2", "a = 1", 1, "id1"),
new LocationSet(location(1, 0, 1, 5))
));
let merged = slice1.merge(slice2);
@ -82,12 +84,12 @@ describe('SlicedExecution', () => {
it('will include complementary ranges from two slices of the same cell', () => {
let slice1 = slicedExecution(
cellSlice(
cell("1", 1, "a = 1"),
cell("1", "a = 1"),
new LocationSet(location(1, 0, 1, 5))
));
let slice2 = slicedExecution(
cellSlice(
cell("1", 1, "a = 1"),
cell("1", "a = 1"),
new LocationSet(location(1, 0, 1, 4))
));
let merged = slice1.merge(slice2);
@ -100,12 +102,12 @@ describe('SlicedExecution', () => {
it('reorders the cells in execution order', () => {
let slice1 = slicedExecution(
cellSlice(
cell("2", 2, "a = 1"),
cell("2", "a = 1", 2),
new LocationSet(location(1, 0, 1, 5))
));
let slice2 = slicedExecution(
cellSlice(
cell("1", 1, "a = 1"),
cell("1", "a = 1", 1),
new LocationSet(location(1, 0, 1, 4))
));
let merged = slice1.merge(slice2);
@ -115,16 +117,16 @@ describe('SlicedExecution', () => {
it('can do an n-way merge with a bunch of cells', () => {
let slice1 = slicedExecution(
cellSlice(cell("1", 1, "a = 1"), new LocationSet(location(1, 0, 1, 5))),
cellSlice(cell("2", 2, "b = 1"), new LocationSet(location(1, 0, 1, 5)))
cellSlice(cell("1", "a = 1"), new LocationSet(location(1, 0, 1, 5))),
cellSlice(cell("2", "b = 1"), new LocationSet(location(1, 0, 1, 5)))
);
let slice2 = slicedExecution(
cellSlice(cell("3", 3, "c = 1"), new LocationSet(location(1, 0, 1, 5))),
cellSlice(cell("4", 4, "d = 1"), new LocationSet(location(1, 0, 1, 5)))
cellSlice(cell("3", "c = 1"), new LocationSet(location(1, 0, 1, 5))),
cellSlice(cell("4", "d = 1"), new LocationSet(location(1, 0, 1, 5)))
);
let slice3 = slicedExecution(
cellSlice(cell("5", 5, "e = 1"), new LocationSet(location(1, 0, 1, 5))),
cellSlice(cell("6", 6, "f = 1"), new LocationSet(location(1, 0, 1, 5)))
cellSlice(cell("5", "e = 1"), new LocationSet(location(1, 0, 1, 5))),
cellSlice(cell("6", "f = 1"), new LocationSet(location(1, 0, 1, 5)))
);
let merged = slice1.merge(slice2, slice3);
expect(merged.cellSlices.length).to.equal(6);

Просмотреть файл

@ -4,17 +4,9 @@ import { ICell, LogCell } from '../model/cell';
describe('program builder', () => {
const TEST_KERNEL_ID = "kernel-1";
function createCellWithKernelId(persistentId: string, kernelId: string, executionCount: number,
...codeLines: string[]): ICell {
let text = codeLines.join("\n");
return new LogCell({ executionCount, persistentId, kernelId, text });
}
function createCell(persistentId: string, executionCount: number, ...codeLines: string[]): ICell {
return(createCellWithKernelId(persistentId, TEST_KERNEL_ID, executionCount, ...codeLines));
function createCell(executionEventId: string, text: string, executionCount?: number): ICell {
return new LogCell({ executionEventId, text, executionCount });
}
let programBuilder: ProgramBuilder;
@ -22,125 +14,125 @@ describe('program builder', () => {
programBuilder = new ProgramBuilder();
});
it('appends cell contents in execution order', () => {
it('appends cell contents in order', () => {
programBuilder.add(
createCell("id1", 2, "print(1)"),
createCell("id2", 1, "print(2)")
createCell("id1", "print(1)"),
createCell("id2", "print(2)")
)
let code = programBuilder.buildTo("id1", TEST_KERNEL_ID, 2).text;
expect(code).to.equal(["print(2)", "print(1)", ""].join("\n"))
let code = programBuilder.buildTo("id2").text;
expect(code).to.equal(["print(1)", "print(2)", ""].join("\n"))
});
it('builds a map from lines to cells', () => {
let cell1 = createCell("id1", 1, "print(1)");
let cell2 = createCell("id2", 2, "print(2)");
let cell1 = createCell("id1", "print(1)");
let cell2 = createCell("id2", "print(2)");
programBuilder.add(cell1, cell2);
let lineToCellMap = programBuilder.buildTo("id2", TEST_KERNEL_ID, 2).lineToCellMap;
let lineToCellMap = programBuilder.buildTo("id2").lineToCellMap;
expect(lineToCellMap[1]).to.equal(cell1);
expect(lineToCellMap[2]).to.equal(cell2);
});
it('builds a map from cells to lines', () => {
let cell1 = createCell("id1", 1, "print(1)");
let cell2 = createCell("id2", 2, "print(2)");
let cell1 = createCell("id1", "print(1)");
let cell2 = createCell("id2", "print(2)");
programBuilder.add(cell1, cell2);
let cellToLineMap = programBuilder.buildTo("id2", TEST_KERNEL_ID, 2).cellToLineMap;
expect(cellToLineMap["id1"][1].items).to.deep.equal([1]);
expect(cellToLineMap["id2"][2].items).to.deep.equal([2]);
let cellToLineMap = programBuilder.buildTo("id2").cellToLineMap;
expect(cellToLineMap["id1"].items).to.deep.equal([1]);
expect(cellToLineMap["id2"].items).to.deep.equal([2]);
});
it('stops after the specified cell', () => {
programBuilder.add(
createCell("id1", 2, "print(1)"),
createCell("id2", 1, "print(2)")
createCell("id1", "print(1)"),
createCell("id2", "print(2)")
);
let code = programBuilder.buildTo("id2", TEST_KERNEL_ID, 1).text;
expect(code).to.equal("print(2)\n");
let code = programBuilder.buildTo("id1").text;
expect(code).to.equal("print(1)\n");
});
/* We might want the program builder to include code that was executed before a runtime
* error, though this will probably require us to rewrite the code. */
it('skips cells with errors', () => {
let badCell = createCell("idE", 2, "print(2)");
let badCell = createCell("idE", "print(2)");
badCell.hasError = true;
programBuilder.add(
createCell("id1", 1, "print(1)"),
createCell("id1", "print(1)"),
badCell,
createCell("id3", 3, "print(3)")
createCell("id3", "print(3)")
);
let code = programBuilder.buildTo("id3", TEST_KERNEL_ID, 3).text;
let code = programBuilder.buildTo("id3").text;
expect(code).to.equal(["print(1)", "print(3)", ""].join("\n"));
});
it('includes cells that end with errors', () => {
let badCell = createCell("idE", 3, "print(bad_name)");
let badCell = createCell("idE", "print(bad_name)");
badCell.hasError = true;
programBuilder.add(
createCell("id1", 1, "print(1)"),
createCell("id2", 2, "print(2)"),
createCell("id1", "print(1)"),
createCell("id2", "print(2)"),
badCell,
);
let code = programBuilder.buildTo("idE", TEST_KERNEL_ID, 3).text;
let code = programBuilder.buildTo("idE").text;
expect(code).to.equal(["print(1)", "print(2)", "print(bad_name)", ""].join("\n"));
});
/* Sometimes, a cell might not throw an error, but our parser might choke. This shouldn't
* crash the entire program---just skip it if it can't parse. */
it('skips cells that fail to parse', () => {
let badCell = createCell("idE", 2, "causes_syntax_error(");
let badCell = createCell("idE", "causes_syntax_error(");
// Hide console output from parse errors.
let oldConsoleLog = console.log;
console.log = () => {};
programBuilder.add(
createCell("id1", 1, "print(1)"),
createCell("id1", "print(1)"),
badCell,
createCell("id3", 3, "print(3)")
createCell("id3", "print(3)")
);
// Restore console output.
console.log = oldConsoleLog;
let code = programBuilder.buildTo("id3", TEST_KERNEL_ID, 3).text;
let code = programBuilder.buildTo("id3").text;
expect(code).to.equal(["print(1)", "print(3)", ""].join("\n"));
});
it('skips cells that were executed with different kernels', () => {
it('skips cells that were executed in prior kernels', () => {
programBuilder.add(
createCellWithKernelId("id1", "kernel-1", 1, "print(1)"),
createCellWithKernelId("id2", "kernel-2", 2, "print(2)"),
createCellWithKernelId("id3", "kernel-1", 3, "print(3)")
createCell("id1", "print(1)", 1),
createCell("id2", "print(2)", 1),
createCell("id3", "print(3)", 2),
createCell("id3", "print(4)", 1)
);
let code = programBuilder.buildTo("id3", "kernel-1", 3).text;
expect(code).to.equals(["print(1)", "print(3)", ""].join("\n"));
let code = programBuilder.buildTo("id3").text;
expect(code).to.equals(["print(4)", ""].join("\n"));
});
it('constructs a tree for the program', () => {
programBuilder.add(
createCell("id1", 1, "print(1)"),
createCell("id2", 2, "print(2)")
createCell("id1", "print(1)"),
createCell("id2", "print(2)")
)
let tree = programBuilder.buildTo("id2", TEST_KERNEL_ID, 2).tree;
let tree = programBuilder.buildTo("id2").tree;
expect(tree.code.length).to.equal(2);
});
it('adjusts the node locations', () => {
programBuilder.add(
createCell("id1", 1, "print(1)"),
createCell("id2", 2, "print(2)")
createCell("id1", "print(1)"),
createCell("id2", "print(2)")
)
let tree = programBuilder.buildTo("id2", TEST_KERNEL_ID, 2).tree;
let tree = programBuilder.buildTo("id2").tree;
expect(tree.code[0].location.first_line).to.equal(1);
expect(tree.code[1].location.first_line).to.equal(2);
});
it('annotates tree nodes with cell ID info', () => {
programBuilder.add(
createCell("id1", 2, "print(1)")
createCell("id1", "print(1)")
);
let tree = programBuilder.buildTo("id1", TEST_KERNEL_ID, 2).tree;
expect(tree.code[0].cellPersistentId).to.equal("id1");
expect(tree.code[0].executionCount).to.equal(2);
let tree = programBuilder.buildTo("id1").tree;
expect(tree.code[0].cellExecutionEventId).to.equal("id1");
});
});

Просмотреть файл

@ -15,7 +15,7 @@ import { HistoryModel } from './model';
*/
export function buildHistoryModel(
gatherModel: GatherModel,
selectedCellPersistentId: string,
selectedCellExecutionEventId: string,
executionVersions: SlicedExecution[],
includeOutput?: boolean
): HistoryModel {
@ -24,9 +24,9 @@ export function buildHistoryModel(
// recent version, save a mapping from cells' IDs to their content, so we can look them up to
// make comparisons between versions of cells.
let lastestVersion = executionVersions[executionVersions.length - 1];
let latestCellVersions: { [cellPersistentId: string]: CellSlice } = {};
let latestCellVersions: { [cellExecutionEvent: string]: CellSlice } = {};
lastestVersion.cellSlices.forEach(cellSlice => {
latestCellVersions[cellSlice.cell.persistentId] = cellSlice;
latestCellVersions[cellSlice.cell.executionEventId] = cellSlice;
});
// Compute diffs between each of the previous revisions and the current revision.
@ -40,7 +40,7 @@ export function buildHistoryModel(
executionVersion.cellSlices.forEach(function (cellSlice) {
let cell = cellSlice.cell;
let recentCellVersion = latestCellVersions[cell.persistentId];
let recentCellVersion = latestCellVersions[cell.executionEventId];
let latestText: string = "";
if (recentCellVersion) {
latestText = recentCellVersion.textSliceLines;
@ -50,7 +50,7 @@ export function buildHistoryModel(
let diff = computeTextDiff(latestText, thisVersionText);
let slicedCell: SlicedCellModel = new SlicedCellModel({
cellPersistentId: cell.persistentId,
executionEventId: cell.executionEventId,
executionCount: cell.executionCount,
sourceCode: diff.text,
diff: diff
@ -62,7 +62,7 @@ export function buildHistoryModel(
if (includeOutput) {
let selectedCell: ICell = null;
executionVersion.cellSlices.map(cs => cs.cell).forEach(function (cellModel) {
if (cellModel.persistentId == selectedCellPersistentId) {
if (cellModel.executionEventId == selectedCellExecutionEventId) {
selectedCell = cellModel;
}
});

Просмотреть файл

@ -7,9 +7,9 @@ import { Diff } from '../history/diff';
*/
export interface ISlicedCellModel extends CodeEditor.IModel {
/**
* A unique ID for a cell.
* A unique ID for a logged cell, computed at the moment of execution.
*/
readonly cellPersistentId: string;
readonly cellExecutionEventId: string;
/**
* The execution count for the cell.
@ -38,7 +38,7 @@ export class SlicedCellModel extends CodeEditor.Model implements ISlicedCellMode
constructor(options: SlicedCellModel.IOptions) {
super({ modelDB: options.modelDB });
this._cellPersistentId = options.cellPersistentId;
this._cellExecutionEventId = options.executionEventId;
this._executionCount = options.executionCount;
this._sourceCode = options.sourceCode;
this._diff = options.diff;
@ -50,8 +50,8 @@ export class SlicedCellModel extends CodeEditor.Model implements ISlicedCellMode
/**
* Get the cell ID.
*/
get cellPersistentId(): string {
return this._cellPersistentId;
get cellExecutionEventId(): string {
return this._cellExecutionEventId;
}
/**
@ -76,7 +76,7 @@ export class SlicedCellModel extends CodeEditor.Model implements ISlicedCellMode
return this._diff;
}
private _cellPersistentId: string;
private _cellExecutionEventId: string;
private _executionCount: number;
private _sourceCode: string;
private _diff:Diff;
@ -93,7 +93,7 @@ export namespace SlicedCellModel {
/**
* A unique ID for a cell.
*/
cellPersistentId: string;
executionEventId: string;
/**
* The execution count for the cell.