diff --git a/.travis.yml b/.travis.yml index 99ae90699..7463d827e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,6 @@ # http://travis-ci.org/#!/ipython/ipython language: python +group: edge cache: directories: - ~/.cache/bower diff --git a/notebook/static/base/js/keyboard.js b/notebook/static/base/js/keyboard.js index ee2f46e2c..ee8f2538b 100644 --- a/notebook/static/base/js/keyboard.js +++ b/notebook/static/base/js/keyboard.js @@ -368,7 +368,7 @@ define([ **/ var action_name = this.actions.get_name(data); if (! action_name){ - throw new Error('does not know how to deal with', data); + throw new Error('does not know how to deal with : ' + data); } shortcut = normalize_shortcut(shortcut); this.set_shortcut(shortcut, action_name); diff --git a/notebook/static/notebook/js/actions.js b/notebook/static/notebook/js/actions.js index 2b05f7603..b39a68902 100644 --- a/notebook/static/notebook/js/actions.js +++ b/notebook/static/notebook/js/actions.js @@ -107,10 +107,10 @@ define(function(require){ } }, 'run-cell':{ - help : 'run marked cells', + help : 'run selected cells', help_index : 'bb', handler : function (env) { - env.notebook.execute_marked_cells(); + env.notebook.execute_selected_cells(); } }, 'run-cell-and-insert-below':{ @@ -163,7 +163,7 @@ define(function(require){ handler : function (env) { var index = env.notebook.get_selected_index(); if (index !== 0 && index !== null) { - env.notebook.select_prev(); + env.notebook.select_prev(true); env.notebook.focus_cell(); } } @@ -174,23 +174,23 @@ define(function(require){ handler : function (env) { var index = env.notebook.get_selected_index(); if (index !== (env.notebook.ncells()-1) && index !== null) { - env.notebook.select_next(); + env.notebook.select_next(true); env.notebook.focus_cell(); } } }, - 'extend-marked-cells-above' : { - help: 'extend marked cells above', + 'extend-selection-above' : { + help: 'extend selected cells above', help_index : 'dc', handler : function (env) { - env.notebook.extend_marked(-1); + env.notebook.extend_selection_by(-1) } }, - 'extend-marked-cells-below' : { - help: 'extend marked cells below', + 'extend-selection-below' : { + help: 'extend selected cells below', help_index : 'dd', handler : function (env) { - env.notebook.extend_marked(1); + env.notebook.extend_selection_by(1) } }, 'cut-cell' : { @@ -229,7 +229,7 @@ define(function(require){ help_index : 'ec', handler : function (env) { env.notebook.insert_cell_above(); - env.notebook.select_prev(); + env.notebook.select_prev(true); env.notebook.focus_cell(); } }, @@ -239,7 +239,7 @@ define(function(require){ help_index : 'ed', handler : function (env) { env.notebook.insert_cell_below(); - env.notebook.select_next(); + env.notebook.select_next(true); env.notebook.focus_cell(); } }, @@ -380,16 +380,10 @@ define(function(require){ } }, 'merge-cells' : { - help : 'merge marked cells', + help : 'merge selected cells', help_index: 'el', handler: function(env) { - env.notebook.merge_marked_cells(); - } - }, - 'close-pager' : { - help_index : 'gd', - handler : function (env) { - env.pager.collapse(); + env.notebook.merge_selected_cells(); } }, 'show-command-palette': { @@ -400,29 +394,6 @@ define(function(require){ env.notebook.show_command_palette(); } }, - 'toggle-cell-marked': { - help_index : 'cj', - help: 'toggle marks', - icon: 'fa-check', - handler : function(env){ - // Use bitwise logic to toggle the marked state. - env.notebook.get_selected_cell().marked ^= true; - } - }, - 'unmark-all-cells': { - help_index : 'ck', - help : 'unmark all cells', - handler : function(env) { - env.notebook.unmark_all_cells(); - } - }, - 'mark-all-cells': { - help_index : 'cl', - help : 'mark all cells', - handler : function(env) { - env.notebook.mark_all_cells(); - } - }, 'toggle-toolbar':{ help: 'hide/show the toolbar', handler : function(env){ @@ -438,14 +409,12 @@ define(function(require){ events.trigger('resize-header.Page'); } }, - 'close-pager-or-unmark-all-cells': { - help : 'close the pager or unmark all cells', + 'close-pager': { + help : 'close the pager', handler : function(env) { - // Collapse the page if it is open, otherwise unmark all. + // Collapse the page if it is open if (env.pager && env.pager.expanded) { env.pager.collapse(); - } else { - env.notebook.unmark_all_cells(); } } }, @@ -481,7 +450,7 @@ define(function(require){ event.preventDefault(); } env.notebook.command_mode(); - env.notebook.select_prev(); + env.notebook.select_prev(true); env.notebook.edit_mode(); cm = env.notebook.get_selected_cell().code_mirror; cm.setCursor(cm.lastLine(), 0); @@ -498,7 +467,7 @@ define(function(require){ event.preventDefault(); } env.notebook.command_mode(); - env.notebook.select_next(); + env.notebook.select_next(true); env.notebook.edit_mode(); var cm = env.notebook.get_selected_cell().code_mirror; cm.setCursor(0, 0); diff --git a/notebook/static/notebook/js/cell.js b/notebook/static/notebook/js/cell.js index 8594816e3..fc67293fd 100644 --- a/notebook/static/notebook/js/cell.js +++ b/notebook/static/notebook/js/cell.js @@ -1,4 +1,3 @@ -// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /** @@ -21,7 +20,7 @@ define([ "use strict"; var overlayHack = CodeMirror.scrollbarModel.native.prototype.overlayHack; - + CodeMirror.scrollbarModel.native.prototype.overlayHack = function () { overlayHack.apply(this, arguments); // Reverse `min-height: 18px` scrollbar hack on OS X @@ -55,6 +54,7 @@ define([ this.placeholder = config.placeholder || ''; this.selected = false; + this.anchor = false; this.rendered = false; this.mode = 'command'; @@ -154,30 +154,29 @@ define([ } }; + /** + * trigger on focus and on click to bubble up to the notebook and + * potentially extend the selection if shift-click, contract the selection + * if just codemirror focus (so edit mode). + * We **might** be able to move that to notebook `handle_edit_mode`. + */ + Cell.prototype._on_click = function(event){ + if (!this.selected) { + this.events.trigger('select.Cell', {'cell':this, 'extendSelection':event.shiftKey}); + } + } + /** * Subclasses can implement override bind_events. - * Be carefull to call the parent method when overwriting as it fires event. - * this will be triggerd after create_element in constructor. + * Be careful to call the parent method when overwriting as it fires event. + * this will be triggered after create_element in constructor. * @method bind_events */ Cell.prototype.bind_events = function () { var that = this; // We trigger events so that Cell doesn't have to depend on Notebook. that.element.click(function (event) { - if (!that.selected) { - that.events.trigger('select.Cell', {'cell':that}); - } - - // Cmdtrl-click should mark the cell. - var isMac = navigator.platform.slice(0, 3).toLowerCase() === 'mac'; - if ((!isMac && event.ctrlKey) || (isMac && event.metaKey)) { - that.marked = !that.marked; - } - }); - that.element.focusin(function (event) { - if (!that.selected) { - that.events.trigger('select.Cell', {'cell':that}); - } + that._on_click(event) }); if (this.code_mirror) { this.code_mirror.on("change", function(cm, change) { @@ -186,6 +185,9 @@ define([ } if (this.code_mirror) { this.code_mirror.on('focus', function(cm, change) { + if (!that.selected) { + that.events.trigger('select.Cell', {'cell':that}); + } that.events.trigger('edit_mode.Cell', {cell: that}); }); } @@ -239,7 +241,7 @@ define([ /** - * Triger typsetting of math by mathjax on current cell element + * Triger typesetting of math by mathjax on current cell element * @method typeset */ Cell.prototype.typeset = function () { @@ -251,7 +253,13 @@ define([ * @method select * @return is the action being taken */ - Cell.prototype.select = function () { + Cell.prototype.select = function (moveanchor) { + // if anchor is true, set the move the anchor + moveanchor = (moveanchor === undefined)? true:moveanchor; + if(moveanchor){ + this.anchor=true; + } + if (!this.selected) { this.element.addClass('selected'); this.element.removeClass('unselected'); @@ -265,10 +273,14 @@ define([ /** * handle cell level logic when the cell is unselected * @method unselect - * @param {bool} leave_selected - true to move cursor away and extend selection * @return is the action being taken */ - Cell.prototype.unselect = function (leave_selected) { + Cell.prototype.unselect = function (moveanchor) { + // if anchor is true, remove also the anchor + moveanchor = (moveanchor === undefined)? true:moveanchor; + if (moveanchor){ + this.anchor = false + } if (this.selected) { this.element.addClass('unselected'); this.element.removeClass('selected'); @@ -279,32 +291,9 @@ define([ } }; - /** - * Whether or not the cell is marked. - * @return {boolean} - */ - Object.defineProperty(Cell.prototype, 'marked', { - get: function() { - return this.element.hasClass('marked'); - }, - set: function(value) { - var isMarked = this.element.hasClass('marked'); - // Use a casting comparison. Allows for the caller to assign 0 or - // 1 instead of a boolean value, which in return means the caller - // can do cell.marked ^= true to toggle the mark. - if (isMarked != value) { - if (value) { - this.element.addClass('marked'); - } else { - this.element.removeClass('marked'); - } - this.events.trigger('marked_changed.Cell', {cell: this, value: value}); - } - } - }); /** - * should be overritten by subclass + * should be overwritten by subclass * @method execute */ Cell.prototype.execute = function () { @@ -426,6 +415,7 @@ define([ */ Cell.prototype.focus_cell = function () { this.element.focus(); + this._on_click({}); }; /** diff --git a/notebook/static/notebook/js/codecell.js b/notebook/static/notebook/js/codecell.js index c75ebd9e0..088249dc8 100644 --- a/notebook/static/notebook/js/codecell.js +++ b/notebook/static/notebook/js/codecell.js @@ -196,7 +196,7 @@ define([ /** @method bind_events */ CodeCell.prototype.bind_events = function () { - Cell.prototype.bind_events.apply(this); + Cell.prototype.bind_events.apply(this, arguments); var that = this; this.element.focusout( @@ -401,7 +401,7 @@ define([ // Basic cell manipulation. CodeCell.prototype.select = function () { - var cont = Cell.prototype.select.apply(this); + var cont = Cell.prototype.select.apply(this, arguments); if (cont) { this.code_mirror.refresh(); this.auto_highlight(); @@ -410,7 +410,7 @@ define([ }; CodeCell.prototype.render = function () { - var cont = Cell.prototype.render.apply(this); + var cont = Cell.prototype.render.apply(this, arguments); // Always execute, even if we are already in the rendered state return cont; }; @@ -548,7 +548,7 @@ define([ * @return is the action being taken */ CodeCell.prototype.unselect = function() { - var cont = Cell.prototype.unselect.call(this); + var cont = Cell.prototype.unselect.apply(this, arguments); if (cont) { // When a code cell is unselected, make sure that the corresponding // tooltip and completer to that cell is closed. diff --git a/notebook/static/notebook/js/keyboardmanager.js b/notebook/static/notebook/js/keyboardmanager.js index 9955923df..21138b4ee 100644 --- a/notebook/static/notebook/js/keyboardmanager.js +++ b/notebook/static/notebook/js/keyboardmanager.js @@ -96,14 +96,14 @@ define([ 'i,i' : 'jupyter-notebook:interrupt-kernel', '0,0' : 'jupyter-notebook:confirm-restart-kernel', 'd,d' : 'jupyter-notebook:delete-cell', - 'esc': 'jupyter-notebook:close-pager-or-unmark-all-cells', + 'esc': 'jupyter-notebook:close-pager', 'up' : 'jupyter-notebook:select-previous-cell', 'k' : 'jupyter-notebook:select-previous-cell', 'j' : 'jupyter-notebook:select-next-cell', - 'shift-k': 'jupyter-notebook:extend-marked-cells-above', - 'shift-j': 'jupyter-notebook:extend-marked-cells-below', - 'shift-up': 'jupyter-notebook:extend-marked-cells-above', - 'shift-down': 'jupyter-notebook:extend-marked-cells-below', + 'shift-k': 'jupyter-notebook:extend-selection-above', + 'shift-j': 'jupyter-notebook:extend-selection-below', + 'shift-up': 'jupyter-notebook:extend-selection-above', + 'shift-down': 'jupyter-notebook:extend-selection-below', 'x' : 'jupyter-notebook:cut-cell', 'c' : 'jupyter-notebook:copy-cell', 'v' : 'jupyter-notebook:paste-cell-below', diff --git a/notebook/static/notebook/js/menubar.js b/notebook/static/notebook/js/menubar.js index 61207ff31..6ae6fc785 100644 --- a/notebook/static/notebook/js/menubar.js +++ b/notebook/static/notebook/js/menubar.js @@ -63,7 +63,7 @@ define([ // The selected cell loses focus when the menu is entered, so we // re-select it upon selection. var i = that.notebook.get_selected_index(); - that.notebook.select(i); + that.notebook.select(i, false); } ); }; diff --git a/notebook/static/notebook/js/notebook.js b/notebook/static/notebook/js/notebook.js index 5de9f14ba..14c8b42aa 100644 --- a/notebook/static/notebook/js/notebook.js +++ b/notebook/static/notebook/js/notebook.js @@ -29,6 +29,12 @@ define(function (require) { var scrollmanager = require('notebook/js/scrollmanager'); var commandpalette = require('notebook/js/commandpalette'); + var _SOFT_SELECTION_CLASS = 'jupyter-soft-selected'; + + function soft_selected(cell){ + return cell.element.hasClass(_SOFT_SELECTION_CLASS); + } + /** * Contains and manages cells. * @class Notebook @@ -187,9 +193,6 @@ define(function (require) { Notebook.prototype.bind_events = function () { var that = this; - this.events.on('marked_changed.Cell', function() { - that.update_marked_status(); - }); this.events.on('set_next_input.Notebook', function (event, data) { if (data.replace) { @@ -221,7 +224,7 @@ define(function (require) { this.events.on('select.Cell', function (event, data) { var index = that.find_cell_index(data.cell); - that.select(index); + that.select(index, !data.extendSelection); }); this.events.on('edit_mode.Cell', function (event, data) { @@ -298,9 +301,6 @@ define(function (require) { expand_time(time); }); - this.scroll_manager.onScroll(function () { - that.update_marked_status(); - }, 100); // Firefox 22 broke $(window).on("beforeunload") // I'm not sure why or how. @@ -584,13 +584,30 @@ define(function (require) { return i; }; + + Notebook.prototype.get_selected_cells = function () { + return this.get_cells().filter(function(cell, index){ return cell.selected || soft_selected(cell) || cell.anchor}) + }; + + Notebook.prototype.get_selected_cells_indices = function () { + + var result = []; + this.get_cells().filter(function (cell, index) { + if (cell.selected || soft_selected(cell) || cell.anchor) { + result.push(index); + } + }); + return result; + }; + + /** * Get the currently selected cell. * * @return {Cell} The selected cell */ Notebook.prototype.get_selected_cell = function () { - var index = this.get_selected_index(); + var index = this.get_selected_cells_indices(); return this.get_cell(index); }; @@ -608,6 +625,15 @@ define(function (require) { } }; + Notebook.prototype.get_anchor_index = function () { + var result = null; + this.get_cell_elements().filter(function (index) { + if ($(this).data("cell").anchor === true) { + result = index; + } + }); + return result; + }; /** * Get the index of the currently selected cell. * @@ -622,144 +648,52 @@ define(function (require) { }); return result; }; - - /** - * Toggles the marks on the cells - * @param {Cell[]} [cells] - optionally specify what cells should be toggled - */ - Notebook.prototype.toggle_cells_marked = function(cells) { - cells = cells || this.get_cells(); - cells.forEach(function(cell) { cell.marked = !cell.marked; }); - }; - - /** - * Mark all of the cells - * @param {Cell[]} [cells] - optionally specify what cells should be marked - */ - Notebook.prototype.mark_all_cells = function(cells) { - cells = cells || this.get_cells(); - cells.forEach(function(cell) { cell.marked = true; }); - }; - - /** - * Unmark all of the cells - * @param {Cell[]} [cells] - optionally specify what cells should be unmarked - */ - Notebook.prototype.unmark_all_cells = function(cells) { - this.get_marked_cells(cells).forEach(function(cell) { cell.marked = false; }); - }; - - /** - * Set the cells that should be marked, exclusively - * @param {Cell[]} cells - */ - Notebook.prototype.set_marked_cells = function(cells) { - this.unmark_all_cells(); - this.mark_all_cells(cells); - }; - - /** - * Gets the cells that are marked - * @param {Cell[]} [cells] - optionally provide the cells to search through - * @return {Cell[]} marked cells - */ - Notebook.prototype.get_marked_cells = function(cells) { - cells = cells || this.get_cells(); - return cells.filter(function(cell) { return (cell.marked || cell.selected); }); - }; - - /** - * Sets the cells that are marked by indices - * @param {number[]} indices - * @param {Cell[]} [cells] - optionally provide the cells to search through - */ - Notebook.prototype.set_marked_indices = function(indices, cells) { - cells = cells || this.get_cells(); - this.unmark_all_cells(cells); - this.mark_all_cells(cells.filter(function(cell, index) { return indices.indexOf(index) !== -1; })); - }; - - /** - * Gets the indices of the cells that are marked - * @param {Cell[]} [cells] - optionally provide the cells to search through - * @return {number[]} marked cell indices - */ - Notebook.prototype.get_marked_indices = function(cells) { - cells = cells || this.get_cells(); - var markedCells = this.get_marked_cells(cells); - return markedCells.map(function(cell) { return cells.indexOf(cell); }); - }; - - /** - * Checks if the marked cells are contiguous - * @param {Cell[]} [cells] - optionally provide the cells to search through - * @return {boolean} - */ - Notebook.prototype.are_marked_cells_contiguous = function(cells) { - // Get a numerically sorted list of the marked indices. - var markedIndices = this.get_marked_indices(cells).sort( - function(a,b) { return a-b; }); - // Check for contiguousness - for (var i = 0; i < markedIndices.length - 1; i++) { - if (markedIndices[i+1] - markedIndices[i] !== 1) { - return false; - } - } - return true; - }; - - /** - * Checks if the marked cells specified by their indices are contiguous - * @param {number[]} indices - the cell indices to search through - * @param {Cell[]} [cells] - the cells to search through - * @return {boolean} - */ - Notebook.prototype.are_marked_indices_contiguous = function(indices, cells) { - cells = cells || this.get_cells(); - return this.are_marked_cells_contiguous(cells.filter(function(cell, index) { - return indices.indexOf(index) !== -1; - })); - }; - /** - * Extend the selected range - * - * @param {number} offset - */ - Notebook.prototype.extend_marked = function(offset) { - // Mark currently selected cell - this.get_selected_cell().marked = true; + // Cell selection. - // Select the cell in the offset direction. Bound index between 0 and - // the number of cells -1. - var selectedIndex = Math.min(Math.max(this.get_selected_index() + offset, 0), this.ncells()-1); - this.select(selectedIndex); - this.ensure_focused(); + Notebook.prototype.extend_selection_by = function(delta) { + var index = this.get_selected_index(); + // do not move anchor + return this.select(index+delta, false); }; - Notebook.prototype.update_marked_status = function() { - var marked_cells = this.get_marked_cells(); - var num_offscreen = 0; - var i; - for (i = 0; i < marked_cells.length; i++) { - if (!this.scroll_manager.is_cell_visible(marked_cells[i])) { - num_offscreen += 1; - } - } - this.events.trigger('marked_offscreen.Cell', num_offscreen); - }; + Notebook.prototype.update_soft_selection = function(){ + var i1 = this.get_selected_index(); + var i2 = this.get_anchor_index(); + var low = Math.min(i1, i2); + var high = Math.max(i1, i2); + if (low !== high){ + $('body').addClass('jupyter-multi-select'); + } else { + $('body').removeClass('jupyter-multi-select'); + } + this.get_cells().map(function(cell, index, all){ + if( low <= index && index <= high ){ + cell.element.addClass(_SOFT_SELECTION_CLASS); + } else { + cell.element.removeClass(_SOFT_SELECTION_CLASS); + } + }) + } - // Cell selection. + Notebook.prototype._contract_selection = function(){ + var i = this.get_selected_index(); + this.select(i, true); + } /** * Programmatically select a cell. * * @param {integer} index - A cell's index + * @param {bool} moveanchor – whether to move the selection + * anchor, default to true. * @return {Notebook} This notebook */ - Notebook.prototype.select = function (index) { + Notebook.prototype.select = function (index, moveanchor) { + moveanchor = (moveanchor===undefined)? true : moveanchor; + if (this.is_valid_cell_index(index)) { var sindex = this.get_selected_index(); if (sindex !== null && index !== sindex) { @@ -768,11 +702,13 @@ define(function (require) { if (this.mode !== 'command') { this.command_mode(); } - this.get_cell(sindex).unselect(); + this.get_cell(sindex).unselect(moveanchor); + } + if(moveanchor){ + this.get_cell(this.get_anchor_index()).unselect(true); } var cell = this.get_cell(index); - cell.select(); - this.update_marked_status(); + cell.select(moveanchor); if (cell.cell_type === 'heading') { this.events.trigger('selected_cell_type_changed.Notebook', {'cell_type':cell.cell_type,level:cell.level} @@ -783,17 +719,20 @@ define(function (require) { ); } } + this.update_soft_selection(); return this; }; /** * Programmatically select the next cell. * + * @param {bool} moveanchor – whether to move the selection + * anchor, default to true. * @return {Notebook} This notebook */ - Notebook.prototype.select_next = function () { + Notebook.prototype.select_next = function (moveanchor) { var index = this.get_selected_index(); - this.select(index+1); + this.select(index+1, moveanchor); return this; }; @@ -802,9 +741,9 @@ define(function (require) { * * @return {Notebook} This notebook */ - Notebook.prototype.select_prev = function () { + Notebook.prototype.select_prev = function (moveanchor) { var index = this.get_selected_index(); - this.select(index-1); + this.select(index-1, moveanchor); return this; }; @@ -858,6 +797,7 @@ define(function (require) { * @param {Cell} [cell] Cell to enter edit mode on. */ Notebook.prototype.handle_edit_mode = function (cell) { + this._contract_selection(); if (cell && this.mode !== 'edit') { cell.edit_mode(); this.mode = 'edit'; @@ -870,6 +810,7 @@ define(function (require) { * Make a cell enter edit mode. */ Notebook.prototype.edit_mode = function () { + this._contract_selection(); var cell = this.get_selected_cell(); if (cell && this.mode !== 'edit') { cell.unrender(); @@ -978,7 +919,7 @@ define(function (require) { */ Notebook.prototype.delete_cells = function(indices) { if (indices === undefined) { - indices = this.get_marked_indices(); + indices = this.get_selected_cells_indices(); } this.undelete_backup = []; @@ -1418,7 +1359,7 @@ define(function (require) { * Copy cells. */ Notebook.prototype.copy_cell = function () { - var cells = this.get_marked_cells(); + var cells = this.get_selected_cells(); if (cells.length === 0) { cells = [this.get_selected_cell()]; } @@ -1507,7 +1448,6 @@ define(function (require) { // Unrender the new cell so we can call set_text. new_cell.unrender(); new_cell.set_text(texta); - new_cell.marked = cell.marked; } }; @@ -1564,14 +1504,13 @@ define(function (require) { this.delete_cells(indices); this.select(this.find_cell_index(target)); - this.unmark_all_cells(); }; /** * Merge the selected range of cells */ - Notebook.prototype.merge_marked_cells = function() { - this.merge_cells(this.get_marked_indices()); + Notebook.prototype.merge_selected_cells = function() { + this.merge_cells(this.get_selected_cells_indices()); }; /** @@ -2009,27 +1948,27 @@ define(function (require) { /** * Execute or render cell outputs and go into command mode. */ - Notebook.prototype.execute_marked_cells = function () { - this.execute_cells(this.get_marked_indices()); + Notebook.prototype.execute_selected_cells = function () { + this.execute_cells(this.get_selected_cells_indices()); }; + /** - * Alias for execute_marked_cells, for backwards compatibility -- + * Alias for execute_selected_cells, for backwards compatibility -- * previously, doing "Run Cell" would only ever run a single cell (hence * `execute_cell`), but now it runs all marked cells, so that's the * preferable function to use. But it is good to keep this function to avoid * breaking existing extensions, etc. */ Notebook.prototype.execute_cell = function () { - this.execute_marked_cells(); + this.execute_selected_cells(); }; /** * Execute or render cell outputs and insert a new cell below. */ Notebook.prototype.execute_cell_and_insert_below = function () { - // execute the marked cells, and don't insert anything - var indices = this.get_marked_indices(); + var indices = this.get_selected_cells_indices(); if (indices.length > 1) { this.execute_cells(indices); return; @@ -2061,8 +2000,7 @@ define(function (require) { * Execute or render cell outputs and select the next cell. */ Notebook.prototype.execute_cell_and_select_below = function () { - // execute the marked cells, and don't select anything - var indices = this.get_marked_indices(); + var indices = this.get_selected_cells_indices(); if (indices.length > 1) { this.execute_cells(indices); return; diff --git a/notebook/static/notebook/js/textcell.js b/notebook/static/notebook/js/textcell.js index 0b0e4ea49..d95563080 100644 --- a/notebook/static/notebook/js/textcell.js +++ b/notebook/static/notebook/js/textcell.js @@ -118,7 +118,7 @@ define([ // Cell level actions TextCell.prototype.select = function () { - var cont = Cell.prototype.select.apply(this); + var cont = Cell.prototype.select.apply(this, arguments); if (cont) { if (this.mode === 'edit') { this.code_mirror.refresh(); diff --git a/notebook/static/notebook/less/cell.less b/notebook/static/notebook/less/cell.less index 85e0481ff..393626caa 100644 --- a/notebook/static/notebook/less/cell.less +++ b/notebook/static/notebook/less/cell.less @@ -1,59 +1,65 @@ @_cell_padding_minus_border: @cell_padding - @cell_border_width; -._marked_style(@n) { - border-left-width: @n; - padding-left: @cell_padding - @n; +._selected_style(@c1, @c2, @sep:0, @border_width:@cell_border_width) { + border-left-width: @border_width; + padding-left: @cell_padding - @border_width; + background: linear-gradient(to right, @c1 -40px,@c1 @sep,@c2 @sep,@c2 100%); } + div.cell { - border: @cell_border_width solid transparent; .vbox(); .corner-all(); .border-box-sizing(); + border-width: @cell_border_width; border-style: solid; + border-color: transparent; - &.marked { - ._marked_style(3px); - border-left-color: @marked_border_color_light; - - /* Don't border the cells when printing */ + width: 100%; + padding: @_cell_padding_minus_border; + /* This acts as a spacer between cells, that is outside the border */ + margin: 0px; + outline: none; + + ._selected_style(transparent, transparent, @cell_border_width); + + .jupyter-multi-select &.jupyter-soft-selected { + border-left-color: @selected_border_color_light; + border-left-color: @soft_select_color; + ._selected_style(@selected_border_color_light, @soft_select_color, 5px, 0px); + @media print { border-color: transparent; } } - &.selected { + &.selected { border-color: @border_color; - border-left-color: @marked_border_color; - ._marked_style(2px); - - &.marked { - ._marked_style(4px); - } - - /* Don't border the cells when printing */ + ._selected_style(@selected_border_color, transparent, 5px, 0px); + + @media print { border-color: transparent; } } + .jupyter-multi-select &.selected.jupyter-soft-selected { + ._selected_style(@selected_border_color, @soft_select_color, 7px, 0); + } + .edit_mode &.selected { border-color: @edit_mode_border_color; + ._selected_style(@edit_mode_border_color, transparent, 5px, 0px); - /* Don't border the cells when printing */ @media print { border-color: transparent; } } - width: 100%; - padding: @_cell_padding_minus_border; - /* This acts as a spacer between cells, that is outside the border */ - margin: 0px; - outline: none; } + .prompt { /* This needs to be wide enough for 3 digit prompt numbers: In[100]: */ min-width: 14ex; diff --git a/notebook/static/notebook/less/variables.less b/notebook/static/notebook/less/variables.less index 441395491..c1dad6927 100644 --- a/notebook/static/notebook/less/variables.less +++ b/notebook/static/notebook/less/variables.less @@ -11,12 +11,17 @@ @code_line_height: 1.21429em; // changed from 1.231 to get 17px even @code_padding: 0.4em; // 5.6 px @rendered_html_border_color: black; -@input_prompt_color: navy; -@output_prompt_color: darkred; +@input_prompt_color: #303F9F; +@output_prompt_color: #D84315; @output_pre_color: black; @notification_widget_bg: rgba(240, 240, 240, 0.5); -@marked_border_color: #009AF5; -@marked_border_color_light: #7AC7F5; -@edit_mode_border_color: green; + + +@selected_border_color: #42A5F5; +@selected_border_color_light: #90CAF9; +@soft_select_color: #E3F2FD; + + +@edit_mode_border_color: #66BB6A; @cell_padding: 6px; -@cell_border_width: 1px; \ No newline at end of file +@cell_border_width: 1px; diff --git a/notebook/tests/notebook/dualmode_merge.js b/notebook/tests/notebook/dualmode_merge.js index 2a6520595..b39f753fa 100644 --- a/notebook/tests/notebook/dualmode_merge.js +++ b/notebook/tests/notebook/dualmode_merge.js @@ -41,7 +41,7 @@ casper.notebook_test(function () { this.test.assertEquals(this.get_cell_text(1), 'cd', 'split; Verify that cell 1 has the second half.'); this.validate_notebook_state('split', 'edit', 1); this.select_cell(0); // Move up to cell 0 - this.evaluate(function() { IPython.notebook.extend_marked(1);}); + this.evaluate(function() { IPython.notebook.extend_selection_by(1);}); this.trigger_keydown('shift-m'); // Merge this.validate_notebook_state('merge', 'command', 0); this.test.assertEquals(this.get_cell_text(0), a, 'merge; Verify that cell 0 has the merged contents.'); diff --git a/notebook/tests/notebook/execute_marked_cells.js b/notebook/tests/notebook/execute_selected_cells.js similarity index 61% rename from notebook/tests/notebook/execute_marked_cells.js rename to notebook/tests/notebook/execute_selected_cells.js index 2ce0f08e0..67119f397 100644 --- a/notebook/tests/notebook/execute_marked_cells.js +++ b/notebook/tests/notebook/execute_selected_cells.js @@ -3,16 +3,18 @@ // casper.notebook_test(function () { var that = this; - var assert_outputs = function (expected) { + var assert_outputs = function (expected, msg_prefix) { var msg, i; + msg_prefix = "(assert_outputs) "+(msg_prefix || 'no prefix')+": "; for (i = 0; i < that.get_cells_length(); i++) { if (expected[i] === undefined) { - msg = 'cell ' + i + ' not executed'; + msg = msg_prefix + 'cell ' + i + ' not executed'; that.test.assertFalse(that.cell_has_outputs(i), msg); } else { - msg = 'cell ' + i + ' executed'; - that.test.assertEquals(that.get_output_cell(i).text, expected[i], msg); + msg = msg_prefix + 'cell ' + i + ' executed'; + var out = that.get_output_cell(i, undefined, msg_prefix).text + that.test.assertEquals(out, expected[i], msg + 'out is: '+out); } } }; @@ -23,69 +25,54 @@ casper.notebook_test(function () { this.append_cell('print("c")'); this.append_cell('print("d")'); this.test.assertEquals(this.get_cells_length(), 4, "correct number of cells"); - - this.evaluate(function () { - IPython.notebook.unmark_all_cells(); - IPython.notebook.set_marked_indices([1, 2]); - }); }); this.then(function () { - this.evaluate(function () { - IPython.notebook.clear_all_output(); - }); - this.select_cell(1); - this.validate_notebook_state('before execute', 'command', 1); - this.trigger_keydown('ctrl-enter'); - }); - - this.wait_for_output(1); - this.wait_for_output(2); - - this.then(function () { - assert_outputs([undefined, 'b\n', 'c\n', undefined]); - this.validate_notebook_state('run marked cells', 'command', 2); + this.select_cell(2, false); }); - // execute cells in place when there are marked cells this.then(function () { this.evaluate(function () { IPython.notebook.clear_all_output(); }); + }) + this.then(function(){ this.select_cell(1); - this.validate_notebook_state('before execute', 'command', 1); - this.trigger_keydown('shift-enter'); + this.validate_notebook_state('before execute 1', 'command', 1); + this.select_cell(1); + this.select_cell(2, false); + this.trigger_keydown('ctrl-enter'); }); this.wait_for_output(1); this.wait_for_output(2); this.then(function () { - assert_outputs([undefined, 'b\n', 'c\n', undefined]); - this.validate_notebook_state('run marked cells', 'command', 2); + assert_outputs([undefined, 'b\n', 'c\n', undefined], 'run selected 1'); + this.validate_notebook_state('run selected cells 1', 'command', 2); }); - // execute and insert below when there are marked cells + + // execute and insert below when there are selected cells this.then(function () { this.evaluate(function () { IPython.notebook.clear_all_output(); }); this.select_cell(1); - this.validate_notebook_state('before execute', 'command', 1); + this.validate_notebook_state('before execute 2', 'command', 1); this.evaluate(function () { $("#run_cell_insert_below").click(); }); }); this.wait_for_output(1); - this.wait_for_output(2); this.then(function () { - assert_outputs([undefined, 'b\n', 'c\n', undefined]); - this.validate_notebook_state('run marked cells', 'command', 2); + assert_outputs([undefined, 'b\n', undefined, undefined , undefined],'run selected cells 2'); + this.validate_notebook_state('run selected cells 2', 'edit', 2); }); // check that it doesn't affect run all above @@ -95,7 +82,7 @@ casper.notebook_test(function () { }); this.select_cell(1); - this.validate_notebook_state('before execute', 'command', 1); + this.validate_notebook_state('before execute 3', 'command', 1); this.evaluate(function () { $("#run_all_cells_above").click(); }); @@ -104,7 +91,7 @@ casper.notebook_test(function () { this.wait_for_output(0); this.then(function () { - assert_outputs(['a\n', undefined, undefined, undefined]); + assert_outputs(['a\n', undefined, undefined, undefined],'run cells above'); this.validate_notebook_state('run cells above', 'command', 0); }); @@ -115,7 +102,7 @@ casper.notebook_test(function () { }); this.select_cell(1); - this.validate_notebook_state('before execute', 'command', 1); + this.validate_notebook_state('before execute 4', 'command', 1); this.evaluate(function () { $("#run_all_cells_below").click(); }); @@ -126,8 +113,8 @@ casper.notebook_test(function () { this.wait_for_output(3); this.then(function () { - assert_outputs([undefined, 'b\n', 'c\n', 'd\n']); - this.validate_notebook_state('run cells below', 'command', 3); + assert_outputs([undefined, 'b\n', undefined, 'c\n', 'd\n'],'run cells below'); + this.validate_notebook_state('run cells below', 'command', 4); }); // check that it doesn't affect run all @@ -137,7 +124,7 @@ casper.notebook_test(function () { }); this.select_cell(1); - this.validate_notebook_state('before execute', 'command', 1); + this.validate_notebook_state('before execute 5', 'command', 1); this.evaluate(function () { $("#run_all_cells").click(); }); @@ -149,7 +136,7 @@ casper.notebook_test(function () { this.wait_for_output(3); this.then(function () { - assert_outputs(['a\n', 'b\n', 'c\n', 'd\n']); - this.validate_notebook_state('run all cells', 'command', 3); + assert_outputs(['a\n', 'b\n', undefined, 'c\n', 'd\n'],'run all cells'); + this.validate_notebook_state('run all cells', 'command', 4); }); }); diff --git a/notebook/tests/notebook/marks.js b/notebook/tests/notebook/marks.js deleted file mode 100644 index a59823491..000000000 --- a/notebook/tests/notebook/marks.js +++ /dev/null @@ -1,73 +0,0 @@ - -// Test -casper.notebook_test(function () { - var that = this; - - var a = 'print("a")'; - var index = this.append_cell(a); - - var b = 'print("b")'; - index = this.append_cell(b); - - var c = 'print("c")'; - index = this.append_cell(c); - - this.then(function () { - var selectedIndex = this.evaluate(function () { - Jupyter.notebook.select(0); - return Jupyter.notebook.get_selected_index(); - }); - - this.test.assertEquals(this.evaluate(function() { - return Jupyter.notebook.get_marked_cells().length; - }), 1, 'only one cell is marked programmatically'); - - this.test.assertEquals(this.evaluate(function() { - return Jupyter.notebook.get_marked_indices()[0]; - }), selectedIndex, 'marked cell is selected cell'); - - this.test.assertEquals(this.evaluate(function() { - return $('.cell.marked').length; - }), 0, 'no cells are marked visibily'); - - this.evaluate(function() { - Jupyter.notebook.mark_all_cells(); - }); - - var cellCount = this.evaluate(function() { - return Jupyter.notebook.ncells(); - }); - - this.test.assertEquals(this.evaluate(function() { - return Jupyter.notebook.get_marked_cells().length; - }), cellCount, 'mark_all'); - - this.test.assertEquals(this.evaluate(function() { - return $('.cell.marked').length; - }), cellCount, 'marked cells are marked visibily'); - - this.evaluate(function() { - Jupyter.notebook.unmark_all_cells(); - }); - - this.test.assertEquals(this.evaluate(function() { - return Jupyter.notebook.get_marked_cells().length; - }), 1, 'unmark_all'); - - this.test.assertEquals(this.evaluate(function() { - return Jupyter.notebook.get_marked_indices()[0]; - }), selectedIndex, 'marked cell is selected cell'); - - this.evaluate(function() { - Jupyter.notebook.set_marked_indices([1]); - }); - - this.test.assertEquals(this.evaluate(function() { - return Jupyter.notebook.get_marked_cells().length; - }), 2, 'two cells are marked'); - - this.test.assertEquals(this.evaluate(function() { - return Jupyter.notebook.get_marked_indices(); - }), [selectedIndex, 1], 'get/set_marked_indices'); - }); -}); diff --git a/notebook/tests/notebook/multiselect.js b/notebook/tests/notebook/multiselect.js new file mode 100644 index 000000000..0e0df023a --- /dev/null +++ b/notebook/tests/notebook/multiselect.js @@ -0,0 +1,49 @@ + +// Test +casper.notebook_test(function () { + var that = this; + + var a = 'print("a")'; + var index = this.append_cell(a); + + var b = 'print("b")'; + index = this.append_cell(b); + + var c = 'print("c")'; + index = this.append_cell(c); + + this.then(function () { + var selectedIndex = this.evaluate(function () { + Jupyter.notebook.select(0); + return Jupyter.notebook.get_selected_index(); + }); + + this.test.assertEquals(this.evaluate(function() { + return Jupyter.notebook.get_selected_cells().length; + }), 1, 'only one cell is selected programmatically'); + + this.test.assertEquals(this.evaluate(function() { + return $('.cell.jupyter-soft-selected').length; + }), 1, 'one cell is selected'); + + + + this.test.assertEquals(this.evaluate(function() { + Jupyter.notebook.extend_selection_by(1); + return Jupyter.notebook.get_selected_cells().length; + }), 2, 'extend selection by one'); + + + this.test.assertEquals(this.evaluate(function() { + Jupyter.notebook.extend_selection_by(-1); + return Jupyter.notebook.get_selected_cells().length; + }), 1, 'contract selection by one'); + + this.test.assertEquals(this.evaluate(function() { + Jupyter.notebook.select(1); + Jupyter.notebook.extend_selection_by(-1); + return Jupyter.notebook.get_selected_cells().length; + }), 2, 'extend selection by one up'); + + }); +}); diff --git a/notebook/tests/notebook/undelete.js b/notebook/tests/notebook/undelete.js index 6269b6f17..371c1838c 100644 --- a/notebook/tests/notebook/undelete.js +++ b/notebook/tests/notebook/undelete.js @@ -4,11 +4,11 @@ casper.notebook_test(function () { var that = this; - var assert_marked_cells = function (action, indices) { - var marked = that.evaluate(function () { - return IPython.notebook.get_marked_indices(); + var assert_selected_cells = function (action, indices) { + var selected = that.evaluate(function () { + return IPython.notebook.get_selected_cells_indices(); }); - that.test.assertEquals(marked, indices, action + "; verify marked cells"); + that.test.assertEquals( selected, indices, action + "; verify selected cells"); }; var assert_cells = function (action, cells, index) { @@ -22,7 +22,7 @@ casper.notebook_test(function () { } that.validate_notebook_state(action, 'command', index); - assert_marked_cells(action, [index]); + assert_selected_cells(action, [index]); }; var a = 'print("a")'; @@ -59,7 +59,7 @@ casper.notebook_test(function () { this.select_cell(1); this.trigger_keydown('esc'); this.trigger_keydown('shift-j'); - assert_marked_cells("select cells 1-2", [1, 2]); + assert_selected_cells("select cells 1-2", [1, 2]); this.trigger_keydown('shift-m'); this.trigger_keydown('esc'); assert_cells("merge cells 1-2", [a, bc, d], 1); @@ -75,7 +75,7 @@ casper.notebook_test(function () { this.select_cell(3); this.trigger_keydown('esc'); this.trigger_keydown('shift-k'); - assert_marked_cells("select cells 3-2", [2, 3]); + assert_selected_cells("select cells 3-2", [2, 3]); this.trigger_keydown('shift-m'); this.trigger_keydown('esc'); assert_cells("merge cells 3-2", [a, bc, cd], 2); diff --git a/notebook/tests/util.js b/notebook/tests/util.js index 8a15b1d56..8a82c9a37 100644 --- a/notebook/tests/util.js +++ b/notebook/tests/util.js @@ -169,11 +169,11 @@ casper.wait_for_output = function (cell_num, out_num) { }, function then() { }, function timeout() { - this.echo("wait_for_output timed out on cell "+cell_num+", waiting for "+out_num+"outputs ."); + this.echo("wait_for_output timed out on cell "+cell_num+", waiting for "+out_num+" outputs ."); var pn = this.evaluate(function get_prompt(c) { - return IPython.notebook.get_cell(c).input_prompt_number; + return (IPython.notebook.get_cell(c)|| {'input_prompt_number':'no cell'}).input_prompt_number; }); - this.echo("cell prompt was :'"+pn+"'.") + this.echo("cell prompt was :'"+pn+"'."); }); }); }; @@ -229,7 +229,8 @@ casper.cell_has_outputs = function (cell_num) { return result > 0; }; -casper.get_output_cell = function (cell_num, out_num) { +casper.get_output_cell = function (cell_num, out_num, message) { + messsge = message+': ' ||'no category :' // return an output of a given cell out_num = out_num || 0; var result = casper.evaluate(function (c, o) { @@ -244,7 +245,7 @@ casper.get_output_cell = function (cell_num, out_num) { }, {c : cell_num}); this.test.assertTrue(false, - "Cell " + cell_num + " has no output #" + out_num + " (" + num_outputs + " total)" + message+"Cell " + cell_num + " has no output #" + out_num + " (" + num_outputs + " total)" ); } else { return result; @@ -394,14 +395,19 @@ casper.cell_element_function = function(index, selector, function_name, function casper.validate_notebook_state = function(message, mode, cell_index) { // Validate the entire dual mode state of the notebook. Make sure no more than // one cell is selected, focused, in edit mode, etc... - // General tests. this.test.assertEquals(this.get_keyboard_mode(), this.get_notebook_mode(), message + '; keyboard and notebook modes match'); // Is the selected cell the only cell that is selected? if (cell_index!==undefined) { this.test.assert(this.is_only_cell_selected(cell_index), - message + '; cell ' + cell_index + ' is the only cell selected'); + message + '; expecting cell ' + cell_index + ' to be the only cell selected. Got selected cell(s):'+ + (function(){ + return casper.evaluate(function(){ + return IPython.notebook.get_selected_cells_indices(); + }) + })() + ); } // Mode specific tests. @@ -433,11 +439,11 @@ casper.validate_notebook_state = function(message, mode, cell_index) { } }; -casper.select_cell = function(index) { +casper.select_cell = function(index, moveanchor) { // Select a cell in the notebook. - this.evaluate(function (i) { - IPython.notebook.select(i); - }, {i: index}); + this.evaluate(function (i, moveanchor) { + IPython.notebook.select(i, moveanchor); + }, {i: index, moveanchor: moveanchor}); }; casper.click_cell_editor = function(index) {