Bläddra i källkod

Context menu item for scrolling output (#3545)

* Initial work to add scrolling output.

* Initial working version with styling.

* Reset scrolling and better styling.

* refactor notebook context menu

* More menu work for context menus in notebook.

* More work on notebook context menu.

* Missing .jp-mod-focus

* Add code docs.
Brian E. Granger 7 år sedan
förälder
incheckning
a47bb82a6c

+ 16 - 0
packages/cells/src/widget.ts

@@ -614,6 +614,17 @@ class CodeCell extends Cell {
     this._outputHidden = value;
   }
 
+  /**
+   * Whether the output is in a scrolled state?
+   */
+  get outputsScrolled(): boolean {
+    return this._outputsScrolled;
+  }
+  set outputsScrolled(value: boolean) {
+    this.toggleClass('jp-mod-outputsScrolled', value);
+    this._outputsScrolled = value;
+  }
+
   /**
    * Handle the input being hidden.
    *
@@ -714,10 +725,15 @@ class CodeCell extends Cell {
   private _outputLengthHandler(sender: OutputArea, args: number) {
     let force = args === 0 ? true : false;
     this.toggleClass(NO_OUTPUTS_CLASS, force);
+    /* Turn off scrolling outputs if there are none */
+    if (force) {
+      this.outputsScrolled = false;
+    }
   }
 
   private _rendermime: RenderMimeRegistry = null;
   private _outputHidden = false;
+  private _outputsScrolled = false;
   private _outputWrapper: Widget = null;
   private _outputCollapser: OutputCollapser = null;
   private _outputPlaceholder: OutputPlaceholder = null;

+ 14 - 0
packages/cells/style/widget.css

@@ -10,6 +10,7 @@
 
 
 :root {
+  --jp-private-cell-scrolling-output-offset: 5px;
 }
 
 
@@ -87,6 +88,19 @@
 }
 
 
+.jp-CodeCell.jp-mod-outputsScrolled .jp-Cell-outputArea {
+  overflow-y: auto;
+  max-height: 200px;
+  box-shadow: inset 0 0 6px 2px rgba(0,0,0,0.3);
+  margin-left: var(--jp-private-cell-scrolling-output-offset);
+}
+
+
+.jp-CodeCell.jp-mod-outputsScrolled .jp-OutputArea-prompt {
+  flex: 0 0 calc( var(--jp-cell-prompt-width) - var(--jp-private-cell-scrolling-output-offset) );
+}
+
+
 /*-----------------------------------------------------------------------------
 | CodeCell
 |----------------------------------------------------------------------------*/

+ 135 - 45
packages/notebook-extension/src/index.ts

@@ -60,7 +60,6 @@ import {
 } from '@phosphor/widgets';
 
 
-
 /**
  * The command IDs used by the notebook plugin.
  */
@@ -245,6 +244,11 @@ namespace CommandIDs {
   export
   const showAllOutputs = 'notebook:show-all-cell-outputs';
 
+  export
+  const enableOutputScrolling = 'notebook:enable-output-scrolling';
+
+  export
+  const disableOutputScrolling = 'notebook:disable-output-scrolling';
 }
 
 
@@ -547,18 +551,92 @@ function activateNotebookHandler(app: JupyterLab, mainMenu: IMainMenu, palette:
     });
   }
 
+  // Cell context menu groups
   app.contextMenu.addItem({
-    command: CommandIDs.clearOutputs,
-    selector: '.jp-Notebook.jp-mod-focus .jp-Cell'
+    type: 'separator',
+    selector: '.jp-Notebook.jp-mod-focus  .jp-Cell',
+    rank: 0
+  });
+  app.contextMenu.addItem({
+    command: CommandIDs.cut,
+    selector: '.jp-Notebook.jp-mod-focus .jp-Cell',
+    rank: 1
+  });
+  app.contextMenu.addItem({
+    command: CommandIDs.copy,
+    selector: '.jp-Notebook.jp-mod-focus .jp-Cell',
+    rank: 2
+  });
+  app.contextMenu.addItem({
+    command: CommandIDs.pasteBelow,
+    selector: '.jp-Notebook.jp-mod-focus .jp-Cell',
+    rank: 3
+  });
+  app.contextMenu.addItem({
+    type: 'separator',
+    selector: '.jp-Notebook.jp-mod-focus  .jp-Cell',
+    rank: 4
+  });
+  app.contextMenu.addItem({
+    command: CommandIDs.deleteCell,
+    selector: '.jp-Notebook.jp-mod-focus .jp-Cell',
+    rank: 5
+  });
+  app.contextMenu.addItem({
+    type: 'separator',
+    selector: '.jp-Notebook.jp-mod-focus .jp-Cell',
+    rank: 6
   });
   app.contextMenu.addItem({
     command: CommandIDs.split,
-    selector: '.jp-Notebook.jp-mod-focus .jp-Cell'
+    selector: '.jp-Notebook.jp-mod-focus .jp-Cell',
+    rank: 7
+  });
+
+  // CodeCell context menu groups
+  app.contextMenu.addItem({
+    type: 'separator',
+    selector: '.jp-Notebook.jp-mod-focus .jp-CodeCell',
+    rank: 8
+  });
+  app.contextMenu.addItem({
+    command: CommandIDs.clearOutputs,
+    selector: '.jp-Notebook.jp-mod-focus .jp-CodeCell',
+    rank: 9
+  });
+  app.contextMenu.addItem({
+    command: CommandIDs.clearAllOutputs,
+    selector: '.jp-Notebook.jp-mod-focus .jp-CodeCell',
+    rank: 10
+  });
+  app.contextMenu.addItem({
+    type: 'separator',
+    selector: '.jp-Notebook.jp-mod-focus .jp-CodeCell',
+    rank: 11
+  });
+  app.contextMenu.addItem({
+    command: CommandIDs.enableOutputScrolling,
+    selector: '.jp-Notebook.jp-mod-focus .jp-CodeCell',
+    rank: 12
+  });
+  app.contextMenu.addItem({
+    command: CommandIDs.disableOutputScrolling,
+    selector: '.jp-Notebook.jp-mod-focus .jp-CodeCell',
+    rank: 13
+  });
+  app.contextMenu.addItem({
+    type: 'separator',
+    selector: '.jp-Notebook.jp-mod-focus .jp-CodeCell',
+    rank: 14
   });
   app.contextMenu.addItem({
     command: CommandIDs.createOutputView,
-    selector: '.jp-Notebook.jp-mod-focus .jp-Cell'
+    selector: '.jp-Notebook.jp-mod-focus .jp-CodeCell',
+    rank: 15
   });
+
+
+  // Notebook context menu groups
   app.contextMenu.addItem({
     type: 'separator',
     selector: '.jp-Notebook.jp-mod-focus',
@@ -577,19 +655,16 @@ function activateNotebookHandler(app: JupyterLab, mainMenu: IMainMenu, palette:
   app.contextMenu.addItem({
     type: 'separator',
     selector: '.jp-Notebook.jp-mod-focus',
-    rank: 0
-  });
-  app.contextMenu.addItem({
-    command: CommandIDs.createConsole,
-    selector: '.jp-Notebook.jp-mod-focus',
     rank: 3
   });
   app.contextMenu.addItem({
-    command: CommandIDs.clearAllOutputs,
+    command: CommandIDs.createConsole,
     selector: '.jp-Notebook.jp-mod-focus',
-    rank: 3
+    rank: 4
   });
 
+
+
   return tracker;
 }
 
@@ -621,6 +696,25 @@ function addCommands(app: JupyterLab, services: ServiceManager, tracker: Noteboo
            tracker.currentWidget === app.shell.currentWidget;
   }
 
+  /**
+   * Whether there is an notebook active, with a single selected cell.
+   */
+  function isEnabledAndSingleSelected(): boolean {
+    if (!isEnabled()) { return false; }
+    const { notebook } = tracker.currentWidget;
+    const index = notebook.activeCellIndex;
+    // Can't run above if we are at the top of a notebook.
+    if (index === notebook.widgets.length - 1) { return false; }
+    // If there are selections that are not the active cell,
+    // this command is confusing, so disable it.
+    for (let i = 0; i < notebook.widgets.length; ++i) {
+      if (notebook.isSelected(notebook.widgets[i]) && i !== index) {
+        return false;
+      }
+    }
+    return true;
+  }
+
   commands.addCommand(CommandIDs.runAndAdvance, {
     label: 'Run Cells and Select Below',
     execute: args => {
@@ -684,21 +778,7 @@ function addCommands(app: JupyterLab, services: ServiceManager, tracker: Noteboo
         return NotebookActions.runAllAbove(notebook, context.session);
       }
     },
-    isEnabled: args => {
-      if (!isEnabled()) { return false; }
-      const { notebook } = tracker.currentWidget;
-      const index = notebook.activeCellIndex;
-      // Can't run above if we are at the top of a notebook.
-      if (index === 0) { return false; }
-      // If there are selections that are not the active cell,
-      // this command is confusing, so disable it.
-      for (let i = 0; i < notebook.widgets.length; ++i) {
-        if (notebook.isSelected(notebook.widgets[i]) && i !== index) {
-          return false;
-        }
-      }
-      return true;
-    }
+    isEnabled: isEnabledAndSingleSelected
   });
   commands.addCommand(CommandIDs.runAllBelow, {
     label: 'Run Selected Cell and All Below',
@@ -711,21 +791,7 @@ function addCommands(app: JupyterLab, services: ServiceManager, tracker: Noteboo
         return NotebookActions.runAllBelow(notebook, context.session);
       }
     },
-    isEnabled: args => {
-      if (!isEnabled()) { return false; }
-      const { notebook } = tracker.currentWidget;
-      const index = notebook.activeCellIndex;
-      // Can't run above if we are at the top of a notebook.
-      if (index === notebook.widgets.length - 1) { return false; }
-      // If there are selections that are not the active cell,
-      // this command is confusing, so disable it.
-      for (let i = 0; i < notebook.widgets.length; ++i) {
-        if (notebook.isSelected(notebook.widgets[i]) && i !== index) {
-          return false;
-        }
-      }
-      return true;
-    }
+    isEnabled: isEnabledAndSingleSelected
   });
   commands.addCommand(CommandIDs.restart, {
     label: 'Restart Kernel',
@@ -849,7 +915,7 @@ function addCommands(app: JupyterLab, services: ServiceManager, tracker: Noteboo
     isEnabled
   });
   commands.addCommand(CommandIDs.clearOutputs, {
-    label: 'Clear Selected Outputs',
+    label: 'Clear Outputs',
     execute: args => {
       const current = getCurrent(args);
 
@@ -1224,7 +1290,7 @@ function addCommands(app: JupyterLab, services: ServiceManager, tracker: Noteboo
       // Remove the output view if the parent notebook is closed.
       nb.disposed.connect(widget.dispose);
     },
-    isEnabled
+    isEnabled: isEnabledAndSingleSelected
   });
   commands.addCommand(CommandIDs.createConsole, {
     label: 'Create Console for Notebook',
@@ -1402,6 +1468,28 @@ function addCommands(app: JupyterLab, services: ServiceManager, tracker: Noteboo
     },
     isEnabled
   });
+  commands.addCommand(CommandIDs.enableOutputScrolling, {
+    label: 'Enable Scrolling for Outputs',
+    execute: args => {
+      const current = getCurrent(args);
+
+      if (current) {
+        return NotebookActions.enableOutputScrolling(current.notebook);
+      }
+    },
+    isEnabled
+  });
+  commands.addCommand(CommandIDs.disableOutputScrolling, {
+    label: 'Disable Scrolling for Outputs',
+    execute: args => {
+      const current = getCurrent(args);
+
+      if (current) {
+        return NotebookActions.disableOutputScrolling(current.notebook);
+      }
+    },
+    isEnabled
+  });
 }
 
 
@@ -1477,6 +1565,8 @@ function populatePalette(palette: ICommandPalette): void {
     CommandIDs.showOutput,
     CommandIDs.hideAllOutputs,
     CommandIDs.showAllOutputs,
+    CommandIDs.enableOutputScrolling,
+    CommandIDs.disableOutputScrolling
   ].forEach(command => { palette.addItem({ command, category }); });
 }
 
@@ -1497,7 +1587,7 @@ function populateMenus(app: JupyterLab, mainMenu: IMainMenu, tracker: INotebookT
   // Add a clearer to the edit menu
   mainMenu.editMenu.clearers.add({
     tracker,
-    noun: 'Selected Outputs',
+    noun: 'Outputs',
     pluralNoun: 'Outputs',
     clearCurrent: (current: NotebookPanel) => {
       return NotebookActions.clearOutputs(current.notebook);

+ 41 - 0
packages/notebook/src/actions.ts

@@ -1043,6 +1043,47 @@ namespace NotebookActions {
     Private.handleState(widget, state);
   }
 
+  /**
+   * Enable output scrolling for all selected cells.
+   *
+   * @param widget - The target notebook widget.
+   */
+  export
+  function enableOutputScrolling(widget: Notebook): void {
+    if (!widget.model || !widget.activeCell) {
+      return;
+    }
+    let state = Private.getState(widget);
+    let cells = widget.widgets;
+    each(cells, (cell: Cell) => {
+      if (widget.isSelectedOrActive(cell) && cell.model.type === 'code') {
+        (cell as CodeCell).outputsScrolled = true;
+      }
+    });
+    Private.handleState(widget, state);
+  }
+
+  /**
+   * Disable output scrolling for all selected cells.
+   *
+   * @param widget - The target notebook widget.
+   */
+  export
+  function disableOutputScrolling(widget: Notebook): void {
+    if (!widget.model || !widget.activeCell) {
+      return;
+    }
+    let state = Private.getState(widget);
+    let cells = widget.widgets;
+    each(cells, (cell: Cell) => {
+      if (widget.isSelectedOrActive(cell) && cell.model.type === 'code') {
+        (cell as CodeCell).outputsScrolled = false;
+      }
+    });
+    Private.handleState(widget, state);
+  }
+
+
   /**
    * Set the markdown header level.
    *