Merge branch 'master' of github.com:ipython/ipython

Brian E. Granger 12 years ago
commit b60d1542b2

@ -73,6 +73,7 @@ from .services.sessions.sessionmanager import SessionManager
from .base.handlers import AuthenticatedFileHandler, FileFindHandler
from IPython.config import Config
from IPython.config.application import catch_config_error, boolean_flag
from IPython.core.application import BaseIPythonApplication
from IPython.core.profiledir import ProfileDir
@ -554,10 +555,12 @@ class NotebookApp(BaseIPythonApplication):
# Use config here, to ensure that it takes higher priority than
# anything that comes from the profile.
c = Config()
if os.path.isdir(f):
self.config.NotebookApp.notebook_dir = f
c.NotebookApp.notebook_dir = f
elif os.path.isfile(f):
self.config.NotebookApp.file_to_run = f
c.NotebookApp.file_to_run = f
self.update_config(c)
def init_kernel_argv(self):
"""construct the kernel arguments"""

@ -128,15 +128,6 @@ IPython.keyboard = (function (IPython) {
return shortcut;
};
var trigger_keydown = function (shortcut, element) {
// Trigger shortcut keydown on an element
element = element || document;
element = $(element);
var event = shortcut_to_event(shortcut, 'keydown');
element.trigger(event);
};
// Shortcut manager class
var ShortcutManager = function (delay) {
@ -252,7 +243,7 @@ IPython.keyboard = (function (IPython) {
ShortcutManager.prototype.handles = function (event) {
var shortcut = event_to_shortcut(event);
var data = this._shortcuts[shortcut];
return !( data === undefined )
return !( data === undefined || data.handler === undefined )
}
return {
@ -262,8 +253,7 @@ IPython.keyboard = (function (IPython) {
normalize_key : normalize_key,
normalize_shortcut : normalize_shortcut,
shortcut_to_event : shortcut_to_event,
event_to_shortcut : event_to_shortcut,
trigger_keydown : trigger_keydown
event_to_shortcut : event_to_shortcut
};
}(IPython));

@ -58,6 +58,9 @@ var IPython = (function (IPython) {
this.style();
this.create_elements();
this.bind_events();
this.save_notebook = function() { // don't allow save until notebook_loaded
this.save_notebook_error(null, null, "Load failed, save is disabled");
};
};
/**
@ -1723,7 +1726,8 @@ var IPython = (function (IPython) {
};
/**
* Save this notebook on the server.
* Save this notebook on the server. This becomes a notebook instance's
* .save_notebook method *after* the entire notebook has been loaded.
*
* @method save_notebook
*/
@ -1829,7 +1833,7 @@ var IPython = (function (IPython) {
" Selecting trust will immediately reload this notebook in a trusted state."
).append(
" For more information, see the "
).append($("<a>").attr("href", "http://ipython.org/security.html")
).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
.text("IPython security documentation")
).append(".")
);
@ -2100,7 +2104,9 @@ var IPython = (function (IPython) {
IPython.CellToolbar.global_show();
IPython.CellToolbar.activate_preset(this.metadata.celltoolbar);
}
// now that we're fully loaded, it is safe to restore save functionality
delete(this.save_notebook);
$([IPython.events]).trigger('notebook_loaded.Notebook');
};

@ -188,8 +188,8 @@ var IPython = (function (IPython) {
$([IPython.events]).on('notebook_saved.Notebook', function () {
nnw.set_message("Notebook saved",2000);
});
$([IPython.events]).on('notebook_save_failed.Notebook', function () {
nnw.set_message("Notebook save failed");
$([IPython.events]).on('notebook_save_failed.Notebook', function (evt, xhr, status, data) {
nnw.set_message(data || "Notebook save failed");
});
// Checkpoint events

@ -541,6 +541,10 @@ var IPython = (function (IPython) {
var container = element;
container.show = function(){console.log('Warning "container.show()" is deprecated.')};
// end backward compat
// Fix for ipython/issues/5293, make sure `element` is the area which
// output can be inserted into at the time of JS execution.
element = toinsert;
try {
eval(js);
} catch(err) {

@ -131,17 +131,13 @@ var IPython = (function (IPython) {
Tooltip.prototype.showInPager = function (cell) {
// reexecute last call in pager by appending ? to show back in pager
var that = this;
var empty = function () {};
cell.kernel.execute(
that.name + '?', {
'execute_reply': empty,
'output': empty,
'clear_output': empty,
'cell': cell
}, {
'silent': false,
'store_history': true
});
var callbacks = {'shell' : {
'payload' : {
'page' : $.proxy(cell._open_with_pager, cell)
}
}
};
cell.kernel.execute(that.name + '?', callbacks, {'silent': false, 'store_history': true});
this.remove_and_cancel_tooltip();
};

@ -33,6 +33,14 @@ div.prompt {
line-height: @code_line_height;
}
@media (max-width: 480px) {
// prompts are in the main column on small screens,
// so text should be left-aligned
div.prompt {
text-align: left;
}
}
div.inner_cell {
.vbox();
.box-flex1();

@ -10,13 +10,19 @@ div.input {
.hbox();
}
@media (max-width: 480px) {
// move prompts above code on small screens
div.input {
.vbox();
}
}
/* input_area and input_prompt must match in top border and margin for alignment */
div.input_prompt {
color: navy;
border-top: 1px solid transparent;
}
// The styles related to div.highlight are for nbconvert HTML output only. This works
// because the .highlight div isn't present in the live notebook. We could put this into
// nbconvert, but it easily falls out of sync, can't use our less variables and doesn't

@ -7,6 +7,14 @@ body.notebook_app {
overflow: hidden;
}
@media (max-width: 767px) {
// remove bootstrap-responsive's body padding on small screens
body.notebook_app {
padding-left: 0px;
padding-right: 0px;
}
}
span#notebook_name {
height: 1em;
line-height: 1em;

@ -72,6 +72,13 @@ div.output_area {
.vbox();
}
@media (max-width: 480px) {
// move prompts above output on small screens
div.output_area {
.vbox();
}
}
div.output_area pre {
margin: 0;
padding: 0;

@ -2,6 +2,12 @@ div.text_cell {
padding: 5px 5px 5px 0px;
.hbox();
}
@media (max-width: 480px) {
// remove prompt indentation on small screens
div.text_cell > div.prompt {
display: none;
}
}
div.text_cell_render {
/*font-family: "Helvetica Neue", Arial, Helvetica, Geneva, sans-serif;*/

@ -73,11 +73,11 @@ div.cell{border:1px solid transparent;display:-webkit-box;-webkit-box-orient:ver
div.cell.edit_mode{border-radius:4px;border:thin #008000 solid}
div.cell{width:100%;padding:5px 5px 5px 0;margin:0;outline:none}
div.prompt{min-width:11ex;padding:.4em;margin:0;font-family:monospace;text-align:right;line-height:1.21429em}
div.inner_cell{display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;display:flex;flex-direction:column;align-items:stretch;-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;flex:1}
@media (max-width:480px){div.prompt{text-align:left}}div.inner_cell{display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;display:flex;flex-direction:column;align-items:stretch;-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;flex:1}
div.input_area{border:1px solid #cfcfcf;border-radius:4px;background:#f7f7f7}
div.prompt:empty{padding-top:0;padding-bottom:0}
div.input{page-break-inside:avoid;display:-webkit-box;-webkit-box-orient:horizontal;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:horizontal;-moz-box-align:stretch;display:box;box-orient:horizontal;box-align:stretch;display:flex;flex-direction:row;align-items:stretch}
div.input_prompt{color:#000080;border-top:1px solid transparent}
@media (max-width:480px){div.input{display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;display:flex;flex-direction:column;align-items:stretch}}div.input_prompt{color:#000080;border-top:1px solid transparent}
div.input_area>div.highlight{margin:.4em;border:none;padding:0;background-color:transparent}
div.input_area>div.highlight>pre{margin:0;border:0;padding:0;background-color:transparent;font-size:14px;line-height:1.21429em}
.CodeMirror{line-height:1.21429em;height:auto;background:none;}
@ -117,7 +117,7 @@ div.output_area{padding:0;page-break-inside:avoid;display:-webkit-box;-webkit-bo
div.output_area .rendered_html table{margin-left:0;margin-right:0}
div.output_area .rendered_html img{margin-left:0;margin-right:0}
.output{display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;display:flex;flex-direction:column;align-items:stretch}
div.output_area pre{margin:0;padding:0;border:0;font-size:100%;vertical-align:baseline;color:#000;background-color:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;line-height:inherit}
@media (max-width:480px){div.output_area{display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;display:flex;flex-direction:column;align-items:stretch}}div.output_area pre{margin:0;padding:0;border:0;font-size:100%;vertical-align:baseline;color:#000;background-color:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;line-height:inherit}
div.output_subarea{padding:.4em .4em 0 .4em;-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;flex:1}
div.output_text{text-align:left;color:#000;line-height:1.21429em}
div.output_stderr{background:#fdd;}
@ -170,7 +170,7 @@ p.p-space{margin-bottom:10px}
.rendered_html img{display:block;margin-left:auto;margin-right:auto}
.rendered_html *+img{margin-top:1em}
div.text_cell{padding:5px 5px 5px 0;display:-webkit-box;-webkit-box-orient:horizontal;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:horizontal;-moz-box-align:stretch;display:box;box-orient:horizontal;box-align:stretch;display:flex;flex-direction:row;align-items:stretch}
div.text_cell_render{outline:none;resize:none;width:inherit;border-style:none;padding:.5em .5em .5em .4em;color:#000}
@media (max-width:480px){div.text_cell>div.prompt{display:none}}div.text_cell_render{outline:none;resize:none;width:inherit;border-style:none;padding:.5em .5em .5em .4em;color:#000}
a.anchor-link:link{text-decoration:none;padding:0 20px;visibility:hidden}
h1:hover .anchor-link,h2:hover .anchor-link,h3:hover .anchor-link,h4:hover .anchor-link,h5:hover .anchor-link,h6:hover .anchor-link{visibility:visible}
div.cell.text_cell.rendered{padding:0}

@ -1350,11 +1350,11 @@ div.cell{border:1px solid transparent;display:-webkit-box;-webkit-box-orient:ver
div.cell.edit_mode{border-radius:4px;border:thin #008000 solid}
div.cell{width:100%;padding:5px 5px 5px 0;margin:0;outline:none}
div.prompt{min-width:11ex;padding:.4em;margin:0;font-family:monospace;text-align:right;line-height:1.21429em}
div.inner_cell{display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;display:flex;flex-direction:column;align-items:stretch;-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;flex:1}
@media (max-width:480px){div.prompt{text-align:left}}div.inner_cell{display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;display:flex;flex-direction:column;align-items:stretch;-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;flex:1}
div.input_area{border:1px solid #cfcfcf;border-radius:4px;background:#f7f7f7}
div.prompt:empty{padding-top:0;padding-bottom:0}
div.input{page-break-inside:avoid;display:-webkit-box;-webkit-box-orient:horizontal;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:horizontal;-moz-box-align:stretch;display:box;box-orient:horizontal;box-align:stretch;display:flex;flex-direction:row;align-items:stretch}
div.input_prompt{color:#000080;border-top:1px solid transparent}
@media (max-width:480px){div.input{display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;display:flex;flex-direction:column;align-items:stretch}}div.input_prompt{color:#000080;border-top:1px solid transparent}
div.input_area>div.highlight{margin:.4em;border:none;padding:0;background-color:transparent}
div.input_area>div.highlight>pre{margin:0;border:0;padding:0;background-color:transparent;font-size:14px;line-height:1.21429em}
.CodeMirror{line-height:1.21429em;height:auto;background:none;}
@ -1394,7 +1394,7 @@ div.output_area{padding:0;page-break-inside:avoid;display:-webkit-box;-webkit-bo
div.output_area .rendered_html table{margin-left:0;margin-right:0}
div.output_area .rendered_html img{margin-left:0;margin-right:0}
.output{display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;display:flex;flex-direction:column;align-items:stretch}
div.output_area pre{margin:0;padding:0;border:0;font-size:100%;vertical-align:baseline;color:#000;background-color:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;line-height:inherit}
@media (max-width:480px){div.output_area{display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;display:flex;flex-direction:column;align-items:stretch}}div.output_area pre{margin:0;padding:0;border:0;font-size:100%;vertical-align:baseline;color:#000;background-color:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;line-height:inherit}
div.output_subarea{padding:.4em .4em 0 .4em;-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;flex:1}
div.output_text{text-align:left;color:#000;line-height:1.21429em}
div.output_stderr{background:#fdd;}
@ -1447,7 +1447,7 @@ p.p-space{margin-bottom:10px}
.rendered_html img{display:block;margin-left:auto;margin-right:auto}
.rendered_html *+img{margin-top:1em}
div.text_cell{padding:5px 5px 5px 0;display:-webkit-box;-webkit-box-orient:horizontal;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:horizontal;-moz-box-align:stretch;display:box;box-orient:horizontal;box-align:stretch;display:flex;flex-direction:row;align-items:stretch}
div.text_cell_render{outline:none;resize:none;width:inherit;border-style:none;padding:.5em .5em .5em .4em;color:#000}
@media (max-width:480px){div.text_cell>div.prompt{display:none}}div.text_cell_render{outline:none;resize:none;width:inherit;border-style:none;padding:.5em .5em .5em .4em;color:#000}
a.anchor-link:link{text-decoration:none;padding:0 20px;visibility:hidden}
h1:hover .anchor-link,h2:hover .anchor-link,h3:hover .anchor-link,h4:hover .anchor-link,h5:hover .anchor-link,h6:hover .anchor-link{visibility:visible}
div.cell.text_cell.rendered{padding:0}
@ -1476,7 +1476,7 @@ div.cell.text_cell.rendered{padding:0}
.docked-widget-modal{overflow:hidden;position:relative !important;top:0 !important;left:0 !important;margin-left:0 !important}
body{background-color:#fff}
body.notebook_app{overflow:hidden}
span#notebook_name{height:1em;line-height:1em;padding:3px;border:none;font-size:146.5%}
@media (max-width:767px){body.notebook_app{padding-left:0;padding-right:0}}span#notebook_name{height:1em;line-height:1em;padding:3px;border:none;font-size:146.5%}
div#notebook_panel{margin:0 0 0 0;padding:0;-webkit-box-shadow:0 -1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 -1px 10px rgba(0,0,0,0.1);box-shadow:0 -1px 10px rgba(0,0,0,0.1)}
div#notebook{font-size:14px;line-height:20px;overflow-y:scroll;overflow-x:auto;width:100%;padding:1em 0 1em 0;margin:0;border-top:1px solid #ababab;outline:none;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box}
div.ui-widget-content{border:1px solid #ababab;outline:none}

@ -227,14 +227,14 @@ class="notebook_app"
(
("http://ipython.org/documentation.html","IPython Help",True),
("http://nbviewer.ipython.org/github/ipython/ipython/tree/master/examples/notebooks/", "Notebook Examples", True),
("http://ipython.org/ipython-doc/stable/interactive/notebook.html","Notebook Help",True),
("http://ipython.org/ipython-doc/dev/interactive/cm_keyboard.html","Editor Shortcuts",True),
("http://ipython.org/ipython-doc/2/notebook/notebook.html","Notebook Help",True),
("http://ipython.org/ipython-doc/2/notebook/cm_keyboard.html","Editor Shortcuts",True),
),(
("http://docs.python.org","Python",True),
("http://docs.scipy.org/doc/numpy/reference/","NumPy",True),
("http://docs.scipy.org/doc/scipy/reference/","SciPy",True),
("http://matplotlib.org/contents.html","Matplotlib",True),
("http://docs.sympy.org/dev/index.html","SymPy",True),
("http://docs.sympy.org/latest/index.html","SymPy",True),
("http://pandas.pydata.org/pandas-docs/stable/","pandas", True)
)
)

@ -0,0 +1,78 @@
// Test the notebook dual mode feature.
// Test
casper.notebook_test(function () {
var a = 'print("a")';
var index = this.append_cell(a);
this.execute_cell_then(index);
var b = 'print("b")';
index = this.append_cell(b);
this.execute_cell_then(index);
var c = 'print("c")';
index = this.append_cell(c);
this.execute_cell_then(index);
this.then(function () {
this.validate_notebook_state('initial state', 'edit', 0);
this.trigger_keydown('esc');
this.validate_notebook_state('esc', 'command', 0);
this.trigger_keydown('down');
this.validate_notebook_state('down', 'command', 1);
this.trigger_keydown('enter');
this.validate_notebook_state('enter', 'edit', 1);
this.trigger_keydown('j');
this.validate_notebook_state('j in edit mode', 'edit', 1);
this.trigger_keydown('esc');
this.validate_notebook_state('esc', 'command', 1);
this.trigger_keydown('j');
this.validate_notebook_state('j in command mode', 'command', 2);
this.click_cell_editor(0);
this.validate_notebook_state('click cell 0', 'edit', 0);
this.click_cell_editor(3);
this.validate_notebook_state('click cell 3', 'edit', 3);
this.trigger_keydown('esc');
this.validate_notebook_state('esc', 'command', 3);
// Open keyboard help
this.evaluate(function(){
$('#keyboard_shortcuts a').click();
}, {});
this.trigger_keydown('k');
this.validate_notebook_state('k in command mode while keyboard help is up', 'command', 3);
// Close keyboard help
this.evaluate(function(){
$('div.modal button.close').click();
}, {});
this.trigger_keydown('k');
this.validate_notebook_state('k in command mode', 'command', 2);
this.click_cell_editor(0);
this.validate_notebook_state('click cell 0', 'edit', 0);
this.focus_notebook();
this.validate_notebook_state('focus #notebook', 'command', 0);
this.click_cell_editor(0);
this.validate_notebook_state('click cell 0', 'edit', 0);
this.focus_notebook();
this.validate_notebook_state('focus #notebook', 'command', 0);
this.click_cell_editor(3);
this.validate_notebook_state('click cell 3', 'edit', 3);
// Cell deletion
this.trigger_keydown('esc', 'd', 'd');
this.test.assertEquals(this.get_cells_length(), 3, 'dd actually deletes a cell');
this.validate_notebook_state('dd', 'command', 2);
// Make sure that if the time between d presses is too long, nothing gets removed.
this.trigger_keydown('d');
});
this.wait(1000);
this.then(function () {
this.trigger_keydown('d');
this.test.assertEquals(this.get_cells_length(), 3, "d, 1 second wait, d doesn't delete a cell");
this.validate_notebook_state('d, 1 second wait, d', 'command', 2);
});
});

@ -0,0 +1,51 @@
// Test
casper.notebook_test(function () {
var a = 'print("a")';
var index = this.append_cell(a);
this.execute_cell_then(index);
var b = 'print("b")';
index = this.append_cell(b);
this.execute_cell_then(index);
var c = 'print("c")';
index = this.append_cell(c);
this.execute_cell_then(index);
this.then(function () {
// Up and down in command mode
this.select_cell(3);
this.trigger_keydown('j');
this.validate_notebook_state('j at end of notebook', 'command', 3);
this.trigger_keydown('down');
this.validate_notebook_state('down at end of notebook', 'command', 3);
this.trigger_keydown('up');
this.validate_notebook_state('up', 'command', 2);
this.select_cell(0);
this.validate_notebook_state('select 0', 'command', 0);
this.trigger_keydown('k');
this.validate_notebook_state('k at top of notebook', 'command', 0);
this.trigger_keydown('up');
this.validate_notebook_state('up at top of notebook', 'command', 0);
this.trigger_keydown('down');
this.validate_notebook_state('down', 'command', 1);
// Up and down in edit mode
this.click_cell_editor(3);
this.validate_notebook_state('click cell 3', 'edit', 3);
this.trigger_keydown('down');
this.validate_notebook_state('down at end of notebook', 'edit', 3);
this.set_cell_editor_cursor(3, 0, 0);
this.trigger_keydown('up');
this.validate_notebook_state('up', 'edit', 2);
this.click_cell_editor(0);
this.validate_notebook_state('click 0', 'edit', 0);
this.trigger_keydown('up');
this.validate_notebook_state('up at top of notebook', 'edit', 0);
this.set_cell_editor_cursor(0, 0, 10);
this.trigger_keydown('down');
this.validate_notebook_state('down', 'edit', 1);
});
});

@ -0,0 +1,27 @@
// Test
casper.notebook_test(function () {
var a = 'print("a")';
var index = this.append_cell(a);
this.execute_cell_then(index);
var b = 'print("b")';
index = this.append_cell(b);
this.execute_cell_then(index);
var c = 'print("c")';
index = this.append_cell(c);
this.execute_cell_then(index);
this.then(function () {
// Cell insertion
this.select_cell(2);
this.trigger_keydown('a'); // Creates one cell
this.test.assertEquals(this.get_cell_text(2), '', 'a; New cell 2 text is empty');
this.validate_notebook_state('a', 'command', 2);
this.trigger_keydown('b'); // Creates one cell
this.test.assertEquals(this.get_cell_text(2), '', 'b; Cell 2 text is still empty');
this.test.assertEquals(this.get_cell_text(3), '', 'b; New cell 3 text is empty');
this.validate_notebook_state('b', 'command', 3);
});
});

@ -0,0 +1,28 @@
// Test keyboard shortcuts that change the cell's mode.
// Test
casper.notebook_test(function () {
this.then(function () {
// Cell mode change
this.select_cell(0);
this.trigger_keydown('esc','r');
this.test.assertEquals(this.get_cell(0).cell_type, 'raw', 'r; cell is raw');
this.trigger_keydown('1');
this.test.assertEquals(this.get_cell(0).cell_type, 'heading', '1; cell is heading');
this.test.assertEquals(this.get_cell(0).level, 1, '1; cell is level 1 heading');
this.trigger_keydown('2');
this.test.assertEquals(this.get_cell(0).level, 2, '2; cell is level 2 heading');
this.trigger_keydown('3');
this.test.assertEquals(this.get_cell(0).level, 3, '3; cell is level 3 heading');
this.trigger_keydown('4');
this.test.assertEquals(this.get_cell(0).level, 4, '4; cell is level 4 heading');
this.trigger_keydown('5');
this.test.assertEquals(this.get_cell(0).level, 5, '5; cell is level 5 heading');
this.trigger_keydown('6');
this.test.assertEquals(this.get_cell(0).level, 6, '6; cell is level 6 heading');
this.trigger_keydown('m');
this.test.assertEquals(this.get_cell(0).cell_type, 'markdown', 'm; cell is markdown');
this.trigger_keydown('y');
this.test.assertEquals(this.get_cell(0).cell_type, 'code', 'y; cell is code');
});
});

@ -0,0 +1,55 @@
// Test
casper.notebook_test(function () {
var a = 'print("a")';
var index = this.append_cell(a);
this.execute_cell_then(index);
var b = 'print("b")';
index = this.append_cell(b);
this.execute_cell_then(index);
var c = 'print("c")';
index = this.append_cell(c);
this.execute_cell_then(index);
this.then(function () {
// Copy/paste/cut
var num_cells = this.get_cells_length();
this.test.assertEquals(this.get_cell_text(1), a, 'Verify that cell 1 is a');
this.select_cell(1);
this.trigger_keydown('x'); // Cut
this.validate_notebook_state('x', 'command', 1);
this.test.assertEquals(this.get_cells_length(), num_cells-1, 'Verify that a cell was removed.');
this.test.assertEquals(this.get_cell_text(1), b, 'Verify that cell 2 is now where cell 1 was.');
this.select_cell(2);
this.trigger_keydown('v'); // Paste
this.validate_notebook_state('v', 'command', 3); // Selection should move to pasted cell, below current cell.
this.test.assertEquals(this.get_cell_text(3), a, 'Verify that cell 3 has the cut contents.');
this.test.assertEquals(this.get_cells_length(), num_cells, 'Verify a the cell was added.');
this.trigger_keydown('v'); // Paste
this.validate_notebook_state('v', 'command', 4); // Selection should move to pasted cell, below current cell.
this.test.assertEquals(this.get_cell_text(4), a, 'Verify that cell 4 has the cut contents.');
this.test.assertEquals(this.get_cells_length(), num_cells+1, 'Verify a the cell was added.');
this.select_cell(1);
this.trigger_keydown('c'); // Copy
this.validate_notebook_state('c', 'command', 1);
this.test.assertEquals(this.get_cell_text(1), b, 'Verify that cell 1 is b');
this.select_cell(2);
this.trigger_keydown('c'); // Copy
this.validate_notebook_state('c', 'command', 2);
this.test.assertEquals(this.get_cell_text(2), c, 'Verify that cell 2 is c');
this.select_cell(4);
this.trigger_keydown('v'); // Paste
this.validate_notebook_state('v', 'command', 5);
this.test.assertEquals(this.get_cell_text(2), c, 'Verify that cell 2 still has the copied contents.');
this.test.assertEquals(this.get_cell_text(5), c, 'Verify that cell 5 has the copied contents.');
this.test.assertEquals(this.get_cells_length(), num_cells+2, 'Verify a the cell was added.');
this.select_cell(0);
this.trigger_keydown('shift-v'); // Paste
this.validate_notebook_state('shift-v', 'command', 0);
this.test.assertEquals(this.get_cell_text(0), c, 'Verify that cell 0 has the copied contents.');
this.test.assertEquals(this.get_cells_length(), num_cells+3, 'Verify a the cell was added.');
});
});

@ -0,0 +1,72 @@
// Test keyboard invoked execution.
// Test
casper.notebook_test(function () {
var a = 'print("a")';
var index = this.append_cell(a);
this.execute_cell_then(index);
var b = 'print("b")';
index = this.append_cell(b);
this.execute_cell_then(index);
var c = 'print("c")';
index = this.append_cell(c);
this.execute_cell_then(index);
this.then(function () {
// shift-enter
// last cell in notebook
var base_index = 3;
this.select_cell(base_index);
this.trigger_keydown('shift-enter'); // Creates one cell
this.validate_notebook_state('shift-enter (no cell below)', 'edit', base_index + 1);
// not last cell in notebook & starts in edit mode
this.click_cell_editor(base_index);
this.validate_notebook_state('click cell ' + base_index, 'edit', base_index);
this.trigger_keydown('shift-enter');
this.validate_notebook_state('shift-enter (cell exists below)', 'command', base_index + 1);
// starts in command mode
this.trigger_keydown('k');
this.validate_notebook_state('k in comand mode', 'command', base_index);
this.trigger_keydown('shift-enter');
this.validate_notebook_state('shift-enter (start in command mode)', 'command', base_index + 1);
// ctrl-enter
// last cell in notebook
base_index++;
this.trigger_keydown('ctrl-enter');
this.validate_notebook_state('ctrl-enter (no cell below)', 'command', base_index);
// not last cell in notebook & starts in edit mode
this.click_cell_editor(base_index-1);
this.validate_notebook_state('click cell ' + (base_index-1), 'edit', base_index-1);
this.trigger_keydown('ctrl-enter');
this.validate_notebook_state('ctrl-enter (cell exists below)', 'command', base_index-1);
// starts in command mode
this.trigger_keydown('j');
this.validate_notebook_state('j in comand mode', 'command', base_index);
this.trigger_keydown('ctrl-enter');
this.validate_notebook_state('ctrl-enter (start in command mode)', 'command', base_index);
// alt-enter
// last cell in notebook
this.trigger_keydown('alt-enter'); // Creates one cell
this.validate_notebook_state('alt-enter (no cell below)', 'edit', base_index + 1);
// not last cell in notebook & starts in edit mode
this.click_cell_editor(base_index);
this.validate_notebook_state('click cell ' + base_index, 'edit', base_index);
this.trigger_keydown('alt-enter'); // Creates one cell
this.validate_notebook_state('alt-enter (cell exists below)', 'edit', base_index + 1);
// starts in command mode
this.trigger_keydown('esc', 'k');
this.validate_notebook_state('k in comand mode', 'command', base_index);
this.trigger_keydown('alt-enter'); // Creates one cell
this.validate_notebook_state('alt-enter (start in command mode)', 'edit', base_index + 1);
// Notebook will now have 8 cells, the index of the last cell will be 7.
this.test.assertEquals(this.get_cells_length(), 8, '*-enter commands added cells where needed.');
this.select_cell(7);
this.validate_notebook_state('click cell ' + 7 + ' and esc', 'command', 7);
});
});

@ -0,0 +1,39 @@
// Test
casper.notebook_test(function () {
var a = 'print("a")';
var index = this.append_cell(a);
this.execute_cell_then(index);
this.then(function () {
// Markdown rendering / unredering
this.select_cell(1);
this.validate_notebook_state('select 1', 'command', 1);
this.trigger_keydown('m');
this.test.assertEquals(this.get_cell(1).cell_type, 'markdown', 'm; cell is markdown');
this.test.assertEquals(this.get_cell(1).rendered, false, 'm; cell is rendered');
this.trigger_keydown('enter');
this.test.assertEquals(this.get_cell(1).rendered, false, 'enter; cell is unrendered');
this.validate_notebook_state('enter', 'edit', 1);
this.trigger_keydown('ctrl-enter');
this.test.assertEquals(this.get_cell(1).rendered, true, 'ctrl-enter; cell is rendered');
this.validate_notebook_state('enter', 'command', 1);
this.trigger_keydown('enter');
this.test.assertEquals(this.get_cell(1).rendered, false, 'enter; cell is unrendered');
this.select_cell(0);
this.test.assertEquals(this.get_cell(1).rendered, false, 'select 0; cell 1 is still unrendered');
this.validate_notebook_state('select 0', 'command', 0);
this.select_cell(1);
this.validate_notebook_state('select 1', 'command', 1);
this.trigger_keydown('ctrl-enter');
this.test.assertEquals(this.get_cell(1).rendered, true, 'ctrl-enter; cell is rendered');
this.select_cell(0);
this.validate_notebook_state('select 0', 'command', 0);
this.trigger_keydown('shift-enter');
this.validate_notebook_state('shift-enter', 'command', 1);
this.test.assertEquals(this.get_cell(1).rendered, true, 'shift-enter; cell is rendered');
this.trigger_keydown('shift-enter'); // Creates one cell
this.validate_notebook_state('shift-enter', 'edit', 2);
this.test.assertEquals(this.get_cell(1).rendered, true, 'shift-enter; cell is rendered');
});
});

@ -0,0 +1,21 @@
// Test
casper.notebook_test(function () {
this.then(function () {
// Split and merge cells
this.select_cell(0);
this.trigger_keydown('a', 'enter'); // Create cell above and enter edit mode.
this.validate_notebook_state('a, enter', 'edit', 0);
this.set_cell_text(0, 'abcd');
this.set_cell_editor_cursor(0, 0, 2);
this.test.assertEquals(this.get_cell_text(0), 'abcd', 'Verify that cell 0 has the new contents.');
this.trigger_keydown('ctrl-shift-subtract'); // Split
this.test.assertEquals(this.get_cell_text(0), 'ab', 'split; Verify that cell 0 has the first half.');
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.trigger_keydown('shift-m'); // Merge
this.validate_notebook_state('merge', 'command', 0);
this.test.assertEquals(this.get_cell_text(0), 'ab\ncd', 'merge; Verify that cell 0 has the merged contents.');
});
});

@ -10,12 +10,12 @@ casper.notebook_test(function () {
for (i = 0; i < ncells; i++) {
IPython.notebook.delete_cell();
}
// Simulate the "up arrow" and "down arrow" keys.
//
IPython.keyboard.trigger_keydown('up');
IPython.keyboard.trigger_keydown('down');
return true;
});
// Simulate the "up arrow" and "down arrow" keys.
this.trigger_keydown('up');
this.trigger_keydown('down');
this.test.assertTrue(result, 'Up/down arrow okay in empty notebook.');
});

@ -22,7 +22,11 @@ casper.notebook_test(function () {
var cell = IPython.notebook.get_cell(0);
cell.set_text('a=11; print(a)');
cell.clear_output();
IPython.keyboard.trigger_keydown('shift-enter');
});
this.then(function(){
this.trigger_keydown('shift-enter');
});
this.wait_for_output(0);
@ -41,7 +45,10 @@ casper.notebook_test(function () {
var cell = IPython.notebook.get_cell(0);
cell.set_text('a=12; print(a)');
cell.clear_output();
IPython.keyboard.trigger_keydown('ctrl-enter');
});
this.then(function(){
this.trigger_keydown('ctrl-enter');
});
this.wait_for_output(0);

@ -31,8 +31,8 @@ casper.notebook_test(function () {
});
// interrupt using Ctrl-M I keyboard shortcut
this.thenEvaluate( function() {
IPython.keyboard.trigger_keydown('i');
this.then(function(){
this.trigger_keydown('i');
});
this.wait_for_output(0);

@ -1,38 +0,0 @@
//
// Test merging two notebook cells.
//
casper.notebook_test(function() {
var output = this.evaluate(function () {
// Fill in test data.
IPython.notebook.command_mode();
var set_cell_text = function () {
var cell_one = IPython.notebook.get_selected_cell();
cell_one.set_text('a = 5');
IPython.keyboard.trigger_keydown('b');
var cell_two = IPython.notebook.get_selected_cell();
cell_two.set_text('print(a)');
};
// merge_cell_above()
set_cell_text();
IPython.notebook.merge_cell_above();
var merged_above = IPython.notebook.get_selected_cell();
// merge_cell_below()
set_cell_text();
IPython.notebook.select(0);
IPython.notebook.merge_cell_below();
var merged_below = IPython.notebook.get_selected_cell();
return {
above: merged_above.get_text(),
below: merged_below.get_text()
};
});
this.test.assertEquals(output.above, 'a = 5\nprint(a)',
'Successful merge_cell_above().');
this.test.assertEquals(output.below, 'a = 5\nprint(a)',
'Successful merge_cell_below().');
});

@ -0,0 +1,43 @@
//
// Test merging two notebook cells.
//
casper.notebook_test(function() {
var that = this;
var set_cells_text = function () {
that.evaluate(function() {
var cell_one = IPython.notebook.get_selected_cell();
cell_one.set_text('a = 5');
});
that.trigger_keydown('b');
that.evaluate(function() {
var cell_two = IPython.notebook.get_selected_cell();
cell_two.set_text('print(a)');
});
};
this.evaluate(function () {
IPython.notebook.command_mode();
});
// merge_cell_above()
set_cells_text();
var output_above = this.evaluate(function () {
IPython.notebook.merge_cell_above();
return IPython.notebook.get_selected_cell().get_text();
});
// merge_cell_below()
set_cells_text();
var output_below = this.evaluate(function() {
IPython.notebook.select(0);
IPython.notebook.merge_cell_below();
return IPython.notebook.get_selected_cell().get_text();
});
this.test.assertEquals(output_above, 'a = 5\nprint(a)',
'Successful merge_cell_above().');
this.test.assertEquals(output_below, 'a = 5\nprint(a)',
'Successful merge_cell_below().');
});

@ -2,15 +2,15 @@
// Utility functions for the HTML notebook's CasperJS tests.
//
// Get the URL of a notebook server on which to run tests.
casper.get_notebook_server = function () {
port = casper.cli.get("port")
// Get the URL of a notebook server on which to run tests.
port = casper.cli.get("port");
port = (typeof port === 'undefined') ? '8888' : port;
return 'http://127.0.0.1:' + port
return 'http://127.0.0.1:' + port;
};
// Create and open a new notebook.
casper.open_new_notebook = function () {
// Create and open a new notebook.
var baseUrl = this.get_notebook_server();
this.start(baseUrl);
this.thenClick('button#new_notebook');
@ -34,15 +34,15 @@ casper.open_new_notebook = function () {
});
};
// Return whether or not the kernel is running.
casper.kernel_running = function kernel_running() {
// Return whether or not the kernel is running.
return this.evaluate(function kernel_running() {
return IPython.notebook.kernel.running;
});
};
// Shut down the current notebook's kernel.
casper.shutdown_current_kernel = function () {
// Shut down the current notebook's kernel.
this.thenEvaluate(function() {
IPython.notebook.kernel.kill();
});
@ -50,8 +50,9 @@ casper.shutdown_current_kernel = function () {
this.wait(1000);
};
// Delete created notebook.
casper.delete_current_notebook = function () {
// Delete created notebook.
// For some unknown reason, this doesn't work?!?
this.thenEvaluate(function() {
IPython.notebook.delete();
@ -59,6 +60,7 @@ casper.delete_current_notebook = function () {
};
casper.wait_for_busy = function () {
// Waits for the notebook to enter a busy state.
this.waitFor(function () {
return this.evaluate(function () {
return IPython._status == 'busy';
@ -67,6 +69,7 @@ casper.wait_for_busy = function () {
};
casper.wait_for_idle = function () {
// Waits for the notebook to idle.
this.waitFor(function () {
return this.evaluate(function () {
return IPython._status == 'idle';
@ -74,8 +77,8 @@ casper.wait_for_idle = function () {
});
};
// wait for the nth output in a given cell
casper.wait_for_output = function (cell_num, out_num) {
// wait for the nth output in a given cell
this.wait_for_idle();
out_num = out_num || 0;
this.then(function() {
@ -94,29 +97,29 @@ casper.wait_for_output = function (cell_num, out_num) {
});
};
// wait for a widget msg que to reach 0
//
// Parameters
// ----------
// widget_info : object
// Object which contains info related to the widget. The model_id property
// is used to identify the widget.
casper.wait_for_widget = function (widget_info) {
// wait for a widget msg que to reach 0
//
// Parameters
// ----------
// widget_info : object
// Object which contains info related to the widget. The model_id property
// is used to identify the widget.
this.waitFor(function () {
var pending = this.evaluate(function (m) {
return IPython.notebook.kernel.widget_manager.get_model(m).pending_msgs;
}, {m: widget_info.model_id});
if (pending == 0) {
if (pending === 0) {
return true;
} else {
return false;
}
});
}
};
// return an output of a given cell
casper.get_output_cell = function (cell_num, out_num) {
// return an output of a given cell
out_num = out_num || 0;
var result = casper.evaluate(function (c, o) {
var cell = IPython.notebook.get_cell(c);
@ -137,25 +140,33 @@ casper.get_output_cell = function (cell_num, out_num) {
}
};
// return the number of cells in the notebook
casper.get_cells_length = function () {
// return the number of cells in the notebook
var result = casper.evaluate(function () {
return IPython.notebook.get_cells().length;
})
});
return result;
};
// Set the text content of a cell.
casper.set_cell_text = function(index, text){
// Set the text content of a cell.
this.evaluate(function (index, text) {
var cell = IPython.notebook.get_cell(index);
cell.set_text(text);
}, index, text);
};
// Inserts a cell at the bottom of the notebook
// Returns the new cell's index.
casper.get_cell_text = function(index){
// Get the text content of a cell.
return this.evaluate(function (index) {
var cell = IPython.notebook.get_cell(index);
return cell.get_text();
}, index);
};
casper.insert_cell_at_bottom = function(cell_type){
// Inserts a cell at the bottom of the notebook
// Returns the new cell's index.
cell_type = cell_type || 'code';
return this.evaluate(function (cell_type) {
@ -164,9 +175,9 @@ casper.insert_cell_at_bottom = function(cell_type){
}, cell_type);
};
// Insert a cell at the bottom of the notebook and set the cells text.
// Returns the new cell's index.
casper.append_cell = function(text, cell_type) {
// Insert a cell at the bottom of the notebook and set the cells text.
// Returns the new cell's index.
var index = this.insert_cell_at_bottom(cell_type);
if (text !== undefined) {
this.set_cell_text(index, text);
@ -174,9 +185,9 @@ casper.append_cell = function(text, cell_type) {
return index;
};
// Asynchronously executes a cell by index.
// Returns the cell's index.
casper.execute_cell = function(index){
// Asynchronously executes a cell by index.
// Returns the cell's index.
var that = this;
this.then(function(){
that.evaluate(function (index) {
@ -187,11 +198,11 @@ casper.execute_cell = function(index){
return index;
};
// Synchronously executes a cell by index.
// Optionally accepts a then_callback parameter. then_callback will get called
// when the cell has finished executing.
// Returns the cell's index.
casper.execute_cell_then = function(index, then_callback) {
// Synchronously executes a cell by index.
// Optionally accepts a then_callback parameter. then_callback will get called
// when the cell has finished executing.
// Returns the cell's index.
var return_val = this.execute_cell(index);
this.wait_for_idle();
@ -206,18 +217,18 @@ casper.execute_cell_then = function(index, then_callback) {
return return_val;
};
// Utility function that allows us to easily check if an element exists
// within a cell. Uses JQuery selector to look for the element.
casper.cell_element_exists = function(index, selector){
// Utility function that allows us to easily check if an element exists
// within a cell. Uses JQuery selector to look for the element.
return casper.evaluate(function (index, selector) {
var $cell = IPython.notebook.get_cell(index).element;
return $cell.find(selector).length > 0;
}, index, selector);
};
// Utility function that allows us to execute a jQuery function on an
// element within a cell.
casper.cell_element_function = function(index, selector, function_name, function_args){
// Utility function that allows us to execute a jQuery function on an
// element within a cell.
return casper.evaluate(function (index, selector, function_name, function_args) {
var $cell = IPython.notebook.get_cell(index).element;
var $el = $cell.find(selector);
@ -225,8 +236,183 @@ casper.cell_element_function = function(index, selector, function_name, function
}, index, selector, function_name, function_args);
};
// Wrap a notebook test to reduce boilerplate.
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');
}
// Mode specific tests.
if (mode==='command') {
// Are the notebook and keyboard manager in command mode?
this.test.assertEquals(this.get_keyboard_mode(), 'command',
message + '; in command mode');
// Make sure there isn't a single cell in edit mode.
this.test.assert(this.is_only_cell_edit(null),
message + '; all cells in command mode');
this.test.assert(this.is_cell_editor_focused(null),
message + '; no cell editors are focused while in command mode');
} else if (mode==='edit') {
// Are the notebook and keyboard manager in edit mode?
this.test.assertEquals(this.get_keyboard_mode(), 'edit',
message + '; in edit mode');
if (cell_index!==undefined) {
// Is the specified cell the only cell in edit mode?
this.test.assert(this.is_only_cell_edit(cell_index),
message + '; cell ' + cell_index + ' is the only cell in edit mode');
// Is the specified cell the only cell with a focused code mirror?
this.test.assert(this.is_cell_editor_focused(cell_index),
message + '; cell ' + cell_index + '\'s editor is appropriately focused');
}
} else {
this.test.assert(false, message + '; ' + mode + ' is an unknown mode');
}
};
casper.select_cell = function(index) {
// Select a cell in the notebook.
this.evaluate(function (i) {
IPython.notebook.select(i);
}, {i: index});
};
casper.click_cell_editor = function(index) {
// Emulate a click on a cell's editor.
// Code Mirror does not play nicely with emulated brower events.
// Instead of trying to emulate a click, here we run code similar to
// the code used in Code Mirror that handles the mousedown event on a
// region of codemirror that the user can focus.
this.evaluate(function (i) {
var cm = IPython.notebook.get_cell(i).code_mirror;
if (cm.options.readOnly != "nocursor" && (document.activeElement != cm.display.input))
cm.display.input.focus();
}, {i: index});
};
casper.set_cell_editor_cursor = function(index, line_index, char_index) {
// Set the Code Mirror instance cursor's location.
this.evaluate(function (i, l, c) {
IPython.notebook.get_cell(i).code_mirror.setCursor(l, c);
}, {i: index, l: line_index, c: char_index});
};
casper.focus_notebook = function() {
// Focus the notebook div.
this.evaluate(function (){
$('#notebook').focus();
}, {});
};
casper.trigger_keydown = function() {
// Emulate a keydown in the notebook.
for (var i = 0; i < arguments.length; i++) {
this.evaluate(function (k) {
var element = $(document);
var event = IPython.keyboard.shortcut_to_event(k, 'keydown');
element.trigger(event);
}, {k: arguments[i]});
}
};
casper.get_keyboard_mode = function() {
// Get the mode of the keyboard manager.
return this.evaluate(function() {
return IPython.keyboard_manager.mode;
}, {});
};
casper.get_notebook_mode = function() {
// Get the mode of the notebook.
return this.evaluate(function() {
return IPython.notebook.mode;
}, {});
};
casper.get_cell = function(index) {
// Get a single cell.
//
// Note: Handles to DOM elements stored in the cell will be useless once in
// CasperJS context.
return this.evaluate(function(i) {
var cell = IPython.notebook.get_cell(i);
if (cell) {
return cell;
}
return null;
}, {i : index});
};
casper.is_cell_editor_focused = function(index) {
// Make sure a cell's editor is the only editor focused on the page.
return this.evaluate(function(i) {
var focused_textarea = $('#notebook .CodeMirror-focused textarea');
if (focused_textarea.length > 1) { throw 'More than one Code Mirror editor is focused at once!'; }
if (i === null) {
return focused_textarea.length === 0;
} else {
var cell = IPython.notebook.get_cell(i);
if (cell) {
return cell.code_mirror.getInputField() == focused_textarea[0];
}
}
return false;
}, {i : index});
};
casper.is_only_cell_selected = function(index) {
// Check if a cell is the only cell selected.
// Pass null as the index to check if no cells are selected.
return this.is_only_cell_on(index, 'selected', 'unselected');
};
casper.is_only_cell_edit = function(index) {
// Check if a cell is the only cell in edit mode.
// Pass null as the index to check if all of the cells are in command mode.
return this.is_only_cell_on(index, 'edit_mode', 'command_mode');
};
casper.is_only_cell_on = function(i, on_class, off_class) {
// Check if a cell is the only cell with the `on_class` DOM class applied to it.
// All of the other cells are checked for the `off_class` DOM class.
// Pass null as the index to check if all of the cells have the `off_class`.
var cells_length = this.get_cells_length();
for (var j = 0; j < cells_length; j++) {
if (j === i) {
if (this.cell_has_class(j, off_class) || !this.cell_has_class(j, on_class)) {
return false;
}
} else {
if (!this.cell_has_class(j, off_class) || this.cell_has_class(j, on_class)) {
return false;
}
}
}
return true;
};
casper.cell_has_class = function(index, classes) {
// Check if a cell has a class.
return this.evaluate(function(i, c) {
var cell = IPython.notebook.get_cell(i);
if (cell) {
return cell.element.hasClass(c);
}
return false;
}, {i : index, c: classes});
};
casper.notebook_test = function(test) {
// Wrap a notebook test to reduce boilerplate.
this.open_new_notebook();
this.then(test);
@ -253,14 +439,14 @@ casper.notebook_test = function(test) {
casper.wait_for_dashboard = function () {
// Wait for the dashboard list to load.
casper.waitForSelector('.list_item');
}
};
casper.open_dashboard = function () {
// Start casper by opening the dashboard page.
var baseUrl = this.get_notebook_server();
this.start(baseUrl);
this.wait_for_dashboard();
}
};
casper.dashboard_test = function (test) {
// Open the dashboard page and run a test.
@ -276,16 +462,16 @@ casper.dashboard_test = function (test) {
this.run(function() {
this.test.done();
});
}
};
casper.options.waitTimeout=10000
casper.options.waitTimeout=10000;
casper.on('waitFor.timeout', function onWaitForTimeout(timeout) {
this.echo("Timeout for " + casper.get_notebook_server());
this.echo("Is the notebook server running?");
});
// Pass `console.log` calls from page JS to casper.
casper.printLog = function () {
casper.print_log = function () {
// Pass `console.log` calls from page JS to casper.
this.on('remote.message', function(msg) {
this.echo('Remote message caught: ' + msg);
});

@ -0,0 +1,13 @@
====================
The IPython notebook
====================
.. toctree::
:maxdepth: 2
notebook
cm_keyboard
nbconvert
public_server
security

@ -0,0 +1,52 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v2.0.22 (GNU/Linux)
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
5vmLc+Ibz+eT+rM=
=XVDe
-----END PGP PUBLIC KEY BLOCK-----

@ -19,8 +19,8 @@ a public interface <notebook_public_server>`.
.. _notebook_security:
Notebook security
-----------------
Securing a notebook server
--------------------------
You can protect your notebook server with a simple single password by
setting the :attr:`NotebookApp.password` configurable. You can prepare a

@ -0,0 +1,146 @@
Security in IPython notebooks
=============================
As IPython notebooks become more popular for sharing and collaboration,
the potential for malicious people to attempt to exploit the notebook
for their nefarious purposes increases. IPython 2.0 introduces a
security model to prevent execution of untrusted code without explicit
user input.
The problem
-----------
The whole point of IPython is arbitrary code execution. We have no
desire to limit what can be done with a notebook, which would negatively
impact its utility.
Unlike other programs, an IPython notebook document includes output.
Unlike other documents, that output exists in a context that can execute
code (via Javascript).
The security problem we need to solve is that no code should execute
just because a user has **opened** a notebook that **they did not
write**. Like any other program, once a user decides to execute code in
a notebook, it is considered trusted, and should be allowed to do
anything.
Our security model
------------------
- Untrusted HTML is always sanitized
- Untrusted Javascript is never executed
- HTML and Javascript in Markdown cells are never trusted
- **Outputs** generated by the user are trusted
- Any other HTML or Javascript (in Markdown cells, output generated by
others) is never trusted
- The central question of trust is "Did the current user do this?"
The details of trust
--------------------
IPython notebooks store a signature in metadata, which is used to answer
the question "Did the current user do this?"
This signature is a digest of the notebooks contents plus a secret key,
known only to the user. The secret key is a user-only readable file in
the IPython profile's security directory. By default, this is::
~/.ipython/profile_default/security/notebook_secret
When a notebook is opened by a user, the server computes a signature
with the user's key, and compares it with the signature stored in the
notebook's metadata. If the signature matches, HTML and Javascript
output in the notebook will be trusted at load, otherwise it will be
untrusted.
Any output generated during an interactive session is trusted.
Updating trust
**************
A notebook's trust is updated when the notebook is saved. If there are
any untrusted outputs still in the notebook, the notebook will not be
trusted, and no signature will be stored. If all untrusted outputs have
been removed (either via ``Clear Output`` or re-execution), then the
notebook will become trusted.
While trust is updated per output, this is only for the duration of a
single session. A notebook file on disk is either trusted or not in its
entirety.
Explicit trust
**************
Sometimes re-executing a notebook to generate trusted output is not an
option, either because dependencies are unavailable, or it would take a
long time. Users can explicitly trust a notebook in two ways:
- At the command-line, with::
ipython trust /path/to/notebook.ipynb
- After loading the untrusted notebook, with ``File / Trust Notebook``
These two methods simply load the notebook, compute a new signature with
the user's key, and then store the newly signed notebook.
Reporting security issues
-------------------------
If you find a security vulnerability in IPython, either a failure of the
code to properly implement the model described here, or a failure of the
model itself, please report it to security@ipython.org.
If you prefer to encrypt your security reports,
you can use :download:`this PGP public key <ipython_security.asc>`.
Affected use cases
------------------
Some use cases that work in IPython 1.0 will become less convenient in
2.0 as a result of the security changes. We do our best to minimize
these annoyance, but security is always at odds with convenience.
Javascript and CSS in Markdown cells
************************************
While never officially supported, it had become common practice to put
hidden Javascript or CSS styling in Markdown cells, so that they would
not be visible on the page. Since Markdown cells are now sanitized (by
`Google Caja <https://developers.google.com/caja>`__), all Javascript
(including click event handlers, etc.) and CSS will be stripped.
We plan to provide a mechanism for notebook themes, but in the meantime
styling the notebook can only be done via either ``custom.css`` or CSS
in HTML output. The latter only have an effect if the notebook is
trusted, because otherwise the output will be sanitized just like
Markdown.
Collaboration
*************
When collaborating on a notebook, people probably want to see the
outputs produced by their colleagues' most recent executions. Since each
collaborator's key will differ, this will result in each share starting
in an untrusted state. There are three basic approaches to this:
- re-run notebooks when you get them (not always viable)
- explicitly trust notebooks via ``ipython trust`` or the notebook menu
(annoying, but easy)
- share a notebook secret, and use an IPython profile dedicated to the
collaboration while working on the project.
Multiple profiles or machines
*****************************
Since the notebook secret is stored in a profile directory by default,
opening a notebook with a different profile or on a different machine
will result in a different key, and thus be untrusted. The only current
way to address this is by sharing the notebook secret. This can be
facilitated by setting the configurable:
.. sourcecode:: python
c.NotebookApp.secret_file = "/path/to/notebook_secret"
in each profile, and only sharing the secret once per machine.

@ -14,7 +14,7 @@ requires utilities which are not available under Windows."""
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file COPYING.txt, distributed with this software.
# The full license is in the file COPYING.rst, distributed with this software.
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------

@ -161,6 +161,7 @@ def find_package_data():
pjoin(components, "jquery", "jquery.min.js"),
pjoin(components, "jquery-ui", "ui", "minified", "jquery-ui.min.js"),
pjoin(components, "jquery-ui", "themes", "smoothness", "jquery-ui.min.css"),
pjoin(components, "jquery-ui", "themes", "smoothness", "images", "*"),
pjoin(components, "marked", "lib", "marked.js"),
pjoin(components, "requirejs", "require.js"),
pjoin(components, "underscore", "underscore-min.js"),

Loading…
Cancel
Save