Skip to content

Commit

Permalink
Merge pull request #969 from minrk/undelete-stack
Browse files Browse the repository at this point in the history
multiple-undo for cell deletion
  • Loading branch information
minrk committed Feb 6, 2016
2 parents 38464ef + 5eec6a5 commit 4d64519
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 39 deletions.
69 changes: 33 additions & 36 deletions notebook/static/notebook/js/notebook.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,7 @@ define(function (require) {
this.kernel = null;
this.kernel_busy = false;
this.clipboard = null;
this.undelete_backup = null;
this.undelete_index = null;
this.undelete_below = false;
this.undelete_backup_stack = [];
this.paste_enabled = false;
this.writable = false;
// It is important to start out in command mode to match the intial mode
Expand Down Expand Up @@ -1007,7 +1005,11 @@ define(function (require) {
indices = this.get_selected_cells_indices();
}

this.undelete_backup = [];
var undelete_backup = {
cells: [],
below: false,
index: 0,
};

var cursor_ix_before = this.get_selected_index();
var deleting_before_cursor = 0;
Expand All @@ -1027,45 +1029,42 @@ define(function (require) {
indices.sort(function(a, b) {return b-a;});
for (i=0; i < indices.length; i++) {
var cell = this.get_cell(indices[i]);
this.undelete_backup.push(cell.toJSON());
undelete_backup.cells.push(cell.toJSON());
this.get_cell_element(indices[i]).remove();
this.events.trigger('delete.Cell', {'cell': cell, 'index': indices[i]});
}

// Flip the backup copy of cells back to first-to-last order
this.undelete_backup.reverse();

var new_ncells = this.ncells();
// Always make sure we have at least one cell.
if (new_ncells === 0) {
this.insert_cell_below('code');
new_ncells = 1;
}

this.undelete_below = false;
var cursor_ix_after = this.get_selected_index();
if (cursor_ix_after === null) {
// Selected cell was deleted
cursor_ix_after = cursor_ix_before - deleting_before_cursor;
if (cursor_ix_after >= new_ncells) {
cursor_ix_after = new_ncells - 1;
this.undelete_below = true;
undelete_backup.below = true;
}
this.select(cursor_ix_after);
}

// Check if the cells were after the cursor
for (i=0; i < indices.length; i++) {
if (indices[i] > cursor_ix_before) {
this.undelete_below = true;
undelete_backup.below = true;
}
}

// This will put all the deleted cells back in one location, rather than
// where they came from. It will do until we have proper undo support.
this.undelete_index = cursor_ix_after;
undelete_backup.index = cursor_ix_after;
$('#undelete_cell').removeClass('disabled');

this.undelete_backup_stack.push(undelete_backup);
this.set_dirty(true);

return this;
Expand All @@ -1089,29 +1088,25 @@ define(function (require) {
* Restore the most recently deleted cells.
*/
Notebook.prototype.undelete_cell = function() {
if (this.undelete_backup !== null && this.undelete_index !== null) {
var i, cell_data, new_cell;
if (this.undelete_below) {
for (i = this.undelete_backup.length-1; i >= 0; i--) {
cell_data = this.undelete_backup[i];
new_cell = this.insert_cell_below(cell_data.cell_type,
this.undelete_index);
new_cell.fromJSON(cell_data);
}
if (this.undelete_backup_stack.length > 0) {
var undelete_backup = this.undelete_backup_stack.pop();
var i, cell_data, new_cell, insert;
if (undelete_backup.below) {
insert = $.proxy(this.insert_cell_below, this);
} else {
for (i=0; i < this.undelete_backup.length; i++) {
cell_data = this.undelete_backup[i];
new_cell = this.insert_cell_above(cell_data.cell_type,
this.undelete_index);
new_cell.fromJSON(cell_data);
}
insert = $.proxy(this.insert_cell_above, this);
}
for (i=0; i < undelete_backup.cells.length; i++) {
cell_data = undelete_backup.cells[i];
new_cell = insert(cell_data.cell_type, undelete_backup.index);
new_cell.fromJSON(cell_data);
}

this.set_dirty(true);
this.undelete_backup = null;
this.undelete_index = null;
}
$('#undelete_cell').addClass('disabled');
if (this.undelete_backup_stack.length === 0) {
$('#undelete_cell').addClass('disabled');
}
};

/**
Expand Down Expand Up @@ -1216,11 +1211,13 @@ define(function (require) {
} else {
return false;
}

if (this.undelete_index !== null && index <= this.undelete_index) {
this.undelete_index = this.undelete_index + 1;
this.set_dirty(true);
}

this.undelete_backup_stack.map(function (undelete_backup) {
if (index < undelete_backup.index) {
undelete_backup.index += 1;
}
});
this.set_dirty(true);
return true;
};

Expand All @@ -1233,7 +1230,7 @@ define(function (require) {
* @return {Cell|null} handle to created cell or null
*/
Notebook.prototype.insert_cell_above = function (type, index) {
if (index === null || index === undefined) {
if (index === null || index === undefined) {
index = Math.min(this.get_selected_index(index), this.get_anchor_index());
}
return this.insert_cell_at_index(type, index);
Expand Down
44 changes: 41 additions & 3 deletions notebook/tests/notebook/undelete.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,55 @@ casper.notebook_test(function () {
this.trigger_keydown('esc');
assert_cells("initial state", [a, b, c, d], 0);

// Delete cell 1
// Delete cells 1,2
this.select_cells(1,3);
this.trigger_keydown('esc');
this.trigger_keydown('d', 'd');
assert_cells("delete cells 1,2", [a, d], 1);

// Delete cell 1 (3)
this.select_cell(1);
this.trigger_keydown('esc');
this.trigger_keydown('d', 'd');
assert_cells("delete cell 1", [a, c, d], 1);
assert_cells("delete cell 1 (3)", [a], 0);

// Undelete cell 1 (3)
this.evaluate(function () {
IPython.notebook.undelete_cell();
});
assert_cells("undelete cell 1 (3)", [a, d], 0);

this.select_cell(1); // select after undelete, to test cursor movement

// Undelete cell 1
this.evaluate(function () {
IPython.notebook.undelete_cell();
});
assert_cells("undelete cell 1", [a, b, c, d], 2);
assert_cells("undelete cell 1,2", [a, b, c, d], 3);

// Undelete cell (none)
this.evaluate(function () {
IPython.notebook.undelete_cell();
});
assert_cells("undelete cell (none)", [a, b, c, d], 3);

this.select_cells(0,2);
this.trigger_keydown('esc');
this.trigger_keydown('d', 'd');
assert_cells("delete first two cells", [c, d], 0);
this.evaluate(function () {
IPython.notebook.undelete_cell();
});
assert_cells("undelete first two cells", [a, b, c, d], 2);

this.select_cells(2, 4);
this.trigger_keydown('esc');
this.trigger_keydown('d', 'd');
assert_cells("delete last two cells", [a, b], 1);
this.evaluate(function () {
IPython.notebook.undelete_cell();
});
assert_cells("undelete last two cells", [a, b, c, d], 1);

// Merge cells 1-2
var bc = b + "\n\n" + c;
Expand Down
9 changes: 9 additions & 0 deletions notebook/tests/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,15 @@ casper.select_cell = function(index, moveanchor) {
}, {i: index, moveanchor: moveanchor});
};

casper.select_cells = function(index, bound, moveanchor) {
// Select a block of cells in the notebook.
// like Python range, selects [index,bound)
this.evaluate(function (i, n, moveanchor) {
Jupyter.notebook.select(i, moveanchor);
Jupyter.notebook.extend_selection_by(n);
}, {i: index, n: (bound - index - 1), moveanchor: moveanchor});
};

casper.click_cell_editor = function(index) {
// Emulate a click on a cell's editor.

Expand Down

0 comments on commit 4d64519

Please sign in to comment.