diff --git a/IPython/html/notebookapp.py b/IPython/html/notebookapp.py
index 3cd81f563..81fda2dc2 100644
--- a/IPython/html/notebookapp.py
+++ b/IPython/html/notebookapp.py
@@ -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"""
diff --git a/IPython/html/static/base/js/keyboard.js b/IPython/html/static/base/js/keyboard.js
index 4fa77d5c9..56391e6d6 100644
--- a/IPython/html/static/base/js/keyboard.js
+++ b/IPython/html/static/base/js/keyboard.js
@@ -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));
diff --git a/IPython/html/static/notebook/js/notebook.js b/IPython/html/static/notebook/js/notebook.js
index 05cb955c1..e9f7a78de 100644
--- a/IPython/html/static/notebook/js/notebook.js
+++ b/IPython/html/static/notebook/js/notebook.js
@@ -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($("").attr("href", "http://ipython.org/security.html")
+ ).append($("").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');
};
diff --git a/IPython/html/static/notebook/js/notificationarea.js b/IPython/html/static/notebook/js/notificationarea.js
index 87f508ef1..a2ecad1dc 100644
--- a/IPython/html/static/notebook/js/notificationarea.js
+++ b/IPython/html/static/notebook/js/notificationarea.js
@@ -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
diff --git a/IPython/html/static/notebook/js/outputarea.js b/IPython/html/static/notebook/js/outputarea.js
index 47f239f3c..79b902e14 100644
--- a/IPython/html/static/notebook/js/outputarea.js
+++ b/IPython/html/static/notebook/js/outputarea.js
@@ -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) {
diff --git a/IPython/html/static/notebook/js/tooltip.js b/IPython/html/static/notebook/js/tooltip.js
index 915cca4a0..af5cbe683 100644
--- a/IPython/html/static/notebook/js/tooltip.js
+++ b/IPython/html/static/notebook/js/tooltip.js
@@ -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();
};
diff --git a/IPython/html/static/notebook/less/cell.less b/IPython/html/static/notebook/less/cell.less
index 5514360d0..3cecbdf1b 100644
--- a/IPython/html/static/notebook/less/cell.less
+++ b/IPython/html/static/notebook/less/cell.less
@@ -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();
diff --git a/IPython/html/static/notebook/less/codecell.less b/IPython/html/static/notebook/less/codecell.less
index 777f18584..8fc60b537 100644
--- a/IPython/html/static/notebook/less/codecell.less
+++ b/IPython/html/static/notebook/less/codecell.less
@@ -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
diff --git a/IPython/html/static/notebook/less/notebook.less b/IPython/html/static/notebook/less/notebook.less
index 8c6589e47..f7345d0a2 100644
--- a/IPython/html/static/notebook/less/notebook.less
+++ b/IPython/html/static/notebook/less/notebook.less
@@ -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;
diff --git a/IPython/html/static/notebook/less/outputarea.less b/IPython/html/static/notebook/less/outputarea.less
index e129385a2..5dd1614fa 100644
--- a/IPython/html/static/notebook/less/outputarea.less
+++ b/IPython/html/static/notebook/less/outputarea.less
@@ -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;
diff --git a/IPython/html/static/notebook/less/textcell.less b/IPython/html/static/notebook/less/textcell.less
index 0ec35bffc..7abe33924 100644
--- a/IPython/html/static/notebook/less/textcell.less
+++ b/IPython/html/static/notebook/less/textcell.less
@@ -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;*/
diff --git a/IPython/html/static/style/ipython.min.css b/IPython/html/static/style/ipython.min.css
index b608f6945..06e5ec568 100644
--- a/IPython/html/static/style/ipython.min.css
+++ b/IPython/html/static/style/ipython.min.css
@@ -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}
diff --git a/IPython/html/static/style/style.min.css b/IPython/html/static/style/style.min.css
index 36563fd52..df65a85f0 100644
--- a/IPython/html/static/style/style.min.css
+++ b/IPython/html/static/style/style.min.css
@@ -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}
diff --git a/IPython/html/templates/notebook.html b/IPython/html/templates/notebook.html
index 097a416f1..eebd652fd 100644
--- a/IPython/html/templates/notebook.html
+++ b/IPython/html/templates/notebook.html
@@ -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)
)
)
diff --git a/IPython/html/tests/notebook/dualmode.js b/IPython/html/tests/notebook/dualmode.js
new file mode 100644
index 000000000..87b556765
--- /dev/null
+++ b/IPython/html/tests/notebook/dualmode.js
@@ -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);
+ });
+});
diff --git a/IPython/html/tests/notebook/dualmode_arrows.js b/IPython/html/tests/notebook/dualmode_arrows.js
new file mode 100644
index 000000000..034929b5f
--- /dev/null
+++ b/IPython/html/tests/notebook/dualmode_arrows.js
@@ -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);
+ });
+});
diff --git a/IPython/html/tests/notebook/dualmode_cellinsert.js b/IPython/html/tests/notebook/dualmode_cellinsert.js
new file mode 100644
index 000000000..59b89a329
--- /dev/null
+++ b/IPython/html/tests/notebook/dualmode_cellinsert.js
@@ -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);
+ });
+});
\ No newline at end of file
diff --git a/IPython/html/tests/notebook/dualmode_cellmode.js b/IPython/html/tests/notebook/dualmode_cellmode.js
new file mode 100644
index 000000000..d4bf5f018
--- /dev/null
+++ b/IPython/html/tests/notebook/dualmode_cellmode.js
@@ -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');
+ });
+});
\ No newline at end of file
diff --git a/IPython/html/tests/notebook/dualmode_clipboard.js b/IPython/html/tests/notebook/dualmode_clipboard.js
new file mode 100644
index 000000000..5068c49c6
--- /dev/null
+++ b/IPython/html/tests/notebook/dualmode_clipboard.js
@@ -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.');
+ });
+});
\ No newline at end of file
diff --git a/IPython/html/tests/notebook/dualmode_execute.js b/IPython/html/tests/notebook/dualmode_execute.js
new file mode 100644
index 000000000..f4cd9542f
--- /dev/null
+++ b/IPython/html/tests/notebook/dualmode_execute.js
@@ -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);
+ });
+});
\ No newline at end of file
diff --git a/IPython/html/tests/notebook/dualmode_markdown.js b/IPython/html/tests/notebook/dualmode_markdown.js
new file mode 100644
index 000000000..d97405723
--- /dev/null
+++ b/IPython/html/tests/notebook/dualmode_markdown.js
@@ -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');
+ });
+});
\ No newline at end of file
diff --git a/IPython/html/tests/notebook/dualmode_merge.js b/IPython/html/tests/notebook/dualmode_merge.js
new file mode 100644
index 000000000..573b4575d
--- /dev/null
+++ b/IPython/html/tests/notebook/dualmode_merge.js
@@ -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.');
+ });
+});
\ No newline at end of file
diff --git a/IPython/html/tests/notebook/empty_arrow_keys.js b/IPython/html/tests/notebook/empty_arrow_keys.js
index 6abed3a96..a949ce53a 100644
--- a/IPython/html/tests/notebook/empty_arrow_keys.js
+++ b/IPython/html/tests/notebook/empty_arrow_keys.js
@@ -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.');
});
diff --git a/IPython/html/tests/notebook/execute_code.js b/IPython/html/tests/notebook/execute_code.js
index 1af684f25..076d3b70a 100644
--- a/IPython/html/tests/notebook/execute_code.js
+++ b/IPython/html/tests/notebook/execute_code.js
@@ -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);
diff --git a/IPython/html/tests/notebook/interrupt.js b/IPython/html/tests/notebook/interrupt.js
index 2bb87f8a5..7c2912c4c 100644
--- a/IPython/html/tests/notebook/interrupt.js
+++ b/IPython/html/tests/notebook/interrupt.js
@@ -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);
diff --git a/IPython/html/tests/notebook/merge_cells.js b/IPython/html/tests/notebook/merge_cells.js
deleted file mode 100644
index 2d8561433..000000000
--- a/IPython/html/tests/notebook/merge_cells.js
+++ /dev/null
@@ -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().');
-});
diff --git a/IPython/html/tests/notebook/merge_cells_api.js b/IPython/html/tests/notebook/merge_cells_api.js
new file mode 100644
index 000000000..9dd2fbdcb
--- /dev/null
+++ b/IPython/html/tests/notebook/merge_cells_api.js
@@ -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().');
+});
diff --git a/IPython/html/tests/util.js b/IPython/html/tests/util.js
index 8c49dd240..a572190bc 100644
--- a/IPython/html/tests/util.js
+++ b/IPython/html/tests/util.js
@@ -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);
});
diff --git a/docs/source/notebook/index.rst b/docs/source/notebook/index.rst
new file mode 100644
index 000000000..2191fda40
--- /dev/null
+++ b/docs/source/notebook/index.rst
@@ -0,0 +1,13 @@
+====================
+The IPython notebook
+====================
+
+.. toctree::
+ :maxdepth: 2
+
+ notebook
+ cm_keyboard
+ nbconvert
+ public_server
+ security
+
diff --git a/docs/source/notebook/ipython_security.asc b/docs/source/notebook/ipython_security.asc
new file mode 100644
index 000000000..95436812a
--- /dev/null
+++ b/docs/source/notebook/ipython_security.asc
@@ -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-----
diff --git a/docs/source/interactive/nbconvert.rst b/docs/source/notebook/nbconvert.rst
similarity index 100%
rename from docs/source/interactive/nbconvert.rst
rename to docs/source/notebook/nbconvert.rst
diff --git a/docs/source/interactive/notebook.rst b/docs/source/notebook/notebook.rst
similarity index 100%
rename from docs/source/interactive/notebook.rst
rename to docs/source/notebook/notebook.rst
diff --git a/docs/source/interactive/public_server.rst b/docs/source/notebook/public_server.rst
similarity index 99%
rename from docs/source/interactive/public_server.rst
rename to docs/source/notebook/public_server.rst
index aae62c8e7..9454cd05c 100644
--- a/docs/source/interactive/public_server.rst
+++ b/docs/source/notebook/public_server.rst
@@ -19,8 +19,8 @@ a public interface `.
.. _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
diff --git a/docs/source/notebook/security.rst b/docs/source/notebook/security.rst
new file mode 100644
index 000000000..fa68579bc
--- /dev/null
+++ b/docs/source/notebook/security.rst
@@ -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 `.
+
+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 `__), 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.
diff --git a/setup.py b/setup.py
index 07ea5a5d4..7ef9289ff 100755
--- a/setup.py
+++ b/setup.py
@@ -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.
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
diff --git a/setupbase.py b/setupbase.py
index 1d474e46e..21a285781 100644
--- a/setupbase.py
+++ b/setupbase.py
@@ -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"),