ソースを参照

Merge pull request #1437 from jjaraalm/master

Add support for undeleteable cells
Steven Silvester 8 年 前
コミット
66512ee073
2 ファイル変更62 行追加66 行削除
  1. 56 63
      src/notebook/notebook/actions.ts
  2. 6 3
      test/src/notebook/notebook/actions.spec.ts

+ 56 - 63
src/notebook/notebook/actions.ts

@@ -178,32 +178,38 @@ namespace NotebookActions {
     }
     let model = widget.model;
     let cells = model.cells;
-    let toDelete: ICellModel[] = [];
-    let index = -1;
+    let toDelete: number[] = [];
     widget.mode = 'command';
 
     // Find the cells to delete.
     each(enumerate(widget.widgets), ([i, child]) => {
-      if (widget.isSelected(child)) {
-        index = i;
-        toDelete.push(cells.at(i));
+      let deletable = child.model.getMetadata('deletable').getValue();
+      if (widget.isSelected(child) && deletable !== false) {
+        toDelete.push(i);
       }
     });
 
-    // Delete the cells as one undo event.
-    cells.beginCompoundOperation();
-    each(toDelete, cell => {
-      cells.remove(cell);
-    });
-    // The model will add a new code cell if there are no
-    // remaining cells.
-    model.cells.endCompoundOperation();
-
-    // Select the cell *after* the last selected.
-    // Note: The activeCellIndex is clamped to the available cells,
-    // so if the last cell is deleted the previous cell will be activated.
-    index -= toDelete.length - 1;
-    widget.activeCellIndex = index;
+    // If cells are not deletable, we may not have anything to delete.
+    if (toDelete.length > 0) {
+      // Delete the cells as one undo event.
+      cells.beginCompoundOperation();
+      each(toDelete.reverse(), i => {
+        cells.removeAt(i);
+      });
+      // The model will add a new code cell if there are no
+      // remaining cells.
+      model.cells.endCompoundOperation();
+
+      // Select the *first* interior cell not deleted or the cell
+      // *after* the last selected cell.
+      // Note: The activeCellIndex is clamped to the available cells,
+      // so if the last cell is deleted the previous cell will be activated.
+      widget.activeCellIndex = toDelete[0];
+    }
+    
+    // Deselect any remaining, undeletable cells. Do this even if we don't
+    // delete anything so that users are aware *something* happened. 
+    widget.deselectAll();
   }
 
   /**
@@ -608,6 +614,35 @@ namespace NotebookActions {
     widget.scrollToActiveCell();
   }
 
+  /**
+   * Copy or cut the selected cell data to a clipboard.
+   *
+   * @param widget - The target notebook widget.
+   *
+   * @param clipboard - The clipboard object.
+   *
+   * @param cut - Whether to copy or cut.
+   */
+   function copyOrCut(widget: Notebook, clipboard: IClipboard, cut: boolean): void {
+     if (!widget.model || !widget.activeCell) {
+       return;
+     }
+     widget.mode = 'command';
+     clipboard.clear();
+     let data: nbformat.IBaseCell[] = [];
+     each(widget.widgets, child => {
+       if (widget.isSelected(child)) {
+         data.push(child.model.toJSON());
+       }
+     });
+     clipboard.setData(JUPYTER_CELL_MIME, data);
+     if (cut) {
+       deleteCells(widget);
+     } else {
+       widget.deselectAll();
+     }
+   }
+
   /**
    * Copy the selected cell data to a clipboard.
    *
@@ -617,19 +652,7 @@ namespace NotebookActions {
    */
   export
   function copy(widget: Notebook, clipboard: IClipboard): void {
-    if (!widget.model || !widget.activeCell) {
-      return;
-    }
-    widget.mode = 'command';
-    clipboard.clear();
-    let data: nbformat.IBaseCell[] = [];
-    each(widget.widgets, child => {
-      if (widget.isSelected(child)) {
-        data.push(child.model.toJSON());
-      }
-    });
-    clipboard.setData(JUPYTER_CELL_MIME, data);
-    widget.deselectAll();
+    copyOrCut(widget, clipboard, false);
   }
 
   /**
@@ -645,37 +668,7 @@ namespace NotebookActions {
    */
   export
   function cut(widget: Notebook, clipboard: IClipboard): void {
-    if (!widget.model || !widget.activeCell) {
-      return;
-    }
-    let data: nbformat.IBaseCell[] = [];
-    let model = widget.model;
-    let cells = model.cells;
-    let toDelete: ICellModel[] = [];
-    widget.mode = 'command';
-
-    // Gather the cell data.
-    each(widget.widgets, child => {
-      if (widget.isSelected(child)) {
-        data.push(child.model.toJSON());
-        toDelete.push(child.model);
-      }
-    });
-
-    // Preserve the history as one undo event.
-    model.cells.beginCompoundOperation();
-    each(toDelete, cell => {
-      cells.remove(cell);
-    });
-
-    // If there are no cells, add a code cell.
-    if (!model.cells.length) {
-      let cell = model.factory.createCodeCell();
-      model.cells.pushBack(cell);
-    }
-    model.cells.endCompoundOperation();
-
-    clipboard.setData(JUPYTER_CELL_MIME, data);
+    copyOrCut(widget, clipboard, true);
   }
 
   /**

+ 6 - 3
test/src/notebook/notebook/actions.spec.ts

@@ -1104,13 +1104,16 @@ describe('notebook/notebook/actions', () => {
         expect(widget.widgets.at(0).model.source).to.be(source);
       });
 
-      it('should add a new code cell if all cells were cut', () => {
+      it('should add a new code cell if all cells were cut', (done) => {
         for (let i = 0; i < widget.widgets.length; i++) {
           widget.select(widget.widgets.at(i));
         }
         NotebookActions.cut(widget, clipboard);
-        expect(widget.widgets.length).to.be(1);
-        expect(widget.activeCell).to.be.a(CodeCellWidget);
+        requestAnimationFrame(() => {
+          expect(widget.widgets.length).to.be(1);
+          expect(widget.activeCell).to.be.a(CodeCellWidget);
+          done();
+        });
       });
 
     });