##// END OF EJS Templates
Merge pull request #6945 from minrk/kernel-info-lang...
Thomas Kluyver -
r19129:1c5293bd merge
parent child Browse files
Show More
@@ -1,2510 +1,2508 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 'base/js/dialog',
8 'base/js/dialog',
9 'notebook/js/cell',
9 'notebook/js/cell',
10 'notebook/js/textcell',
10 'notebook/js/textcell',
11 'notebook/js/codecell',
11 'notebook/js/codecell',
12 'services/sessions/session',
12 'services/sessions/session',
13 'notebook/js/celltoolbar',
13 'notebook/js/celltoolbar',
14 'components/marked/lib/marked',
14 'components/marked/lib/marked',
15 'codemirror/lib/codemirror',
15 'codemirror/lib/codemirror',
16 'codemirror/addon/runmode/runmode',
16 'codemirror/addon/runmode/runmode',
17 'notebook/js/mathjaxutils',
17 'notebook/js/mathjaxutils',
18 'base/js/keyboard',
18 'base/js/keyboard',
19 'notebook/js/tooltip',
19 'notebook/js/tooltip',
20 'notebook/js/celltoolbarpresets/default',
20 'notebook/js/celltoolbarpresets/default',
21 'notebook/js/celltoolbarpresets/rawcell',
21 'notebook/js/celltoolbarpresets/rawcell',
22 'notebook/js/celltoolbarpresets/slideshow',
22 'notebook/js/celltoolbarpresets/slideshow',
23 'notebook/js/scrollmanager'
23 'notebook/js/scrollmanager'
24 ], function (
24 ], function (
25 IPython,
25 IPython,
26 $,
26 $,
27 utils,
27 utils,
28 dialog,
28 dialog,
29 cellmod,
29 cellmod,
30 textcell,
30 textcell,
31 codecell,
31 codecell,
32 session,
32 session,
33 celltoolbar,
33 celltoolbar,
34 marked,
34 marked,
35 CodeMirror,
35 CodeMirror,
36 runMode,
36 runMode,
37 mathjaxutils,
37 mathjaxutils,
38 keyboard,
38 keyboard,
39 tooltip,
39 tooltip,
40 default_celltoolbar,
40 default_celltoolbar,
41 rawcell_celltoolbar,
41 rawcell_celltoolbar,
42 slideshow_celltoolbar,
42 slideshow_celltoolbar,
43 scrollmanager
43 scrollmanager
44 ) {
44 ) {
45 "use strict";
45 "use strict";
46
46
47 var Notebook = function (selector, options) {
47 var Notebook = function (selector, options) {
48 // Constructor
48 // Constructor
49 //
49 //
50 // A notebook contains and manages cells.
50 // A notebook contains and manages cells.
51 //
51 //
52 // Parameters:
52 // Parameters:
53 // selector: string
53 // selector: string
54 // options: dictionary
54 // options: dictionary
55 // Dictionary of keyword arguments.
55 // Dictionary of keyword arguments.
56 // events: $(Events) instance
56 // events: $(Events) instance
57 // keyboard_manager: KeyboardManager instance
57 // keyboard_manager: KeyboardManager instance
58 // contents: Contents instance
58 // contents: Contents instance
59 // save_widget: SaveWidget instance
59 // save_widget: SaveWidget instance
60 // config: dictionary
60 // config: dictionary
61 // base_url : string
61 // base_url : string
62 // notebook_path : string
62 // notebook_path : string
63 // notebook_name : string
63 // notebook_name : string
64 this.config = utils.mergeopt(Notebook, options.config);
64 this.config = utils.mergeopt(Notebook, options.config);
65 this.base_url = options.base_url;
65 this.base_url = options.base_url;
66 this.notebook_path = options.notebook_path;
66 this.notebook_path = options.notebook_path;
67 this.notebook_name = options.notebook_name;
67 this.notebook_name = options.notebook_name;
68 this.events = options.events;
68 this.events = options.events;
69 this.keyboard_manager = options.keyboard_manager;
69 this.keyboard_manager = options.keyboard_manager;
70 this.contents = options.contents;
70 this.contents = options.contents;
71 this.save_widget = options.save_widget;
71 this.save_widget = options.save_widget;
72 this.tooltip = new tooltip.Tooltip(this.events);
72 this.tooltip = new tooltip.Tooltip(this.events);
73 this.ws_url = options.ws_url;
73 this.ws_url = options.ws_url;
74 this._session_starting = false;
74 this._session_starting = false;
75 this.default_cell_type = this.config.default_cell_type || 'code';
75 this.default_cell_type = this.config.default_cell_type || 'code';
76
76
77 // Create default scroll manager.
77 // Create default scroll manager.
78 this.scroll_manager = new scrollmanager.ScrollManager(this);
78 this.scroll_manager = new scrollmanager.ScrollManager(this);
79
79
80 // TODO: This code smells (and the other `= this` line a couple lines down)
80 // TODO: This code smells (and the other `= this` line a couple lines down)
81 // We need a better way to deal with circular instance references.
81 // We need a better way to deal with circular instance references.
82 this.keyboard_manager.notebook = this;
82 this.keyboard_manager.notebook = this;
83 this.save_widget.notebook = this;
83 this.save_widget.notebook = this;
84
84
85 mathjaxutils.init();
85 mathjaxutils.init();
86
86
87 if (marked) {
87 if (marked) {
88 marked.setOptions({
88 marked.setOptions({
89 gfm : true,
89 gfm : true,
90 tables: true,
90 tables: true,
91 // FIXME: probably want central config for CodeMirror theme when we have js config
91 // FIXME: probably want central config for CodeMirror theme when we have js config
92 langPrefix: "cm-s-ipython language-",
92 langPrefix: "cm-s-ipython language-",
93 highlight: function(code, lang, callback) {
93 highlight: function(code, lang, callback) {
94 if (!lang) {
94 if (!lang) {
95 // no language, no highlight
95 // no language, no highlight
96 if (callback) {
96 if (callback) {
97 callback(null, code);
97 callback(null, code);
98 return;
98 return;
99 } else {
99 } else {
100 return code;
100 return code;
101 }
101 }
102 }
102 }
103 utils.requireCodeMirrorMode(lang, function () {
103 utils.requireCodeMirrorMode(lang, function () {
104 var el = document.createElement("div");
104 var el = document.createElement("div");
105 var mode = CodeMirror.getMode({}, lang);
105 var mode = CodeMirror.getMode({}, lang);
106 if (!mode) {
106 if (!mode) {
107 console.log("No CodeMirror mode: " + lang);
107 console.log("No CodeMirror mode: " + lang);
108 callback(null, code);
108 callback(null, code);
109 return;
109 return;
110 }
110 }
111 try {
111 try {
112 CodeMirror.runMode(code, mode, el);
112 CodeMirror.runMode(code, mode, el);
113 callback(null, el.innerHTML);
113 callback(null, el.innerHTML);
114 } catch (err) {
114 } catch (err) {
115 console.log("Failed to highlight " + lang + " code", err);
115 console.log("Failed to highlight " + lang + " code", err);
116 callback(err, code);
116 callback(err, code);
117 }
117 }
118 }, function (err) {
118 }, function (err) {
119 console.log("No CodeMirror mode: " + lang);
119 console.log("No CodeMirror mode: " + lang);
120 callback(err, code);
120 callback(err, code);
121 });
121 });
122 }
122 }
123 });
123 });
124 }
124 }
125
125
126 this.element = $(selector);
126 this.element = $(selector);
127 this.element.scroll();
127 this.element.scroll();
128 this.element.data("notebook", this);
128 this.element.data("notebook", this);
129 this.next_prompt_number = 1;
129 this.next_prompt_number = 1;
130 this.session = null;
130 this.session = null;
131 this.kernel = null;
131 this.kernel = null;
132 this.clipboard = null;
132 this.clipboard = null;
133 this.undelete_backup = null;
133 this.undelete_backup = null;
134 this.undelete_index = null;
134 this.undelete_index = null;
135 this.undelete_below = false;
135 this.undelete_below = false;
136 this.paste_enabled = false;
136 this.paste_enabled = false;
137 this.writable = false;
137 this.writable = false;
138 // It is important to start out in command mode to match the intial mode
138 // It is important to start out in command mode to match the intial mode
139 // of the KeyboardManager.
139 // of the KeyboardManager.
140 this.mode = 'command';
140 this.mode = 'command';
141 this.set_dirty(false);
141 this.set_dirty(false);
142 this.metadata = {};
142 this.metadata = {};
143 this._checkpoint_after_save = false;
143 this._checkpoint_after_save = false;
144 this.last_checkpoint = null;
144 this.last_checkpoint = null;
145 this.checkpoints = [];
145 this.checkpoints = [];
146 this.autosave_interval = 0;
146 this.autosave_interval = 0;
147 this.autosave_timer = null;
147 this.autosave_timer = null;
148 // autosave *at most* every two minutes
148 // autosave *at most* every two minutes
149 this.minimum_autosave_interval = 120000;
149 this.minimum_autosave_interval = 120000;
150 this.notebook_name_blacklist_re = /[\/\\:]/;
150 this.notebook_name_blacklist_re = /[\/\\:]/;
151 this.nbformat = 4; // Increment this when changing the nbformat
151 this.nbformat = 4; // Increment this when changing the nbformat
152 this.nbformat_minor = this.current_nbformat_minor = 0; // Increment this when changing the nbformat
152 this.nbformat_minor = this.current_nbformat_minor = 0; // Increment this when changing the nbformat
153 this.codemirror_mode = 'ipython';
153 this.codemirror_mode = 'ipython';
154 this.create_elements();
154 this.create_elements();
155 this.bind_events();
155 this.bind_events();
156 this.kernel_selector = null;
156 this.kernel_selector = null;
157 this.dirty = null;
157 this.dirty = null;
158 this.trusted = null;
158 this.trusted = null;
159 this._fully_loaded = false;
159 this._fully_loaded = false;
160
160
161 // Trigger cell toolbar registration.
161 // Trigger cell toolbar registration.
162 default_celltoolbar.register(this);
162 default_celltoolbar.register(this);
163 rawcell_celltoolbar.register(this);
163 rawcell_celltoolbar.register(this);
164 slideshow_celltoolbar.register(this);
164 slideshow_celltoolbar.register(this);
165
165
166 // prevent assign to miss-typed properties.
166 // prevent assign to miss-typed properties.
167 Object.seal(this);
167 Object.seal(this);
168 };
168 };
169
169
170 Notebook.options_default = {
170 Notebook.options_default = {
171 // can be any cell type, or the special values of
171 // can be any cell type, or the special values of
172 // 'above', 'below', or 'selected' to get the value from another cell.
172 // 'above', 'below', or 'selected' to get the value from another cell.
173 Notebook: {
173 Notebook: {
174 default_cell_type: 'code'
174 default_cell_type: 'code'
175 }
175 }
176 };
176 };
177
177
178
178
179 /**
179 /**
180 * Create an HTML and CSS representation of the notebook.
180 * Create an HTML and CSS representation of the notebook.
181 *
181 *
182 * @method create_elements
182 * @method create_elements
183 */
183 */
184 Notebook.prototype.create_elements = function () {
184 Notebook.prototype.create_elements = function () {
185 var that = this;
185 var that = this;
186 this.element.attr('tabindex','-1');
186 this.element.attr('tabindex','-1');
187 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
187 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
188 // We add this end_space div to the end of the notebook div to:
188 // We add this end_space div to the end of the notebook div to:
189 // i) provide a margin between the last cell and the end of the notebook
189 // i) provide a margin between the last cell and the end of the notebook
190 // ii) to prevent the div from scrolling up when the last cell is being
190 // ii) to prevent the div from scrolling up when the last cell is being
191 // edited, but is too low on the page, which browsers will do automatically.
191 // edited, but is too low on the page, which browsers will do automatically.
192 var end_space = $('<div/>').addClass('end_space');
192 var end_space = $('<div/>').addClass('end_space');
193 end_space.dblclick(function (e) {
193 end_space.dblclick(function (e) {
194 var ncells = that.ncells();
194 var ncells = that.ncells();
195 that.insert_cell_below('code',ncells-1);
195 that.insert_cell_below('code',ncells-1);
196 });
196 });
197 this.element.append(this.container);
197 this.element.append(this.container);
198 this.container.append(end_space);
198 this.container.append(end_space);
199 };
199 };
200
200
201 /**
201 /**
202 * Bind JavaScript events: key presses and custom IPython events.
202 * Bind JavaScript events: key presses and custom IPython events.
203 *
203 *
204 * @method bind_events
204 * @method bind_events
205 */
205 */
206 Notebook.prototype.bind_events = function () {
206 Notebook.prototype.bind_events = function () {
207 var that = this;
207 var that = this;
208
208
209 this.events.on('set_next_input.Notebook', function (event, data) {
209 this.events.on('set_next_input.Notebook', function (event, data) {
210 var index = that.find_cell_index(data.cell);
210 var index = that.find_cell_index(data.cell);
211 var new_cell = that.insert_cell_below('code',index);
211 var new_cell = that.insert_cell_below('code',index);
212 new_cell.set_text(data.text);
212 new_cell.set_text(data.text);
213 that.dirty = true;
213 that.dirty = true;
214 });
214 });
215
215
216 this.events.on('unrecognized_cell.Cell', function () {
216 this.events.on('unrecognized_cell.Cell', function () {
217 that.warn_nbformat_minor();
217 that.warn_nbformat_minor();
218 });
218 });
219
219
220 this.events.on('unrecognized_output.OutputArea', function () {
220 this.events.on('unrecognized_output.OutputArea', function () {
221 that.warn_nbformat_minor();
221 that.warn_nbformat_minor();
222 });
222 });
223
223
224 this.events.on('set_dirty.Notebook', function (event, data) {
224 this.events.on('set_dirty.Notebook', function (event, data) {
225 that.dirty = data.value;
225 that.dirty = data.value;
226 });
226 });
227
227
228 this.events.on('trust_changed.Notebook', function (event, trusted) {
228 this.events.on('trust_changed.Notebook', function (event, trusted) {
229 that.trusted = trusted;
229 that.trusted = trusted;
230 });
230 });
231
231
232 this.events.on('select.Cell', function (event, data) {
232 this.events.on('select.Cell', function (event, data) {
233 var index = that.find_cell_index(data.cell);
233 var index = that.find_cell_index(data.cell);
234 that.select(index);
234 that.select(index);
235 });
235 });
236
236
237 this.events.on('edit_mode.Cell', function (event, data) {
237 this.events.on('edit_mode.Cell', function (event, data) {
238 that.handle_edit_mode(data.cell);
238 that.handle_edit_mode(data.cell);
239 });
239 });
240
240
241 this.events.on('command_mode.Cell', function (event, data) {
241 this.events.on('command_mode.Cell', function (event, data) {
242 that.handle_command_mode(data.cell);
242 that.handle_command_mode(data.cell);
243 });
243 });
244
244
245 this.events.on('spec_changed.Kernel', function(event, data) {
245 this.events.on('spec_changed.Kernel', function(event, data) {
246 that.metadata.kernelspec =
246 that.metadata.kernelspec =
247 {name: data.name, display_name: data.display_name};
247 {name: data.name, display_name: data.display_name};
248 });
248 });
249
249
250 this.events.on('kernel_ready.Kernel', function(event, data) {
250 this.events.on('kernel_ready.Kernel', function(event, data) {
251 var kinfo = data.kernel.info_reply;
251 var kinfo = data.kernel.info_reply;
252 var langinfo = kinfo.language_info || {};
252 var langinfo = kinfo.language_info || {};
253 if (!langinfo.name) langinfo.name = kinfo.language;
254
255 that.metadata.language_info = langinfo;
253 that.metadata.language_info = langinfo;
256 // Mode 'null' should be plain, unhighlighted text.
254 // Mode 'null' should be plain, unhighlighted text.
257 var cm_mode = langinfo.codemirror_mode || langinfo.language || 'null';
255 var cm_mode = langinfo.codemirror_mode || langinfo.name || 'null';
258 that.set_codemirror_mode(cm_mode);
256 that.set_codemirror_mode(cm_mode);
259 });
257 });
260
258
261 var collapse_time = function (time) {
259 var collapse_time = function (time) {
262 var app_height = $('#ipython-main-app').height(); // content height
260 var app_height = $('#ipython-main-app').height(); // content height
263 var splitter_height = $('div#pager_splitter').outerHeight(true);
261 var splitter_height = $('div#pager_splitter').outerHeight(true);
264 var new_height = app_height - splitter_height;
262 var new_height = app_height - splitter_height;
265 that.element.animate({height : new_height + 'px'}, time);
263 that.element.animate({height : new_height + 'px'}, time);
266 };
264 };
267
265
268 this.element.bind('collapse_pager', function (event, extrap) {
266 this.element.bind('collapse_pager', function (event, extrap) {
269 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
267 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
270 collapse_time(time);
268 collapse_time(time);
271 });
269 });
272
270
273 var expand_time = function (time) {
271 var expand_time = function (time) {
274 var app_height = $('#ipython-main-app').height(); // content height
272 var app_height = $('#ipython-main-app').height(); // content height
275 var splitter_height = $('div#pager_splitter').outerHeight(true);
273 var splitter_height = $('div#pager_splitter').outerHeight(true);
276 var pager_height = $('div#pager').outerHeight(true);
274 var pager_height = $('div#pager').outerHeight(true);
277 var new_height = app_height - pager_height - splitter_height;
275 var new_height = app_height - pager_height - splitter_height;
278 that.element.animate({height : new_height + 'px'}, time);
276 that.element.animate({height : new_height + 'px'}, time);
279 };
277 };
280
278
281 this.element.bind('expand_pager', function (event, extrap) {
279 this.element.bind('expand_pager', function (event, extrap) {
282 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
280 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
283 expand_time(time);
281 expand_time(time);
284 });
282 });
285
283
286 // Firefox 22 broke $(window).on("beforeunload")
284 // Firefox 22 broke $(window).on("beforeunload")
287 // I'm not sure why or how.
285 // I'm not sure why or how.
288 window.onbeforeunload = function (e) {
286 window.onbeforeunload = function (e) {
289 // TODO: Make killing the kernel configurable.
287 // TODO: Make killing the kernel configurable.
290 var kill_kernel = false;
288 var kill_kernel = false;
291 if (kill_kernel) {
289 if (kill_kernel) {
292 that.session.delete();
290 that.session.delete();
293 }
291 }
294 // if we are autosaving, trigger an autosave on nav-away.
292 // if we are autosaving, trigger an autosave on nav-away.
295 // still warn, because if we don't the autosave may fail.
293 // still warn, because if we don't the autosave may fail.
296 if (that.dirty) {
294 if (that.dirty) {
297 if ( that.autosave_interval ) {
295 if ( that.autosave_interval ) {
298 // schedule autosave in a timeout
296 // schedule autosave in a timeout
299 // this gives you a chance to forcefully discard changes
297 // this gives you a chance to forcefully discard changes
300 // by reloading the page if you *really* want to.
298 // by reloading the page if you *really* want to.
301 // the timer doesn't start until you *dismiss* the dialog.
299 // the timer doesn't start until you *dismiss* the dialog.
302 setTimeout(function () {
300 setTimeout(function () {
303 if (that.dirty) {
301 if (that.dirty) {
304 that.save_notebook();
302 that.save_notebook();
305 }
303 }
306 }, 1000);
304 }, 1000);
307 return "Autosave in progress, latest changes may be lost.";
305 return "Autosave in progress, latest changes may be lost.";
308 } else {
306 } else {
309 return "Unsaved changes will be lost.";
307 return "Unsaved changes will be lost.";
310 }
308 }
311 }
309 }
312 // Null is the *only* return value that will make the browser not
310 // Null is the *only* return value that will make the browser not
313 // pop up the "don't leave" dialog.
311 // pop up the "don't leave" dialog.
314 return null;
312 return null;
315 };
313 };
316 };
314 };
317
315
318 Notebook.prototype.warn_nbformat_minor = function (event) {
316 Notebook.prototype.warn_nbformat_minor = function (event) {
319 // trigger a warning dialog about missing functionality from newer minor versions
317 // trigger a warning dialog about missing functionality from newer minor versions
320 var v = 'v' + this.nbformat + '.';
318 var v = 'v' + this.nbformat + '.';
321 var orig_vs = v + this.nbformat_minor;
319 var orig_vs = v + this.nbformat_minor;
322 var this_vs = v + this.current_nbformat_minor;
320 var this_vs = v + this.current_nbformat_minor;
323 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
321 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
324 this_vs + ". You can still work with this notebook, but cell and output types " +
322 this_vs + ". You can still work with this notebook, but cell and output types " +
325 "introduced in later notebook versions will not be available.";
323 "introduced in later notebook versions will not be available.";
326
324
327 dialog.modal({
325 dialog.modal({
328 notebook: this,
326 notebook: this,
329 keyboard_manager: this.keyboard_manager,
327 keyboard_manager: this.keyboard_manager,
330 title : "Newer Notebook",
328 title : "Newer Notebook",
331 body : msg,
329 body : msg,
332 buttons : {
330 buttons : {
333 OK : {
331 OK : {
334 "class" : "btn-danger"
332 "class" : "btn-danger"
335 }
333 }
336 }
334 }
337 });
335 });
338 }
336 }
339
337
340 /**
338 /**
341 * Set the dirty flag, and trigger the set_dirty.Notebook event
339 * Set the dirty flag, and trigger the set_dirty.Notebook event
342 *
340 *
343 * @method set_dirty
341 * @method set_dirty
344 */
342 */
345 Notebook.prototype.set_dirty = function (value) {
343 Notebook.prototype.set_dirty = function (value) {
346 if (value === undefined) {
344 if (value === undefined) {
347 value = true;
345 value = true;
348 }
346 }
349 if (this.dirty == value) {
347 if (this.dirty == value) {
350 return;
348 return;
351 }
349 }
352 this.events.trigger('set_dirty.Notebook', {value: value});
350 this.events.trigger('set_dirty.Notebook', {value: value});
353 };
351 };
354
352
355 /**
353 /**
356 * Scroll the top of the page to a given cell.
354 * Scroll the top of the page to a given cell.
357 *
355 *
358 * @method scroll_to_cell
356 * @method scroll_to_cell
359 * @param {Number} cell_number An index of the cell to view
357 * @param {Number} cell_number An index of the cell to view
360 * @param {Number} time Animation time in milliseconds
358 * @param {Number} time Animation time in milliseconds
361 * @return {Number} Pixel offset from the top of the container
359 * @return {Number} Pixel offset from the top of the container
362 */
360 */
363 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
361 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
364 var cells = this.get_cells();
362 var cells = this.get_cells();
365 time = time || 0;
363 time = time || 0;
366 cell_number = Math.min(cells.length-1,cell_number);
364 cell_number = Math.min(cells.length-1,cell_number);
367 cell_number = Math.max(0 ,cell_number);
365 cell_number = Math.max(0 ,cell_number);
368 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
366 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
369 this.element.animate({scrollTop:scroll_value}, time);
367 this.element.animate({scrollTop:scroll_value}, time);
370 return scroll_value;
368 return scroll_value;
371 };
369 };
372
370
373 /**
371 /**
374 * Scroll to the bottom of the page.
372 * Scroll to the bottom of the page.
375 *
373 *
376 * @method scroll_to_bottom
374 * @method scroll_to_bottom
377 */
375 */
378 Notebook.prototype.scroll_to_bottom = function () {
376 Notebook.prototype.scroll_to_bottom = function () {
379 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
377 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
380 };
378 };
381
379
382 /**
380 /**
383 * Scroll to the top of the page.
381 * Scroll to the top of the page.
384 *
382 *
385 * @method scroll_to_top
383 * @method scroll_to_top
386 */
384 */
387 Notebook.prototype.scroll_to_top = function () {
385 Notebook.prototype.scroll_to_top = function () {
388 this.element.animate({scrollTop:0}, 0);
386 this.element.animate({scrollTop:0}, 0);
389 };
387 };
390
388
391 // Edit Notebook metadata
389 // Edit Notebook metadata
392
390
393 Notebook.prototype.edit_metadata = function () {
391 Notebook.prototype.edit_metadata = function () {
394 var that = this;
392 var that = this;
395 dialog.edit_metadata({
393 dialog.edit_metadata({
396 md: this.metadata,
394 md: this.metadata,
397 callback: function (md) {
395 callback: function (md) {
398 that.metadata = md;
396 that.metadata = md;
399 },
397 },
400 name: 'Notebook',
398 name: 'Notebook',
401 notebook: this,
399 notebook: this,
402 keyboard_manager: this.keyboard_manager});
400 keyboard_manager: this.keyboard_manager});
403 };
401 };
404
402
405 // Cell indexing, retrieval, etc.
403 // Cell indexing, retrieval, etc.
406
404
407 /**
405 /**
408 * Get all cell elements in the notebook.
406 * Get all cell elements in the notebook.
409 *
407 *
410 * @method get_cell_elements
408 * @method get_cell_elements
411 * @return {jQuery} A selector of all cell elements
409 * @return {jQuery} A selector of all cell elements
412 */
410 */
413 Notebook.prototype.get_cell_elements = function () {
411 Notebook.prototype.get_cell_elements = function () {
414 return this.container.find(".cell").not('.cell .cell');
412 return this.container.find(".cell").not('.cell .cell');
415 };
413 };
416
414
417 /**
415 /**
418 * Get a particular cell element.
416 * Get a particular cell element.
419 *
417 *
420 * @method get_cell_element
418 * @method get_cell_element
421 * @param {Number} index An index of a cell to select
419 * @param {Number} index An index of a cell to select
422 * @return {jQuery} A selector of the given cell.
420 * @return {jQuery} A selector of the given cell.
423 */
421 */
424 Notebook.prototype.get_cell_element = function (index) {
422 Notebook.prototype.get_cell_element = function (index) {
425 var result = null;
423 var result = null;
426 var e = this.get_cell_elements().eq(index);
424 var e = this.get_cell_elements().eq(index);
427 if (e.length !== 0) {
425 if (e.length !== 0) {
428 result = e;
426 result = e;
429 }
427 }
430 return result;
428 return result;
431 };
429 };
432
430
433 /**
431 /**
434 * Try to get a particular cell by msg_id.
432 * Try to get a particular cell by msg_id.
435 *
433 *
436 * @method get_msg_cell
434 * @method get_msg_cell
437 * @param {String} msg_id A message UUID
435 * @param {String} msg_id A message UUID
438 * @return {Cell} Cell or null if no cell was found.
436 * @return {Cell} Cell or null if no cell was found.
439 */
437 */
440 Notebook.prototype.get_msg_cell = function (msg_id) {
438 Notebook.prototype.get_msg_cell = function (msg_id) {
441 return codecell.CodeCell.msg_cells[msg_id] || null;
439 return codecell.CodeCell.msg_cells[msg_id] || null;
442 };
440 };
443
441
444 /**
442 /**
445 * Count the cells in this notebook.
443 * Count the cells in this notebook.
446 *
444 *
447 * @method ncells
445 * @method ncells
448 * @return {Number} The number of cells in this notebook
446 * @return {Number} The number of cells in this notebook
449 */
447 */
450 Notebook.prototype.ncells = function () {
448 Notebook.prototype.ncells = function () {
451 return this.get_cell_elements().length;
449 return this.get_cell_elements().length;
452 };
450 };
453
451
454 /**
452 /**
455 * Get all Cell objects in this notebook.
453 * Get all Cell objects in this notebook.
456 *
454 *
457 * @method get_cells
455 * @method get_cells
458 * @return {Array} This notebook's Cell objects
456 * @return {Array} This notebook's Cell objects
459 */
457 */
460 // TODO: we are often calling cells as cells()[i], which we should optimize
458 // TODO: we are often calling cells as cells()[i], which we should optimize
461 // to cells(i) or a new method.
459 // to cells(i) or a new method.
462 Notebook.prototype.get_cells = function () {
460 Notebook.prototype.get_cells = function () {
463 return this.get_cell_elements().toArray().map(function (e) {
461 return this.get_cell_elements().toArray().map(function (e) {
464 return $(e).data("cell");
462 return $(e).data("cell");
465 });
463 });
466 };
464 };
467
465
468 /**
466 /**
469 * Get a Cell object from this notebook.
467 * Get a Cell object from this notebook.
470 *
468 *
471 * @method get_cell
469 * @method get_cell
472 * @param {Number} index An index of a cell to retrieve
470 * @param {Number} index An index of a cell to retrieve
473 * @return {Cell} Cell or null if no cell was found.
471 * @return {Cell} Cell or null if no cell was found.
474 */
472 */
475 Notebook.prototype.get_cell = function (index) {
473 Notebook.prototype.get_cell = function (index) {
476 var result = null;
474 var result = null;
477 var ce = this.get_cell_element(index);
475 var ce = this.get_cell_element(index);
478 if (ce !== null) {
476 if (ce !== null) {
479 result = ce.data('cell');
477 result = ce.data('cell');
480 }
478 }
481 return result;
479 return result;
482 };
480 };
483
481
484 /**
482 /**
485 * Get the cell below a given cell.
483 * Get the cell below a given cell.
486 *
484 *
487 * @method get_next_cell
485 * @method get_next_cell
488 * @param {Cell} cell The provided cell
486 * @param {Cell} cell The provided cell
489 * @return {Cell} the next cell or null if no cell was found.
487 * @return {Cell} the next cell or null if no cell was found.
490 */
488 */
491 Notebook.prototype.get_next_cell = function (cell) {
489 Notebook.prototype.get_next_cell = function (cell) {
492 var result = null;
490 var result = null;
493 var index = this.find_cell_index(cell);
491 var index = this.find_cell_index(cell);
494 if (this.is_valid_cell_index(index+1)) {
492 if (this.is_valid_cell_index(index+1)) {
495 result = this.get_cell(index+1);
493 result = this.get_cell(index+1);
496 }
494 }
497 return result;
495 return result;
498 };
496 };
499
497
500 /**
498 /**
501 * Get the cell above a given cell.
499 * Get the cell above a given cell.
502 *
500 *
503 * @method get_prev_cell
501 * @method get_prev_cell
504 * @param {Cell} cell The provided cell
502 * @param {Cell} cell The provided cell
505 * @return {Cell} The previous cell or null if no cell was found.
503 * @return {Cell} The previous cell or null if no cell was found.
506 */
504 */
507 Notebook.prototype.get_prev_cell = function (cell) {
505 Notebook.prototype.get_prev_cell = function (cell) {
508 var result = null;
506 var result = null;
509 var index = this.find_cell_index(cell);
507 var index = this.find_cell_index(cell);
510 if (index !== null && index > 0) {
508 if (index !== null && index > 0) {
511 result = this.get_cell(index-1);
509 result = this.get_cell(index-1);
512 }
510 }
513 return result;
511 return result;
514 };
512 };
515
513
516 /**
514 /**
517 * Get the numeric index of a given cell.
515 * Get the numeric index of a given cell.
518 *
516 *
519 * @method find_cell_index
517 * @method find_cell_index
520 * @param {Cell} cell The provided cell
518 * @param {Cell} cell The provided cell
521 * @return {Number} The cell's numeric index or null if no cell was found.
519 * @return {Number} The cell's numeric index or null if no cell was found.
522 */
520 */
523 Notebook.prototype.find_cell_index = function (cell) {
521 Notebook.prototype.find_cell_index = function (cell) {
524 var result = null;
522 var result = null;
525 this.get_cell_elements().filter(function (index) {
523 this.get_cell_elements().filter(function (index) {
526 if ($(this).data("cell") === cell) {
524 if ($(this).data("cell") === cell) {
527 result = index;
525 result = index;
528 }
526 }
529 });
527 });
530 return result;
528 return result;
531 };
529 };
532
530
533 /**
531 /**
534 * Get a given index , or the selected index if none is provided.
532 * Get a given index , or the selected index if none is provided.
535 *
533 *
536 * @method index_or_selected
534 * @method index_or_selected
537 * @param {Number} index A cell's index
535 * @param {Number} index A cell's index
538 * @return {Number} The given index, or selected index if none is provided.
536 * @return {Number} The given index, or selected index if none is provided.
539 */
537 */
540 Notebook.prototype.index_or_selected = function (index) {
538 Notebook.prototype.index_or_selected = function (index) {
541 var i;
539 var i;
542 if (index === undefined || index === null) {
540 if (index === undefined || index === null) {
543 i = this.get_selected_index();
541 i = this.get_selected_index();
544 if (i === null) {
542 if (i === null) {
545 i = 0;
543 i = 0;
546 }
544 }
547 } else {
545 } else {
548 i = index;
546 i = index;
549 }
547 }
550 return i;
548 return i;
551 };
549 };
552
550
553 /**
551 /**
554 * Get the currently selected cell.
552 * Get the currently selected cell.
555 * @method get_selected_cell
553 * @method get_selected_cell
556 * @return {Cell} The selected cell
554 * @return {Cell} The selected cell
557 */
555 */
558 Notebook.prototype.get_selected_cell = function () {
556 Notebook.prototype.get_selected_cell = function () {
559 var index = this.get_selected_index();
557 var index = this.get_selected_index();
560 return this.get_cell(index);
558 return this.get_cell(index);
561 };
559 };
562
560
563 /**
561 /**
564 * Check whether a cell index is valid.
562 * Check whether a cell index is valid.
565 *
563 *
566 * @method is_valid_cell_index
564 * @method is_valid_cell_index
567 * @param {Number} index A cell index
565 * @param {Number} index A cell index
568 * @return True if the index is valid, false otherwise
566 * @return True if the index is valid, false otherwise
569 */
567 */
570 Notebook.prototype.is_valid_cell_index = function (index) {
568 Notebook.prototype.is_valid_cell_index = function (index) {
571 if (index !== null && index >= 0 && index < this.ncells()) {
569 if (index !== null && index >= 0 && index < this.ncells()) {
572 return true;
570 return true;
573 } else {
571 } else {
574 return false;
572 return false;
575 }
573 }
576 };
574 };
577
575
578 /**
576 /**
579 * Get the index of the currently selected cell.
577 * Get the index of the currently selected cell.
580
578
581 * @method get_selected_index
579 * @method get_selected_index
582 * @return {Number} The selected cell's numeric index
580 * @return {Number} The selected cell's numeric index
583 */
581 */
584 Notebook.prototype.get_selected_index = function () {
582 Notebook.prototype.get_selected_index = function () {
585 var result = null;
583 var result = null;
586 this.get_cell_elements().filter(function (index) {
584 this.get_cell_elements().filter(function (index) {
587 if ($(this).data("cell").selected === true) {
585 if ($(this).data("cell").selected === true) {
588 result = index;
586 result = index;
589 }
587 }
590 });
588 });
591 return result;
589 return result;
592 };
590 };
593
591
594
592
595 // Cell selection.
593 // Cell selection.
596
594
597 /**
595 /**
598 * Programmatically select a cell.
596 * Programmatically select a cell.
599 *
597 *
600 * @method select
598 * @method select
601 * @param {Number} index A cell's index
599 * @param {Number} index A cell's index
602 * @return {Notebook} This notebook
600 * @return {Notebook} This notebook
603 */
601 */
604 Notebook.prototype.select = function (index) {
602 Notebook.prototype.select = function (index) {
605 if (this.is_valid_cell_index(index)) {
603 if (this.is_valid_cell_index(index)) {
606 var sindex = this.get_selected_index();
604 var sindex = this.get_selected_index();
607 if (sindex !== null && index !== sindex) {
605 if (sindex !== null && index !== sindex) {
608 // If we are about to select a different cell, make sure we are
606 // If we are about to select a different cell, make sure we are
609 // first in command mode.
607 // first in command mode.
610 if (this.mode !== 'command') {
608 if (this.mode !== 'command') {
611 this.command_mode();
609 this.command_mode();
612 }
610 }
613 this.get_cell(sindex).unselect();
611 this.get_cell(sindex).unselect();
614 }
612 }
615 var cell = this.get_cell(index);
613 var cell = this.get_cell(index);
616 cell.select();
614 cell.select();
617 if (cell.cell_type === 'heading') {
615 if (cell.cell_type === 'heading') {
618 this.events.trigger('selected_cell_type_changed.Notebook',
616 this.events.trigger('selected_cell_type_changed.Notebook',
619 {'cell_type':cell.cell_type,level:cell.level}
617 {'cell_type':cell.cell_type,level:cell.level}
620 );
618 );
621 } else {
619 } else {
622 this.events.trigger('selected_cell_type_changed.Notebook',
620 this.events.trigger('selected_cell_type_changed.Notebook',
623 {'cell_type':cell.cell_type}
621 {'cell_type':cell.cell_type}
624 );
622 );
625 }
623 }
626 }
624 }
627 return this;
625 return this;
628 };
626 };
629
627
630 /**
628 /**
631 * Programmatically select the next cell.
629 * Programmatically select the next cell.
632 *
630 *
633 * @method select_next
631 * @method select_next
634 * @return {Notebook} This notebook
632 * @return {Notebook} This notebook
635 */
633 */
636 Notebook.prototype.select_next = function () {
634 Notebook.prototype.select_next = function () {
637 var index = this.get_selected_index();
635 var index = this.get_selected_index();
638 this.select(index+1);
636 this.select(index+1);
639 return this;
637 return this;
640 };
638 };
641
639
642 /**
640 /**
643 * Programmatically select the previous cell.
641 * Programmatically select the previous cell.
644 *
642 *
645 * @method select_prev
643 * @method select_prev
646 * @return {Notebook} This notebook
644 * @return {Notebook} This notebook
647 */
645 */
648 Notebook.prototype.select_prev = function () {
646 Notebook.prototype.select_prev = function () {
649 var index = this.get_selected_index();
647 var index = this.get_selected_index();
650 this.select(index-1);
648 this.select(index-1);
651 return this;
649 return this;
652 };
650 };
653
651
654
652
655 // Edit/Command mode
653 // Edit/Command mode
656
654
657 /**
655 /**
658 * Gets the index of the cell that is in edit mode.
656 * Gets the index of the cell that is in edit mode.
659 *
657 *
660 * @method get_edit_index
658 * @method get_edit_index
661 *
659 *
662 * @return index {int}
660 * @return index {int}
663 **/
661 **/
664 Notebook.prototype.get_edit_index = function () {
662 Notebook.prototype.get_edit_index = function () {
665 var result = null;
663 var result = null;
666 this.get_cell_elements().filter(function (index) {
664 this.get_cell_elements().filter(function (index) {
667 if ($(this).data("cell").mode === 'edit') {
665 if ($(this).data("cell").mode === 'edit') {
668 result = index;
666 result = index;
669 }
667 }
670 });
668 });
671 return result;
669 return result;
672 };
670 };
673
671
674 /**
672 /**
675 * Handle when a a cell blurs and the notebook should enter command mode.
673 * Handle when a a cell blurs and the notebook should enter command mode.
676 *
674 *
677 * @method handle_command_mode
675 * @method handle_command_mode
678 * @param [cell] {Cell} Cell to enter command mode on.
676 * @param [cell] {Cell} Cell to enter command mode on.
679 **/
677 **/
680 Notebook.prototype.handle_command_mode = function (cell) {
678 Notebook.prototype.handle_command_mode = function (cell) {
681 if (this.mode !== 'command') {
679 if (this.mode !== 'command') {
682 cell.command_mode();
680 cell.command_mode();
683 this.mode = 'command';
681 this.mode = 'command';
684 this.events.trigger('command_mode.Notebook');
682 this.events.trigger('command_mode.Notebook');
685 this.keyboard_manager.command_mode();
683 this.keyboard_manager.command_mode();
686 }
684 }
687 };
685 };
688
686
689 /**
687 /**
690 * Make the notebook enter command mode.
688 * Make the notebook enter command mode.
691 *
689 *
692 * @method command_mode
690 * @method command_mode
693 **/
691 **/
694 Notebook.prototype.command_mode = function () {
692 Notebook.prototype.command_mode = function () {
695 var cell = this.get_cell(this.get_edit_index());
693 var cell = this.get_cell(this.get_edit_index());
696 if (cell && this.mode !== 'command') {
694 if (cell && this.mode !== 'command') {
697 // We don't call cell.command_mode, but rather call cell.focus_cell()
695 // We don't call cell.command_mode, but rather call cell.focus_cell()
698 // which will blur and CM editor and trigger the call to
696 // which will blur and CM editor and trigger the call to
699 // handle_command_mode.
697 // handle_command_mode.
700 cell.focus_cell();
698 cell.focus_cell();
701 }
699 }
702 };
700 };
703
701
704 /**
702 /**
705 * Handle when a cell fires it's edit_mode event.
703 * Handle when a cell fires it's edit_mode event.
706 *
704 *
707 * @method handle_edit_mode
705 * @method handle_edit_mode
708 * @param [cell] {Cell} Cell to enter edit mode on.
706 * @param [cell] {Cell} Cell to enter edit mode on.
709 **/
707 **/
710 Notebook.prototype.handle_edit_mode = function (cell) {
708 Notebook.prototype.handle_edit_mode = function (cell) {
711 if (cell && this.mode !== 'edit') {
709 if (cell && this.mode !== 'edit') {
712 cell.edit_mode();
710 cell.edit_mode();
713 this.mode = 'edit';
711 this.mode = 'edit';
714 this.events.trigger('edit_mode.Notebook');
712 this.events.trigger('edit_mode.Notebook');
715 this.keyboard_manager.edit_mode();
713 this.keyboard_manager.edit_mode();
716 }
714 }
717 };
715 };
718
716
719 /**
717 /**
720 * Make a cell enter edit mode.
718 * Make a cell enter edit mode.
721 *
719 *
722 * @method edit_mode
720 * @method edit_mode
723 **/
721 **/
724 Notebook.prototype.edit_mode = function () {
722 Notebook.prototype.edit_mode = function () {
725 var cell = this.get_selected_cell();
723 var cell = this.get_selected_cell();
726 if (cell && this.mode !== 'edit') {
724 if (cell && this.mode !== 'edit') {
727 cell.unrender();
725 cell.unrender();
728 cell.focus_editor();
726 cell.focus_editor();
729 }
727 }
730 };
728 };
731
729
732 /**
730 /**
733 * Focus the currently selected cell.
731 * Focus the currently selected cell.
734 *
732 *
735 * @method focus_cell
733 * @method focus_cell
736 **/
734 **/
737 Notebook.prototype.focus_cell = function () {
735 Notebook.prototype.focus_cell = function () {
738 var cell = this.get_selected_cell();
736 var cell = this.get_selected_cell();
739 if (cell === null) {return;} // No cell is selected
737 if (cell === null) {return;} // No cell is selected
740 cell.focus_cell();
738 cell.focus_cell();
741 };
739 };
742
740
743 // Cell movement
741 // Cell movement
744
742
745 /**
743 /**
746 * Move given (or selected) cell up and select it.
744 * Move given (or selected) cell up and select it.
747 *
745 *
748 * @method move_cell_up
746 * @method move_cell_up
749 * @param [index] {integer} cell index
747 * @param [index] {integer} cell index
750 * @return {Notebook} This notebook
748 * @return {Notebook} This notebook
751 **/
749 **/
752 Notebook.prototype.move_cell_up = function (index) {
750 Notebook.prototype.move_cell_up = function (index) {
753 var i = this.index_or_selected(index);
751 var i = this.index_or_selected(index);
754 if (this.is_valid_cell_index(i) && i > 0) {
752 if (this.is_valid_cell_index(i) && i > 0) {
755 var pivot = this.get_cell_element(i-1);
753 var pivot = this.get_cell_element(i-1);
756 var tomove = this.get_cell_element(i);
754 var tomove = this.get_cell_element(i);
757 if (pivot !== null && tomove !== null) {
755 if (pivot !== null && tomove !== null) {
758 tomove.detach();
756 tomove.detach();
759 pivot.before(tomove);
757 pivot.before(tomove);
760 this.select(i-1);
758 this.select(i-1);
761 var cell = this.get_selected_cell();
759 var cell = this.get_selected_cell();
762 cell.focus_cell();
760 cell.focus_cell();
763 }
761 }
764 this.set_dirty(true);
762 this.set_dirty(true);
765 }
763 }
766 return this;
764 return this;
767 };
765 };
768
766
769
767
770 /**
768 /**
771 * Move given (or selected) cell down and select it
769 * Move given (or selected) cell down and select it
772 *
770 *
773 * @method move_cell_down
771 * @method move_cell_down
774 * @param [index] {integer} cell index
772 * @param [index] {integer} cell index
775 * @return {Notebook} This notebook
773 * @return {Notebook} This notebook
776 **/
774 **/
777 Notebook.prototype.move_cell_down = function (index) {
775 Notebook.prototype.move_cell_down = function (index) {
778 var i = this.index_or_selected(index);
776 var i = this.index_or_selected(index);
779 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
777 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
780 var pivot = this.get_cell_element(i+1);
778 var pivot = this.get_cell_element(i+1);
781 var tomove = this.get_cell_element(i);
779 var tomove = this.get_cell_element(i);
782 if (pivot !== null && tomove !== null) {
780 if (pivot !== null && tomove !== null) {
783 tomove.detach();
781 tomove.detach();
784 pivot.after(tomove);
782 pivot.after(tomove);
785 this.select(i+1);
783 this.select(i+1);
786 var cell = this.get_selected_cell();
784 var cell = this.get_selected_cell();
787 cell.focus_cell();
785 cell.focus_cell();
788 }
786 }
789 }
787 }
790 this.set_dirty();
788 this.set_dirty();
791 return this;
789 return this;
792 };
790 };
793
791
794
792
795 // Insertion, deletion.
793 // Insertion, deletion.
796
794
797 /**
795 /**
798 * Delete a cell from the notebook.
796 * Delete a cell from the notebook.
799 *
797 *
800 * @method delete_cell
798 * @method delete_cell
801 * @param [index] A cell's numeric index
799 * @param [index] A cell's numeric index
802 * @return {Notebook} This notebook
800 * @return {Notebook} This notebook
803 */
801 */
804 Notebook.prototype.delete_cell = function (index) {
802 Notebook.prototype.delete_cell = function (index) {
805 var i = this.index_or_selected(index);
803 var i = this.index_or_selected(index);
806 var cell = this.get_cell(i);
804 var cell = this.get_cell(i);
807 if (!cell.is_deletable()) {
805 if (!cell.is_deletable()) {
808 return this;
806 return this;
809 }
807 }
810
808
811 this.undelete_backup = cell.toJSON();
809 this.undelete_backup = cell.toJSON();
812 $('#undelete_cell').removeClass('disabled');
810 $('#undelete_cell').removeClass('disabled');
813 if (this.is_valid_cell_index(i)) {
811 if (this.is_valid_cell_index(i)) {
814 var old_ncells = this.ncells();
812 var old_ncells = this.ncells();
815 var ce = this.get_cell_element(i);
813 var ce = this.get_cell_element(i);
816 ce.remove();
814 ce.remove();
817 if (i === 0) {
815 if (i === 0) {
818 // Always make sure we have at least one cell.
816 // Always make sure we have at least one cell.
819 if (old_ncells === 1) {
817 if (old_ncells === 1) {
820 this.insert_cell_below('code');
818 this.insert_cell_below('code');
821 }
819 }
822 this.select(0);
820 this.select(0);
823 this.undelete_index = 0;
821 this.undelete_index = 0;
824 this.undelete_below = false;
822 this.undelete_below = false;
825 } else if (i === old_ncells-1 && i !== 0) {
823 } else if (i === old_ncells-1 && i !== 0) {
826 this.select(i-1);
824 this.select(i-1);
827 this.undelete_index = i - 1;
825 this.undelete_index = i - 1;
828 this.undelete_below = true;
826 this.undelete_below = true;
829 } else {
827 } else {
830 this.select(i);
828 this.select(i);
831 this.undelete_index = i;
829 this.undelete_index = i;
832 this.undelete_below = false;
830 this.undelete_below = false;
833 }
831 }
834 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
832 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
835 this.set_dirty(true);
833 this.set_dirty(true);
836 }
834 }
837 return this;
835 return this;
838 };
836 };
839
837
840 /**
838 /**
841 * Restore the most recently deleted cell.
839 * Restore the most recently deleted cell.
842 *
840 *
843 * @method undelete
841 * @method undelete
844 */
842 */
845 Notebook.prototype.undelete_cell = function() {
843 Notebook.prototype.undelete_cell = function() {
846 if (this.undelete_backup !== null && this.undelete_index !== null) {
844 if (this.undelete_backup !== null && this.undelete_index !== null) {
847 var current_index = this.get_selected_index();
845 var current_index = this.get_selected_index();
848 if (this.undelete_index < current_index) {
846 if (this.undelete_index < current_index) {
849 current_index = current_index + 1;
847 current_index = current_index + 1;
850 }
848 }
851 if (this.undelete_index >= this.ncells()) {
849 if (this.undelete_index >= this.ncells()) {
852 this.select(this.ncells() - 1);
850 this.select(this.ncells() - 1);
853 }
851 }
854 else {
852 else {
855 this.select(this.undelete_index);
853 this.select(this.undelete_index);
856 }
854 }
857 var cell_data = this.undelete_backup;
855 var cell_data = this.undelete_backup;
858 var new_cell = null;
856 var new_cell = null;
859 if (this.undelete_below) {
857 if (this.undelete_below) {
860 new_cell = this.insert_cell_below(cell_data.cell_type);
858 new_cell = this.insert_cell_below(cell_data.cell_type);
861 } else {
859 } else {
862 new_cell = this.insert_cell_above(cell_data.cell_type);
860 new_cell = this.insert_cell_above(cell_data.cell_type);
863 }
861 }
864 new_cell.fromJSON(cell_data);
862 new_cell.fromJSON(cell_data);
865 if (this.undelete_below) {
863 if (this.undelete_below) {
866 this.select(current_index+1);
864 this.select(current_index+1);
867 } else {
865 } else {
868 this.select(current_index);
866 this.select(current_index);
869 }
867 }
870 this.undelete_backup = null;
868 this.undelete_backup = null;
871 this.undelete_index = null;
869 this.undelete_index = null;
872 }
870 }
873 $('#undelete_cell').addClass('disabled');
871 $('#undelete_cell').addClass('disabled');
874 };
872 };
875
873
876 /**
874 /**
877 * Insert a cell so that after insertion the cell is at given index.
875 * Insert a cell so that after insertion the cell is at given index.
878 *
876 *
879 * If cell type is not provided, it will default to the type of the
877 * If cell type is not provided, it will default to the type of the
880 * currently active cell.
878 * currently active cell.
881 *
879 *
882 * Similar to insert_above, but index parameter is mandatory
880 * Similar to insert_above, but index parameter is mandatory
883 *
881 *
884 * Index will be brought back into the accessible range [0,n]
882 * Index will be brought back into the accessible range [0,n]
885 *
883 *
886 * @method insert_cell_at_index
884 * @method insert_cell_at_index
887 * @param [type] {string} in ['code','markdown', 'raw'], defaults to 'code'
885 * @param [type] {string} in ['code','markdown', 'raw'], defaults to 'code'
888 * @param [index] {int} a valid index where to insert cell
886 * @param [index] {int} a valid index where to insert cell
889 *
887 *
890 * @return cell {cell|null} created cell or null
888 * @return cell {cell|null} created cell or null
891 **/
889 **/
892 Notebook.prototype.insert_cell_at_index = function(type, index){
890 Notebook.prototype.insert_cell_at_index = function(type, index){
893
891
894 var ncells = this.ncells();
892 var ncells = this.ncells();
895 index = Math.min(index, ncells);
893 index = Math.min(index, ncells);
896 index = Math.max(index, 0);
894 index = Math.max(index, 0);
897 var cell = null;
895 var cell = null;
898 type = type || this.default_cell_type;
896 type = type || this.default_cell_type;
899 if (type === 'above') {
897 if (type === 'above') {
900 if (index > 0) {
898 if (index > 0) {
901 type = this.get_cell(index-1).cell_type;
899 type = this.get_cell(index-1).cell_type;
902 } else {
900 } else {
903 type = 'code';
901 type = 'code';
904 }
902 }
905 } else if (type === 'below') {
903 } else if (type === 'below') {
906 if (index < ncells) {
904 if (index < ncells) {
907 type = this.get_cell(index).cell_type;
905 type = this.get_cell(index).cell_type;
908 } else {
906 } else {
909 type = 'code';
907 type = 'code';
910 }
908 }
911 } else if (type === 'selected') {
909 } else if (type === 'selected') {
912 type = this.get_selected_cell().cell_type;
910 type = this.get_selected_cell().cell_type;
913 }
911 }
914
912
915 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
913 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
916 var cell_options = {
914 var cell_options = {
917 events: this.events,
915 events: this.events,
918 config: this.config,
916 config: this.config,
919 keyboard_manager: this.keyboard_manager,
917 keyboard_manager: this.keyboard_manager,
920 notebook: this,
918 notebook: this,
921 tooltip: this.tooltip
919 tooltip: this.tooltip
922 };
920 };
923 switch(type) {
921 switch(type) {
924 case 'code':
922 case 'code':
925 cell = new codecell.CodeCell(this.kernel, cell_options);
923 cell = new codecell.CodeCell(this.kernel, cell_options);
926 cell.set_input_prompt();
924 cell.set_input_prompt();
927 break;
925 break;
928 case 'markdown':
926 case 'markdown':
929 cell = new textcell.MarkdownCell(cell_options);
927 cell = new textcell.MarkdownCell(cell_options);
930 break;
928 break;
931 case 'raw':
929 case 'raw':
932 cell = new textcell.RawCell(cell_options);
930 cell = new textcell.RawCell(cell_options);
933 break;
931 break;
934 default:
932 default:
935 console.log("Unrecognized cell type: ", type, cellmod);
933 console.log("Unrecognized cell type: ", type, cellmod);
936 cell = new cellmod.UnrecognizedCell(cell_options);
934 cell = new cellmod.UnrecognizedCell(cell_options);
937 }
935 }
938
936
939 if(this._insert_element_at_index(cell.element,index)) {
937 if(this._insert_element_at_index(cell.element,index)) {
940 cell.render();
938 cell.render();
941 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
939 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
942 cell.refresh();
940 cell.refresh();
943 // We used to select the cell after we refresh it, but there
941 // We used to select the cell after we refresh it, but there
944 // are now cases were this method is called where select is
942 // are now cases were this method is called where select is
945 // not appropriate. The selection logic should be handled by the
943 // not appropriate. The selection logic should be handled by the
946 // caller of the the top level insert_cell methods.
944 // caller of the the top level insert_cell methods.
947 this.set_dirty(true);
945 this.set_dirty(true);
948 }
946 }
949 }
947 }
950 return cell;
948 return cell;
951
949
952 };
950 };
953
951
954 /**
952 /**
955 * Insert an element at given cell index.
953 * Insert an element at given cell index.
956 *
954 *
957 * @method _insert_element_at_index
955 * @method _insert_element_at_index
958 * @param element {dom_element} a cell element
956 * @param element {dom_element} a cell element
959 * @param [index] {int} a valid index where to inser cell
957 * @param [index] {int} a valid index where to inser cell
960 * @private
958 * @private
961 *
959 *
962 * return true if everything whent fine.
960 * return true if everything whent fine.
963 **/
961 **/
964 Notebook.prototype._insert_element_at_index = function(element, index){
962 Notebook.prototype._insert_element_at_index = function(element, index){
965 if (element === undefined){
963 if (element === undefined){
966 return false;
964 return false;
967 }
965 }
968
966
969 var ncells = this.ncells();
967 var ncells = this.ncells();
970
968
971 if (ncells === 0) {
969 if (ncells === 0) {
972 // special case append if empty
970 // special case append if empty
973 this.element.find('div.end_space').before(element);
971 this.element.find('div.end_space').before(element);
974 } else if ( ncells === index ) {
972 } else if ( ncells === index ) {
975 // special case append it the end, but not empty
973 // special case append it the end, but not empty
976 this.get_cell_element(index-1).after(element);
974 this.get_cell_element(index-1).after(element);
977 } else if (this.is_valid_cell_index(index)) {
975 } else if (this.is_valid_cell_index(index)) {
978 // otherwise always somewhere to append to
976 // otherwise always somewhere to append to
979 this.get_cell_element(index).before(element);
977 this.get_cell_element(index).before(element);
980 } else {
978 } else {
981 return false;
979 return false;
982 }
980 }
983
981
984 if (this.undelete_index !== null && index <= this.undelete_index) {
982 if (this.undelete_index !== null && index <= this.undelete_index) {
985 this.undelete_index = this.undelete_index + 1;
983 this.undelete_index = this.undelete_index + 1;
986 this.set_dirty(true);
984 this.set_dirty(true);
987 }
985 }
988 return true;
986 return true;
989 };
987 };
990
988
991 /**
989 /**
992 * Insert a cell of given type above given index, or at top
990 * Insert a cell of given type above given index, or at top
993 * of notebook if index smaller than 0.
991 * of notebook if index smaller than 0.
994 *
992 *
995 * default index value is the one of currently selected cell
993 * default index value is the one of currently selected cell
996 *
994 *
997 * @method insert_cell_above
995 * @method insert_cell_above
998 * @param [type] {string} cell type
996 * @param [type] {string} cell type
999 * @param [index] {integer}
997 * @param [index] {integer}
1000 *
998 *
1001 * @return handle to created cell or null
999 * @return handle to created cell or null
1002 **/
1000 **/
1003 Notebook.prototype.insert_cell_above = function (type, index) {
1001 Notebook.prototype.insert_cell_above = function (type, index) {
1004 index = this.index_or_selected(index);
1002 index = this.index_or_selected(index);
1005 return this.insert_cell_at_index(type, index);
1003 return this.insert_cell_at_index(type, index);
1006 };
1004 };
1007
1005
1008 /**
1006 /**
1009 * Insert a cell of given type below given index, or at bottom
1007 * Insert a cell of given type below given index, or at bottom
1010 * of notebook if index greater than number of cells
1008 * of notebook if index greater than number of cells
1011 *
1009 *
1012 * default index value is the one of currently selected cell
1010 * default index value is the one of currently selected cell
1013 *
1011 *
1014 * @method insert_cell_below
1012 * @method insert_cell_below
1015 * @param [type] {string} cell type
1013 * @param [type] {string} cell type
1016 * @param [index] {integer}
1014 * @param [index] {integer}
1017 *
1015 *
1018 * @return handle to created cell or null
1016 * @return handle to created cell or null
1019 *
1017 *
1020 **/
1018 **/
1021 Notebook.prototype.insert_cell_below = function (type, index) {
1019 Notebook.prototype.insert_cell_below = function (type, index) {
1022 index = this.index_or_selected(index);
1020 index = this.index_or_selected(index);
1023 return this.insert_cell_at_index(type, index+1);
1021 return this.insert_cell_at_index(type, index+1);
1024 };
1022 };
1025
1023
1026
1024
1027 /**
1025 /**
1028 * Insert cell at end of notebook
1026 * Insert cell at end of notebook
1029 *
1027 *
1030 * @method insert_cell_at_bottom
1028 * @method insert_cell_at_bottom
1031 * @param {String} type cell type
1029 * @param {String} type cell type
1032 *
1030 *
1033 * @return the added cell; or null
1031 * @return the added cell; or null
1034 **/
1032 **/
1035 Notebook.prototype.insert_cell_at_bottom = function (type){
1033 Notebook.prototype.insert_cell_at_bottom = function (type){
1036 var len = this.ncells();
1034 var len = this.ncells();
1037 return this.insert_cell_below(type,len-1);
1035 return this.insert_cell_below(type,len-1);
1038 };
1036 };
1039
1037
1040 /**
1038 /**
1041 * Turn a cell into a code cell.
1039 * Turn a cell into a code cell.
1042 *
1040 *
1043 * @method to_code
1041 * @method to_code
1044 * @param {Number} [index] A cell's index
1042 * @param {Number} [index] A cell's index
1045 */
1043 */
1046 Notebook.prototype.to_code = function (index) {
1044 Notebook.prototype.to_code = function (index) {
1047 var i = this.index_or_selected(index);
1045 var i = this.index_or_selected(index);
1048 if (this.is_valid_cell_index(i)) {
1046 if (this.is_valid_cell_index(i)) {
1049 var source_cell = this.get_cell(i);
1047 var source_cell = this.get_cell(i);
1050 if (!(source_cell instanceof codecell.CodeCell)) {
1048 if (!(source_cell instanceof codecell.CodeCell)) {
1051 var target_cell = this.insert_cell_below('code',i);
1049 var target_cell = this.insert_cell_below('code',i);
1052 var text = source_cell.get_text();
1050 var text = source_cell.get_text();
1053 if (text === source_cell.placeholder) {
1051 if (text === source_cell.placeholder) {
1054 text = '';
1052 text = '';
1055 }
1053 }
1056 //metadata
1054 //metadata
1057 target_cell.metadata = source_cell.metadata;
1055 target_cell.metadata = source_cell.metadata;
1058
1056
1059 target_cell.set_text(text);
1057 target_cell.set_text(text);
1060 // make this value the starting point, so that we can only undo
1058 // make this value the starting point, so that we can only undo
1061 // to this state, instead of a blank cell
1059 // to this state, instead of a blank cell
1062 target_cell.code_mirror.clearHistory();
1060 target_cell.code_mirror.clearHistory();
1063 source_cell.element.remove();
1061 source_cell.element.remove();
1064 this.select(i);
1062 this.select(i);
1065 var cursor = source_cell.code_mirror.getCursor();
1063 var cursor = source_cell.code_mirror.getCursor();
1066 target_cell.code_mirror.setCursor(cursor);
1064 target_cell.code_mirror.setCursor(cursor);
1067 this.set_dirty(true);
1065 this.set_dirty(true);
1068 }
1066 }
1069 }
1067 }
1070 };
1068 };
1071
1069
1072 /**
1070 /**
1073 * Turn a cell into a Markdown cell.
1071 * Turn a cell into a Markdown cell.
1074 *
1072 *
1075 * @method to_markdown
1073 * @method to_markdown
1076 * @param {Number} [index] A cell's index
1074 * @param {Number} [index] A cell's index
1077 */
1075 */
1078 Notebook.prototype.to_markdown = function (index) {
1076 Notebook.prototype.to_markdown = function (index) {
1079 var i = this.index_or_selected(index);
1077 var i = this.index_or_selected(index);
1080 if (this.is_valid_cell_index(i)) {
1078 if (this.is_valid_cell_index(i)) {
1081 var source_cell = this.get_cell(i);
1079 var source_cell = this.get_cell(i);
1082
1080
1083 if (!(source_cell instanceof textcell.MarkdownCell)) {
1081 if (!(source_cell instanceof textcell.MarkdownCell)) {
1084 var target_cell = this.insert_cell_below('markdown',i);
1082 var target_cell = this.insert_cell_below('markdown',i);
1085 var text = source_cell.get_text();
1083 var text = source_cell.get_text();
1086
1084
1087 if (text === source_cell.placeholder) {
1085 if (text === source_cell.placeholder) {
1088 text = '';
1086 text = '';
1089 }
1087 }
1090 // metadata
1088 // metadata
1091 target_cell.metadata = source_cell.metadata;
1089 target_cell.metadata = source_cell.metadata;
1092 // We must show the editor before setting its contents
1090 // We must show the editor before setting its contents
1093 target_cell.unrender();
1091 target_cell.unrender();
1094 target_cell.set_text(text);
1092 target_cell.set_text(text);
1095 // make this value the starting point, so that we can only undo
1093 // make this value the starting point, so that we can only undo
1096 // to this state, instead of a blank cell
1094 // to this state, instead of a blank cell
1097 target_cell.code_mirror.clearHistory();
1095 target_cell.code_mirror.clearHistory();
1098 source_cell.element.remove();
1096 source_cell.element.remove();
1099 this.select(i);
1097 this.select(i);
1100 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1098 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1101 target_cell.render();
1099 target_cell.render();
1102 }
1100 }
1103 var cursor = source_cell.code_mirror.getCursor();
1101 var cursor = source_cell.code_mirror.getCursor();
1104 target_cell.code_mirror.setCursor(cursor);
1102 target_cell.code_mirror.setCursor(cursor);
1105 this.set_dirty(true);
1103 this.set_dirty(true);
1106 }
1104 }
1107 }
1105 }
1108 };
1106 };
1109
1107
1110 /**
1108 /**
1111 * Turn a cell into a raw text cell.
1109 * Turn a cell into a raw text cell.
1112 *
1110 *
1113 * @method to_raw
1111 * @method to_raw
1114 * @param {Number} [index] A cell's index
1112 * @param {Number} [index] A cell's index
1115 */
1113 */
1116 Notebook.prototype.to_raw = function (index) {
1114 Notebook.prototype.to_raw = function (index) {
1117 var i = this.index_or_selected(index);
1115 var i = this.index_or_selected(index);
1118 if (this.is_valid_cell_index(i)) {
1116 if (this.is_valid_cell_index(i)) {
1119 var target_cell = null;
1117 var target_cell = null;
1120 var source_cell = this.get_cell(i);
1118 var source_cell = this.get_cell(i);
1121
1119
1122 if (!(source_cell instanceof textcell.RawCell)) {
1120 if (!(source_cell instanceof textcell.RawCell)) {
1123 target_cell = this.insert_cell_below('raw',i);
1121 target_cell = this.insert_cell_below('raw',i);
1124 var text = source_cell.get_text();
1122 var text = source_cell.get_text();
1125 if (text === source_cell.placeholder) {
1123 if (text === source_cell.placeholder) {
1126 text = '';
1124 text = '';
1127 }
1125 }
1128 //metadata
1126 //metadata
1129 target_cell.metadata = source_cell.metadata;
1127 target_cell.metadata = source_cell.metadata;
1130 // We must show the editor before setting its contents
1128 // We must show the editor before setting its contents
1131 target_cell.unrender();
1129 target_cell.unrender();
1132 target_cell.set_text(text);
1130 target_cell.set_text(text);
1133 // make this value the starting point, so that we can only undo
1131 // make this value the starting point, so that we can only undo
1134 // to this state, instead of a blank cell
1132 // to this state, instead of a blank cell
1135 target_cell.code_mirror.clearHistory();
1133 target_cell.code_mirror.clearHistory();
1136 source_cell.element.remove();
1134 source_cell.element.remove();
1137 this.select(i);
1135 this.select(i);
1138 var cursor = source_cell.code_mirror.getCursor();
1136 var cursor = source_cell.code_mirror.getCursor();
1139 target_cell.code_mirror.setCursor(cursor);
1137 target_cell.code_mirror.setCursor(cursor);
1140 this.set_dirty(true);
1138 this.set_dirty(true);
1141 }
1139 }
1142 }
1140 }
1143 };
1141 };
1144
1142
1145 Notebook.prototype._warn_heading = function () {
1143 Notebook.prototype._warn_heading = function () {
1146 // warn about heading cells being removed
1144 // warn about heading cells being removed
1147 dialog.modal({
1145 dialog.modal({
1148 notebook: this,
1146 notebook: this,
1149 keyboard_manager: this.keyboard_manager,
1147 keyboard_manager: this.keyboard_manager,
1150 title : "Use markdown headings",
1148 title : "Use markdown headings",
1151 body : $("<p/>").text(
1149 body : $("<p/>").text(
1152 'IPython no longer uses special heading cells. ' +
1150 'IPython no longer uses special heading cells. ' +
1153 'Instead, write your headings in Markdown cells using # characters:'
1151 'Instead, write your headings in Markdown cells using # characters:'
1154 ).append($('<pre/>').text(
1152 ).append($('<pre/>').text(
1155 '## This is a level 2 heading'
1153 '## This is a level 2 heading'
1156 )),
1154 )),
1157 buttons : {
1155 buttons : {
1158 "OK" : {}
1156 "OK" : {}
1159 }
1157 }
1160 });
1158 });
1161 };
1159 };
1162
1160
1163 /**
1161 /**
1164 * Turn a cell into a markdown cell with a heading.
1162 * Turn a cell into a markdown cell with a heading.
1165 *
1163 *
1166 * @method to_heading
1164 * @method to_heading
1167 * @param {Number} [index] A cell's index
1165 * @param {Number} [index] A cell's index
1168 * @param {Number} [level] A heading level (e.g., 1 for h1)
1166 * @param {Number} [level] A heading level (e.g., 1 for h1)
1169 */
1167 */
1170 Notebook.prototype.to_heading = function (index, level) {
1168 Notebook.prototype.to_heading = function (index, level) {
1171 this.to_markdown(index);
1169 this.to_markdown(index);
1172 level = level || 1;
1170 level = level || 1;
1173 var i = this.index_or_selected(index);
1171 var i = this.index_or_selected(index);
1174 if (this.is_valid_cell_index(i)) {
1172 if (this.is_valid_cell_index(i)) {
1175 var cell = this.get_cell(i);
1173 var cell = this.get_cell(i);
1176 cell.set_heading_level(level);
1174 cell.set_heading_level(level);
1177 this.set_dirty(true);
1175 this.set_dirty(true);
1178 }
1176 }
1179 };
1177 };
1180
1178
1181
1179
1182 // Cut/Copy/Paste
1180 // Cut/Copy/Paste
1183
1181
1184 /**
1182 /**
1185 * Enable UI elements for pasting cells.
1183 * Enable UI elements for pasting cells.
1186 *
1184 *
1187 * @method enable_paste
1185 * @method enable_paste
1188 */
1186 */
1189 Notebook.prototype.enable_paste = function () {
1187 Notebook.prototype.enable_paste = function () {
1190 var that = this;
1188 var that = this;
1191 if (!this.paste_enabled) {
1189 if (!this.paste_enabled) {
1192 $('#paste_cell_replace').removeClass('disabled')
1190 $('#paste_cell_replace').removeClass('disabled')
1193 .on('click', function () {that.paste_cell_replace();});
1191 .on('click', function () {that.paste_cell_replace();});
1194 $('#paste_cell_above').removeClass('disabled')
1192 $('#paste_cell_above').removeClass('disabled')
1195 .on('click', function () {that.paste_cell_above();});
1193 .on('click', function () {that.paste_cell_above();});
1196 $('#paste_cell_below').removeClass('disabled')
1194 $('#paste_cell_below').removeClass('disabled')
1197 .on('click', function () {that.paste_cell_below();});
1195 .on('click', function () {that.paste_cell_below();});
1198 this.paste_enabled = true;
1196 this.paste_enabled = true;
1199 }
1197 }
1200 };
1198 };
1201
1199
1202 /**
1200 /**
1203 * Disable UI elements for pasting cells.
1201 * Disable UI elements for pasting cells.
1204 *
1202 *
1205 * @method disable_paste
1203 * @method disable_paste
1206 */
1204 */
1207 Notebook.prototype.disable_paste = function () {
1205 Notebook.prototype.disable_paste = function () {
1208 if (this.paste_enabled) {
1206 if (this.paste_enabled) {
1209 $('#paste_cell_replace').addClass('disabled').off('click');
1207 $('#paste_cell_replace').addClass('disabled').off('click');
1210 $('#paste_cell_above').addClass('disabled').off('click');
1208 $('#paste_cell_above').addClass('disabled').off('click');
1211 $('#paste_cell_below').addClass('disabled').off('click');
1209 $('#paste_cell_below').addClass('disabled').off('click');
1212 this.paste_enabled = false;
1210 this.paste_enabled = false;
1213 }
1211 }
1214 };
1212 };
1215
1213
1216 /**
1214 /**
1217 * Cut a cell.
1215 * Cut a cell.
1218 *
1216 *
1219 * @method cut_cell
1217 * @method cut_cell
1220 */
1218 */
1221 Notebook.prototype.cut_cell = function () {
1219 Notebook.prototype.cut_cell = function () {
1222 this.copy_cell();
1220 this.copy_cell();
1223 this.delete_cell();
1221 this.delete_cell();
1224 };
1222 };
1225
1223
1226 /**
1224 /**
1227 * Copy a cell.
1225 * Copy a cell.
1228 *
1226 *
1229 * @method copy_cell
1227 * @method copy_cell
1230 */
1228 */
1231 Notebook.prototype.copy_cell = function () {
1229 Notebook.prototype.copy_cell = function () {
1232 var cell = this.get_selected_cell();
1230 var cell = this.get_selected_cell();
1233 this.clipboard = cell.toJSON();
1231 this.clipboard = cell.toJSON();
1234 // remove undeletable status from the copied cell
1232 // remove undeletable status from the copied cell
1235 if (this.clipboard.metadata.deletable !== undefined) {
1233 if (this.clipboard.metadata.deletable !== undefined) {
1236 delete this.clipboard.metadata.deletable;
1234 delete this.clipboard.metadata.deletable;
1237 }
1235 }
1238 this.enable_paste();
1236 this.enable_paste();
1239 };
1237 };
1240
1238
1241 /**
1239 /**
1242 * Replace the selected cell with a cell in the clipboard.
1240 * Replace the selected cell with a cell in the clipboard.
1243 *
1241 *
1244 * @method paste_cell_replace
1242 * @method paste_cell_replace
1245 */
1243 */
1246 Notebook.prototype.paste_cell_replace = function () {
1244 Notebook.prototype.paste_cell_replace = function () {
1247 if (this.clipboard !== null && this.paste_enabled) {
1245 if (this.clipboard !== null && this.paste_enabled) {
1248 var cell_data = this.clipboard;
1246 var cell_data = this.clipboard;
1249 var new_cell = this.insert_cell_above(cell_data.cell_type);
1247 var new_cell = this.insert_cell_above(cell_data.cell_type);
1250 new_cell.fromJSON(cell_data);
1248 new_cell.fromJSON(cell_data);
1251 var old_cell = this.get_next_cell(new_cell);
1249 var old_cell = this.get_next_cell(new_cell);
1252 this.delete_cell(this.find_cell_index(old_cell));
1250 this.delete_cell(this.find_cell_index(old_cell));
1253 this.select(this.find_cell_index(new_cell));
1251 this.select(this.find_cell_index(new_cell));
1254 }
1252 }
1255 };
1253 };
1256
1254
1257 /**
1255 /**
1258 * Paste a cell from the clipboard above the selected cell.
1256 * Paste a cell from the clipboard above the selected cell.
1259 *
1257 *
1260 * @method paste_cell_above
1258 * @method paste_cell_above
1261 */
1259 */
1262 Notebook.prototype.paste_cell_above = function () {
1260 Notebook.prototype.paste_cell_above = function () {
1263 if (this.clipboard !== null && this.paste_enabled) {
1261 if (this.clipboard !== null && this.paste_enabled) {
1264 var cell_data = this.clipboard;
1262 var cell_data = this.clipboard;
1265 var new_cell = this.insert_cell_above(cell_data.cell_type);
1263 var new_cell = this.insert_cell_above(cell_data.cell_type);
1266 new_cell.fromJSON(cell_data);
1264 new_cell.fromJSON(cell_data);
1267 new_cell.focus_cell();
1265 new_cell.focus_cell();
1268 }
1266 }
1269 };
1267 };
1270
1268
1271 /**
1269 /**
1272 * Paste a cell from the clipboard below the selected cell.
1270 * Paste a cell from the clipboard below the selected cell.
1273 *
1271 *
1274 * @method paste_cell_below
1272 * @method paste_cell_below
1275 */
1273 */
1276 Notebook.prototype.paste_cell_below = function () {
1274 Notebook.prototype.paste_cell_below = function () {
1277 if (this.clipboard !== null && this.paste_enabled) {
1275 if (this.clipboard !== null && this.paste_enabled) {
1278 var cell_data = this.clipboard;
1276 var cell_data = this.clipboard;
1279 var new_cell = this.insert_cell_below(cell_data.cell_type);
1277 var new_cell = this.insert_cell_below(cell_data.cell_type);
1280 new_cell.fromJSON(cell_data);
1278 new_cell.fromJSON(cell_data);
1281 new_cell.focus_cell();
1279 new_cell.focus_cell();
1282 }
1280 }
1283 };
1281 };
1284
1282
1285 // Split/merge
1283 // Split/merge
1286
1284
1287 /**
1285 /**
1288 * Split the selected cell into two, at the cursor.
1286 * Split the selected cell into two, at the cursor.
1289 *
1287 *
1290 * @method split_cell
1288 * @method split_cell
1291 */
1289 */
1292 Notebook.prototype.split_cell = function () {
1290 Notebook.prototype.split_cell = function () {
1293 var cell = this.get_selected_cell();
1291 var cell = this.get_selected_cell();
1294 if (cell.is_splittable()) {
1292 if (cell.is_splittable()) {
1295 var texta = cell.get_pre_cursor();
1293 var texta = cell.get_pre_cursor();
1296 var textb = cell.get_post_cursor();
1294 var textb = cell.get_post_cursor();
1297 cell.set_text(textb);
1295 cell.set_text(textb);
1298 var new_cell = this.insert_cell_above(cell.cell_type);
1296 var new_cell = this.insert_cell_above(cell.cell_type);
1299 // Unrender the new cell so we can call set_text.
1297 // Unrender the new cell so we can call set_text.
1300 new_cell.unrender();
1298 new_cell.unrender();
1301 new_cell.set_text(texta);
1299 new_cell.set_text(texta);
1302 }
1300 }
1303 };
1301 };
1304
1302
1305 /**
1303 /**
1306 * Combine the selected cell into the cell above it.
1304 * Combine the selected cell into the cell above it.
1307 *
1305 *
1308 * @method merge_cell_above
1306 * @method merge_cell_above
1309 */
1307 */
1310 Notebook.prototype.merge_cell_above = function () {
1308 Notebook.prototype.merge_cell_above = function () {
1311 var index = this.get_selected_index();
1309 var index = this.get_selected_index();
1312 var cell = this.get_cell(index);
1310 var cell = this.get_cell(index);
1313 var render = cell.rendered;
1311 var render = cell.rendered;
1314 if (!cell.is_mergeable()) {
1312 if (!cell.is_mergeable()) {
1315 return;
1313 return;
1316 }
1314 }
1317 if (index > 0) {
1315 if (index > 0) {
1318 var upper_cell = this.get_cell(index-1);
1316 var upper_cell = this.get_cell(index-1);
1319 if (!upper_cell.is_mergeable()) {
1317 if (!upper_cell.is_mergeable()) {
1320 return;
1318 return;
1321 }
1319 }
1322 var upper_text = upper_cell.get_text();
1320 var upper_text = upper_cell.get_text();
1323 var text = cell.get_text();
1321 var text = cell.get_text();
1324 if (cell instanceof codecell.CodeCell) {
1322 if (cell instanceof codecell.CodeCell) {
1325 cell.set_text(upper_text+'\n'+text);
1323 cell.set_text(upper_text+'\n'+text);
1326 } else {
1324 } else {
1327 cell.unrender(); // Must unrender before we set_text.
1325 cell.unrender(); // Must unrender before we set_text.
1328 cell.set_text(upper_text+'\n\n'+text);
1326 cell.set_text(upper_text+'\n\n'+text);
1329 if (render) {
1327 if (render) {
1330 // The rendered state of the final cell should match
1328 // The rendered state of the final cell should match
1331 // that of the original selected cell;
1329 // that of the original selected cell;
1332 cell.render();
1330 cell.render();
1333 }
1331 }
1334 }
1332 }
1335 this.delete_cell(index-1);
1333 this.delete_cell(index-1);
1336 this.select(this.find_cell_index(cell));
1334 this.select(this.find_cell_index(cell));
1337 }
1335 }
1338 };
1336 };
1339
1337
1340 /**
1338 /**
1341 * Combine the selected cell into the cell below it.
1339 * Combine the selected cell into the cell below it.
1342 *
1340 *
1343 * @method merge_cell_below
1341 * @method merge_cell_below
1344 */
1342 */
1345 Notebook.prototype.merge_cell_below = function () {
1343 Notebook.prototype.merge_cell_below = function () {
1346 var index = this.get_selected_index();
1344 var index = this.get_selected_index();
1347 var cell = this.get_cell(index);
1345 var cell = this.get_cell(index);
1348 var render = cell.rendered;
1346 var render = cell.rendered;
1349 if (!cell.is_mergeable()) {
1347 if (!cell.is_mergeable()) {
1350 return;
1348 return;
1351 }
1349 }
1352 if (index < this.ncells()-1) {
1350 if (index < this.ncells()-1) {
1353 var lower_cell = this.get_cell(index+1);
1351 var lower_cell = this.get_cell(index+1);
1354 if (!lower_cell.is_mergeable()) {
1352 if (!lower_cell.is_mergeable()) {
1355 return;
1353 return;
1356 }
1354 }
1357 var lower_text = lower_cell.get_text();
1355 var lower_text = lower_cell.get_text();
1358 var text = cell.get_text();
1356 var text = cell.get_text();
1359 if (cell instanceof codecell.CodeCell) {
1357 if (cell instanceof codecell.CodeCell) {
1360 cell.set_text(text+'\n'+lower_text);
1358 cell.set_text(text+'\n'+lower_text);
1361 } else {
1359 } else {
1362 cell.unrender(); // Must unrender before we set_text.
1360 cell.unrender(); // Must unrender before we set_text.
1363 cell.set_text(text+'\n\n'+lower_text);
1361 cell.set_text(text+'\n\n'+lower_text);
1364 if (render) {
1362 if (render) {
1365 // The rendered state of the final cell should match
1363 // The rendered state of the final cell should match
1366 // that of the original selected cell;
1364 // that of the original selected cell;
1367 cell.render();
1365 cell.render();
1368 }
1366 }
1369 }
1367 }
1370 this.delete_cell(index+1);
1368 this.delete_cell(index+1);
1371 this.select(this.find_cell_index(cell));
1369 this.select(this.find_cell_index(cell));
1372 }
1370 }
1373 };
1371 };
1374
1372
1375
1373
1376 // Cell collapsing and output clearing
1374 // Cell collapsing and output clearing
1377
1375
1378 /**
1376 /**
1379 * Hide a cell's output.
1377 * Hide a cell's output.
1380 *
1378 *
1381 * @method collapse_output
1379 * @method collapse_output
1382 * @param {Number} index A cell's numeric index
1380 * @param {Number} index A cell's numeric index
1383 */
1381 */
1384 Notebook.prototype.collapse_output = function (index) {
1382 Notebook.prototype.collapse_output = function (index) {
1385 var i = this.index_or_selected(index);
1383 var i = this.index_or_selected(index);
1386 var cell = this.get_cell(i);
1384 var cell = this.get_cell(i);
1387 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1385 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1388 cell.collapse_output();
1386 cell.collapse_output();
1389 this.set_dirty(true);
1387 this.set_dirty(true);
1390 }
1388 }
1391 };
1389 };
1392
1390
1393 /**
1391 /**
1394 * Hide each code cell's output area.
1392 * Hide each code cell's output area.
1395 *
1393 *
1396 * @method collapse_all_output
1394 * @method collapse_all_output
1397 */
1395 */
1398 Notebook.prototype.collapse_all_output = function () {
1396 Notebook.prototype.collapse_all_output = function () {
1399 this.get_cells().map(function (cell, i) {
1397 this.get_cells().map(function (cell, i) {
1400 if (cell instanceof codecell.CodeCell) {
1398 if (cell instanceof codecell.CodeCell) {
1401 cell.collapse_output();
1399 cell.collapse_output();
1402 }
1400 }
1403 });
1401 });
1404 // this should not be set if the `collapse` key is removed from nbformat
1402 // this should not be set if the `collapse` key is removed from nbformat
1405 this.set_dirty(true);
1403 this.set_dirty(true);
1406 };
1404 };
1407
1405
1408 /**
1406 /**
1409 * Show a cell's output.
1407 * Show a cell's output.
1410 *
1408 *
1411 * @method expand_output
1409 * @method expand_output
1412 * @param {Number} index A cell's numeric index
1410 * @param {Number} index A cell's numeric index
1413 */
1411 */
1414 Notebook.prototype.expand_output = function (index) {
1412 Notebook.prototype.expand_output = function (index) {
1415 var i = this.index_or_selected(index);
1413 var i = this.index_or_selected(index);
1416 var cell = this.get_cell(i);
1414 var cell = this.get_cell(i);
1417 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1415 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1418 cell.expand_output();
1416 cell.expand_output();
1419 this.set_dirty(true);
1417 this.set_dirty(true);
1420 }
1418 }
1421 };
1419 };
1422
1420
1423 /**
1421 /**
1424 * Expand each code cell's output area, and remove scrollbars.
1422 * Expand each code cell's output area, and remove scrollbars.
1425 *
1423 *
1426 * @method expand_all_output
1424 * @method expand_all_output
1427 */
1425 */
1428 Notebook.prototype.expand_all_output = function () {
1426 Notebook.prototype.expand_all_output = function () {
1429 this.get_cells().map(function (cell, i) {
1427 this.get_cells().map(function (cell, i) {
1430 if (cell instanceof codecell.CodeCell) {
1428 if (cell instanceof codecell.CodeCell) {
1431 cell.expand_output();
1429 cell.expand_output();
1432 }
1430 }
1433 });
1431 });
1434 // this should not be set if the `collapse` key is removed from nbformat
1432 // this should not be set if the `collapse` key is removed from nbformat
1435 this.set_dirty(true);
1433 this.set_dirty(true);
1436 };
1434 };
1437
1435
1438 /**
1436 /**
1439 * Clear the selected CodeCell's output area.
1437 * Clear the selected CodeCell's output area.
1440 *
1438 *
1441 * @method clear_output
1439 * @method clear_output
1442 * @param {Number} index A cell's numeric index
1440 * @param {Number} index A cell's numeric index
1443 */
1441 */
1444 Notebook.prototype.clear_output = function (index) {
1442 Notebook.prototype.clear_output = function (index) {
1445 var i = this.index_or_selected(index);
1443 var i = this.index_or_selected(index);
1446 var cell = this.get_cell(i);
1444 var cell = this.get_cell(i);
1447 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1445 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1448 cell.clear_output();
1446 cell.clear_output();
1449 this.set_dirty(true);
1447 this.set_dirty(true);
1450 }
1448 }
1451 };
1449 };
1452
1450
1453 /**
1451 /**
1454 * Clear each code cell's output area.
1452 * Clear each code cell's output area.
1455 *
1453 *
1456 * @method clear_all_output
1454 * @method clear_all_output
1457 */
1455 */
1458 Notebook.prototype.clear_all_output = function () {
1456 Notebook.prototype.clear_all_output = function () {
1459 this.get_cells().map(function (cell, i) {
1457 this.get_cells().map(function (cell, i) {
1460 if (cell instanceof codecell.CodeCell) {
1458 if (cell instanceof codecell.CodeCell) {
1461 cell.clear_output();
1459 cell.clear_output();
1462 }
1460 }
1463 });
1461 });
1464 this.set_dirty(true);
1462 this.set_dirty(true);
1465 };
1463 };
1466
1464
1467 /**
1465 /**
1468 * Scroll the selected CodeCell's output area.
1466 * Scroll the selected CodeCell's output area.
1469 *
1467 *
1470 * @method scroll_output
1468 * @method scroll_output
1471 * @param {Number} index A cell's numeric index
1469 * @param {Number} index A cell's numeric index
1472 */
1470 */
1473 Notebook.prototype.scroll_output = function (index) {
1471 Notebook.prototype.scroll_output = function (index) {
1474 var i = this.index_or_selected(index);
1472 var i = this.index_or_selected(index);
1475 var cell = this.get_cell(i);
1473 var cell = this.get_cell(i);
1476 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1474 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1477 cell.scroll_output();
1475 cell.scroll_output();
1478 this.set_dirty(true);
1476 this.set_dirty(true);
1479 }
1477 }
1480 };
1478 };
1481
1479
1482 /**
1480 /**
1483 * Expand each code cell's output area, and add a scrollbar for long output.
1481 * Expand each code cell's output area, and add a scrollbar for long output.
1484 *
1482 *
1485 * @method scroll_all_output
1483 * @method scroll_all_output
1486 */
1484 */
1487 Notebook.prototype.scroll_all_output = function () {
1485 Notebook.prototype.scroll_all_output = function () {
1488 this.get_cells().map(function (cell, i) {
1486 this.get_cells().map(function (cell, i) {
1489 if (cell instanceof codecell.CodeCell) {
1487 if (cell instanceof codecell.CodeCell) {
1490 cell.scroll_output();
1488 cell.scroll_output();
1491 }
1489 }
1492 });
1490 });
1493 // this should not be set if the `collapse` key is removed from nbformat
1491 // this should not be set if the `collapse` key is removed from nbformat
1494 this.set_dirty(true);
1492 this.set_dirty(true);
1495 };
1493 };
1496
1494
1497 /** Toggle whether a cell's output is collapsed or expanded.
1495 /** Toggle whether a cell's output is collapsed or expanded.
1498 *
1496 *
1499 * @method toggle_output
1497 * @method toggle_output
1500 * @param {Number} index A cell's numeric index
1498 * @param {Number} index A cell's numeric index
1501 */
1499 */
1502 Notebook.prototype.toggle_output = function (index) {
1500 Notebook.prototype.toggle_output = function (index) {
1503 var i = this.index_or_selected(index);
1501 var i = this.index_or_selected(index);
1504 var cell = this.get_cell(i);
1502 var cell = this.get_cell(i);
1505 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1503 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1506 cell.toggle_output();
1504 cell.toggle_output();
1507 this.set_dirty(true);
1505 this.set_dirty(true);
1508 }
1506 }
1509 };
1507 };
1510
1508
1511 /**
1509 /**
1512 * Hide/show the output of all cells.
1510 * Hide/show the output of all cells.
1513 *
1511 *
1514 * @method toggle_all_output
1512 * @method toggle_all_output
1515 */
1513 */
1516 Notebook.prototype.toggle_all_output = function () {
1514 Notebook.prototype.toggle_all_output = function () {
1517 this.get_cells().map(function (cell, i) {
1515 this.get_cells().map(function (cell, i) {
1518 if (cell instanceof codecell.CodeCell) {
1516 if (cell instanceof codecell.CodeCell) {
1519 cell.toggle_output();
1517 cell.toggle_output();
1520 }
1518 }
1521 });
1519 });
1522 // this should not be set if the `collapse` key is removed from nbformat
1520 // this should not be set if the `collapse` key is removed from nbformat
1523 this.set_dirty(true);
1521 this.set_dirty(true);
1524 };
1522 };
1525
1523
1526 /**
1524 /**
1527 * Toggle a scrollbar for long cell outputs.
1525 * Toggle a scrollbar for long cell outputs.
1528 *
1526 *
1529 * @method toggle_output_scroll
1527 * @method toggle_output_scroll
1530 * @param {Number} index A cell's numeric index
1528 * @param {Number} index A cell's numeric index
1531 */
1529 */
1532 Notebook.prototype.toggle_output_scroll = function (index) {
1530 Notebook.prototype.toggle_output_scroll = function (index) {
1533 var i = this.index_or_selected(index);
1531 var i = this.index_or_selected(index);
1534 var cell = this.get_cell(i);
1532 var cell = this.get_cell(i);
1535 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1533 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1536 cell.toggle_output_scroll();
1534 cell.toggle_output_scroll();
1537 this.set_dirty(true);
1535 this.set_dirty(true);
1538 }
1536 }
1539 };
1537 };
1540
1538
1541 /**
1539 /**
1542 * Toggle the scrolling of long output on all cells.
1540 * Toggle the scrolling of long output on all cells.
1543 *
1541 *
1544 * @method toggle_all_output_scrolling
1542 * @method toggle_all_output_scrolling
1545 */
1543 */
1546 Notebook.prototype.toggle_all_output_scroll = function () {
1544 Notebook.prototype.toggle_all_output_scroll = function () {
1547 this.get_cells().map(function (cell, i) {
1545 this.get_cells().map(function (cell, i) {
1548 if (cell instanceof codecell.CodeCell) {
1546 if (cell instanceof codecell.CodeCell) {
1549 cell.toggle_output_scroll();
1547 cell.toggle_output_scroll();
1550 }
1548 }
1551 });
1549 });
1552 // this should not be set if the `collapse` key is removed from nbformat
1550 // this should not be set if the `collapse` key is removed from nbformat
1553 this.set_dirty(true);
1551 this.set_dirty(true);
1554 };
1552 };
1555
1553
1556 // Other cell functions: line numbers, ...
1554 // Other cell functions: line numbers, ...
1557
1555
1558 /**
1556 /**
1559 * Toggle line numbers in the selected cell's input area.
1557 * Toggle line numbers in the selected cell's input area.
1560 *
1558 *
1561 * @method cell_toggle_line_numbers
1559 * @method cell_toggle_line_numbers
1562 */
1560 */
1563 Notebook.prototype.cell_toggle_line_numbers = function() {
1561 Notebook.prototype.cell_toggle_line_numbers = function() {
1564 this.get_selected_cell().toggle_line_numbers();
1562 this.get_selected_cell().toggle_line_numbers();
1565 };
1563 };
1566
1564
1567 /**
1565 /**
1568 * Set the codemirror mode for all code cells, including the default for
1566 * Set the codemirror mode for all code cells, including the default for
1569 * new code cells.
1567 * new code cells.
1570 *
1568 *
1571 * @method set_codemirror_mode
1569 * @method set_codemirror_mode
1572 */
1570 */
1573 Notebook.prototype.set_codemirror_mode = function(newmode){
1571 Notebook.prototype.set_codemirror_mode = function(newmode){
1574 if (newmode === this.codemirror_mode) {
1572 if (newmode === this.codemirror_mode) {
1575 return;
1573 return;
1576 }
1574 }
1577 this.codemirror_mode = newmode;
1575 this.codemirror_mode = newmode;
1578 codecell.CodeCell.options_default.cm_config.mode = newmode;
1576 codecell.CodeCell.options_default.cm_config.mode = newmode;
1579 var modename = newmode.mode || newmode.name || newmode;
1577 var modename = newmode.mode || newmode.name || newmode;
1580
1578
1581 var that = this;
1579 var that = this;
1582 utils.requireCodeMirrorMode(modename, function () {
1580 utils.requireCodeMirrorMode(modename, function () {
1583 that.get_cells().map(function(cell, i) {
1581 that.get_cells().map(function(cell, i) {
1584 if (cell.cell_type === 'code'){
1582 if (cell.cell_type === 'code'){
1585 cell.code_mirror.setOption('mode', newmode);
1583 cell.code_mirror.setOption('mode', newmode);
1586 // This is currently redundant, because cm_config ends up as
1584 // This is currently redundant, because cm_config ends up as
1587 // codemirror's own .options object, but I don't want to
1585 // codemirror's own .options object, but I don't want to
1588 // rely on that.
1586 // rely on that.
1589 cell.cm_config.mode = newmode;
1587 cell.cm_config.mode = newmode;
1590 }
1588 }
1591 });
1589 });
1592 });
1590 });
1593 };
1591 };
1594
1592
1595 // Session related things
1593 // Session related things
1596
1594
1597 /**
1595 /**
1598 * Start a new session and set it on each code cell.
1596 * Start a new session and set it on each code cell.
1599 *
1597 *
1600 * @method start_session
1598 * @method start_session
1601 */
1599 */
1602 Notebook.prototype.start_session = function (kernel_name) {
1600 Notebook.prototype.start_session = function (kernel_name) {
1603 if (this._session_starting) {
1601 if (this._session_starting) {
1604 throw new session.SessionAlreadyStarting();
1602 throw new session.SessionAlreadyStarting();
1605 }
1603 }
1606 this._session_starting = true;
1604 this._session_starting = true;
1607
1605
1608 var options = {
1606 var options = {
1609 base_url: this.base_url,
1607 base_url: this.base_url,
1610 ws_url: this.ws_url,
1608 ws_url: this.ws_url,
1611 notebook_path: this.notebook_path,
1609 notebook_path: this.notebook_path,
1612 notebook_name: this.notebook_name,
1610 notebook_name: this.notebook_name,
1613 kernel_name: kernel_name,
1611 kernel_name: kernel_name,
1614 notebook: this
1612 notebook: this
1615 };
1613 };
1616
1614
1617 var success = $.proxy(this._session_started, this);
1615 var success = $.proxy(this._session_started, this);
1618 var failure = $.proxy(this._session_start_failed, this);
1616 var failure = $.proxy(this._session_start_failed, this);
1619
1617
1620 if (this.session !== null) {
1618 if (this.session !== null) {
1621 this.session.restart(options, success, failure);
1619 this.session.restart(options, success, failure);
1622 } else {
1620 } else {
1623 this.session = new session.Session(options);
1621 this.session = new session.Session(options);
1624 this.session.start(success, failure);
1622 this.session.start(success, failure);
1625 }
1623 }
1626 };
1624 };
1627
1625
1628
1626
1629 /**
1627 /**
1630 * Once a session is started, link the code cells to the kernel and pass the
1628 * Once a session is started, link the code cells to the kernel and pass the
1631 * comm manager to the widget manager
1629 * comm manager to the widget manager
1632 *
1630 *
1633 */
1631 */
1634 Notebook.prototype._session_started = function (){
1632 Notebook.prototype._session_started = function (){
1635 this._session_starting = false;
1633 this._session_starting = false;
1636 this.kernel = this.session.kernel;
1634 this.kernel = this.session.kernel;
1637 var ncells = this.ncells();
1635 var ncells = this.ncells();
1638 for (var i=0; i<ncells; i++) {
1636 for (var i=0; i<ncells; i++) {
1639 var cell = this.get_cell(i);
1637 var cell = this.get_cell(i);
1640 if (cell instanceof codecell.CodeCell) {
1638 if (cell instanceof codecell.CodeCell) {
1641 cell.set_kernel(this.session.kernel);
1639 cell.set_kernel(this.session.kernel);
1642 }
1640 }
1643 }
1641 }
1644 };
1642 };
1645 Notebook.prototype._session_start_failed = function (jqxhr, status, error){
1643 Notebook.prototype._session_start_failed = function (jqxhr, status, error){
1646 this._session_starting = false;
1644 this._session_starting = false;
1647 utils.log_ajax_error(jqxhr, status, error);
1645 utils.log_ajax_error(jqxhr, status, error);
1648 };
1646 };
1649
1647
1650 /**
1648 /**
1651 * Prompt the user to restart the IPython kernel.
1649 * Prompt the user to restart the IPython kernel.
1652 *
1650 *
1653 * @method restart_kernel
1651 * @method restart_kernel
1654 */
1652 */
1655 Notebook.prototype.restart_kernel = function () {
1653 Notebook.prototype.restart_kernel = function () {
1656 var that = this;
1654 var that = this;
1657 dialog.modal({
1655 dialog.modal({
1658 notebook: this,
1656 notebook: this,
1659 keyboard_manager: this.keyboard_manager,
1657 keyboard_manager: this.keyboard_manager,
1660 title : "Restart kernel or continue running?",
1658 title : "Restart kernel or continue running?",
1661 body : $("<p/>").text(
1659 body : $("<p/>").text(
1662 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1660 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1663 ),
1661 ),
1664 buttons : {
1662 buttons : {
1665 "Continue running" : {},
1663 "Continue running" : {},
1666 "Restart" : {
1664 "Restart" : {
1667 "class" : "btn-danger",
1665 "class" : "btn-danger",
1668 "click" : function() {
1666 "click" : function() {
1669 that.kernel.restart();
1667 that.kernel.restart();
1670 }
1668 }
1671 }
1669 }
1672 }
1670 }
1673 });
1671 });
1674 };
1672 };
1675
1673
1676 /**
1674 /**
1677 * Execute or render cell outputs and go into command mode.
1675 * Execute or render cell outputs and go into command mode.
1678 *
1676 *
1679 * @method execute_cell
1677 * @method execute_cell
1680 */
1678 */
1681 Notebook.prototype.execute_cell = function () {
1679 Notebook.prototype.execute_cell = function () {
1682 // mode = shift, ctrl, alt
1680 // mode = shift, ctrl, alt
1683 var cell = this.get_selected_cell();
1681 var cell = this.get_selected_cell();
1684
1682
1685 cell.execute();
1683 cell.execute();
1686 this.command_mode();
1684 this.command_mode();
1687 this.set_dirty(true);
1685 this.set_dirty(true);
1688 };
1686 };
1689
1687
1690 /**
1688 /**
1691 * Execute or render cell outputs and insert a new cell below.
1689 * Execute or render cell outputs and insert a new cell below.
1692 *
1690 *
1693 * @method execute_cell_and_insert_below
1691 * @method execute_cell_and_insert_below
1694 */
1692 */
1695 Notebook.prototype.execute_cell_and_insert_below = function () {
1693 Notebook.prototype.execute_cell_and_insert_below = function () {
1696 var cell = this.get_selected_cell();
1694 var cell = this.get_selected_cell();
1697 var cell_index = this.find_cell_index(cell);
1695 var cell_index = this.find_cell_index(cell);
1698
1696
1699 cell.execute();
1697 cell.execute();
1700
1698
1701 // If we are at the end always insert a new cell and return
1699 // If we are at the end always insert a new cell and return
1702 if (cell_index === (this.ncells()-1)) {
1700 if (cell_index === (this.ncells()-1)) {
1703 this.command_mode();
1701 this.command_mode();
1704 this.insert_cell_below();
1702 this.insert_cell_below();
1705 this.select(cell_index+1);
1703 this.select(cell_index+1);
1706 this.edit_mode();
1704 this.edit_mode();
1707 this.scroll_to_bottom();
1705 this.scroll_to_bottom();
1708 this.set_dirty(true);
1706 this.set_dirty(true);
1709 return;
1707 return;
1710 }
1708 }
1711
1709
1712 this.command_mode();
1710 this.command_mode();
1713 this.insert_cell_below();
1711 this.insert_cell_below();
1714 this.select(cell_index+1);
1712 this.select(cell_index+1);
1715 this.edit_mode();
1713 this.edit_mode();
1716 this.set_dirty(true);
1714 this.set_dirty(true);
1717 };
1715 };
1718
1716
1719 /**
1717 /**
1720 * Execute or render cell outputs and select the next cell.
1718 * Execute or render cell outputs and select the next cell.
1721 *
1719 *
1722 * @method execute_cell_and_select_below
1720 * @method execute_cell_and_select_below
1723 */
1721 */
1724 Notebook.prototype.execute_cell_and_select_below = function () {
1722 Notebook.prototype.execute_cell_and_select_below = function () {
1725
1723
1726 var cell = this.get_selected_cell();
1724 var cell = this.get_selected_cell();
1727 var cell_index = this.find_cell_index(cell);
1725 var cell_index = this.find_cell_index(cell);
1728
1726
1729 cell.execute();
1727 cell.execute();
1730
1728
1731 // If we are at the end always insert a new cell and return
1729 // If we are at the end always insert a new cell and return
1732 if (cell_index === (this.ncells()-1)) {
1730 if (cell_index === (this.ncells()-1)) {
1733 this.command_mode();
1731 this.command_mode();
1734 this.insert_cell_below();
1732 this.insert_cell_below();
1735 this.select(cell_index+1);
1733 this.select(cell_index+1);
1736 this.edit_mode();
1734 this.edit_mode();
1737 this.scroll_to_bottom();
1735 this.scroll_to_bottom();
1738 this.set_dirty(true);
1736 this.set_dirty(true);
1739 return;
1737 return;
1740 }
1738 }
1741
1739
1742 this.command_mode();
1740 this.command_mode();
1743 this.select(cell_index+1);
1741 this.select(cell_index+1);
1744 this.focus_cell();
1742 this.focus_cell();
1745 this.set_dirty(true);
1743 this.set_dirty(true);
1746 };
1744 };
1747
1745
1748 /**
1746 /**
1749 * Execute all cells below the selected cell.
1747 * Execute all cells below the selected cell.
1750 *
1748 *
1751 * @method execute_cells_below
1749 * @method execute_cells_below
1752 */
1750 */
1753 Notebook.prototype.execute_cells_below = function () {
1751 Notebook.prototype.execute_cells_below = function () {
1754 this.execute_cell_range(this.get_selected_index(), this.ncells());
1752 this.execute_cell_range(this.get_selected_index(), this.ncells());
1755 this.scroll_to_bottom();
1753 this.scroll_to_bottom();
1756 };
1754 };
1757
1755
1758 /**
1756 /**
1759 * Execute all cells above the selected cell.
1757 * Execute all cells above the selected cell.
1760 *
1758 *
1761 * @method execute_cells_above
1759 * @method execute_cells_above
1762 */
1760 */
1763 Notebook.prototype.execute_cells_above = function () {
1761 Notebook.prototype.execute_cells_above = function () {
1764 this.execute_cell_range(0, this.get_selected_index());
1762 this.execute_cell_range(0, this.get_selected_index());
1765 };
1763 };
1766
1764
1767 /**
1765 /**
1768 * Execute all cells.
1766 * Execute all cells.
1769 *
1767 *
1770 * @method execute_all_cells
1768 * @method execute_all_cells
1771 */
1769 */
1772 Notebook.prototype.execute_all_cells = function () {
1770 Notebook.prototype.execute_all_cells = function () {
1773 this.execute_cell_range(0, this.ncells());
1771 this.execute_cell_range(0, this.ncells());
1774 this.scroll_to_bottom();
1772 this.scroll_to_bottom();
1775 };
1773 };
1776
1774
1777 /**
1775 /**
1778 * Execute a contiguous range of cells.
1776 * Execute a contiguous range of cells.
1779 *
1777 *
1780 * @method execute_cell_range
1778 * @method execute_cell_range
1781 * @param {Number} start Index of the first cell to execute (inclusive)
1779 * @param {Number} start Index of the first cell to execute (inclusive)
1782 * @param {Number} end Index of the last cell to execute (exclusive)
1780 * @param {Number} end Index of the last cell to execute (exclusive)
1783 */
1781 */
1784 Notebook.prototype.execute_cell_range = function (start, end) {
1782 Notebook.prototype.execute_cell_range = function (start, end) {
1785 this.command_mode();
1783 this.command_mode();
1786 for (var i=start; i<end; i++) {
1784 for (var i=start; i<end; i++) {
1787 this.select(i);
1785 this.select(i);
1788 this.execute_cell();
1786 this.execute_cell();
1789 }
1787 }
1790 };
1788 };
1791
1789
1792 // Persistance and loading
1790 // Persistance and loading
1793
1791
1794 /**
1792 /**
1795 * Getter method for this notebook's name.
1793 * Getter method for this notebook's name.
1796 *
1794 *
1797 * @method get_notebook_name
1795 * @method get_notebook_name
1798 * @return {String} This notebook's name (excluding file extension)
1796 * @return {String} This notebook's name (excluding file extension)
1799 */
1797 */
1800 Notebook.prototype.get_notebook_name = function () {
1798 Notebook.prototype.get_notebook_name = function () {
1801 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1799 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1802 return nbname;
1800 return nbname;
1803 };
1801 };
1804
1802
1805 /**
1803 /**
1806 * Setter method for this notebook's name.
1804 * Setter method for this notebook's name.
1807 *
1805 *
1808 * @method set_notebook_name
1806 * @method set_notebook_name
1809 * @param {String} name A new name for this notebook
1807 * @param {String} name A new name for this notebook
1810 */
1808 */
1811 Notebook.prototype.set_notebook_name = function (name) {
1809 Notebook.prototype.set_notebook_name = function (name) {
1812 var parent = utils.url_path_split(this.notebook_path)[0];
1810 var parent = utils.url_path_split(this.notebook_path)[0];
1813 this.notebook_name = name;
1811 this.notebook_name = name;
1814 this.notebook_path = utils.url_path_join(parent, name);
1812 this.notebook_path = utils.url_path_join(parent, name);
1815 };
1813 };
1816
1814
1817 /**
1815 /**
1818 * Check that a notebook's name is valid.
1816 * Check that a notebook's name is valid.
1819 *
1817 *
1820 * @method test_notebook_name
1818 * @method test_notebook_name
1821 * @param {String} nbname A name for this notebook
1819 * @param {String} nbname A name for this notebook
1822 * @return {Boolean} True if the name is valid, false if invalid
1820 * @return {Boolean} True if the name is valid, false if invalid
1823 */
1821 */
1824 Notebook.prototype.test_notebook_name = function (nbname) {
1822 Notebook.prototype.test_notebook_name = function (nbname) {
1825 nbname = nbname || '';
1823 nbname = nbname || '';
1826 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1824 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1827 return true;
1825 return true;
1828 } else {
1826 } else {
1829 return false;
1827 return false;
1830 }
1828 }
1831 };
1829 };
1832
1830
1833 /**
1831 /**
1834 * Load a notebook from JSON (.ipynb).
1832 * Load a notebook from JSON (.ipynb).
1835 *
1833 *
1836 * @method fromJSON
1834 * @method fromJSON
1837 * @param {Object} data JSON representation of a notebook
1835 * @param {Object} data JSON representation of a notebook
1838 */
1836 */
1839 Notebook.prototype.fromJSON = function (data) {
1837 Notebook.prototype.fromJSON = function (data) {
1840
1838
1841 var content = data.content;
1839 var content = data.content;
1842 var ncells = this.ncells();
1840 var ncells = this.ncells();
1843 var i;
1841 var i;
1844 for (i=0; i<ncells; i++) {
1842 for (i=0; i<ncells; i++) {
1845 // Always delete cell 0 as they get renumbered as they are deleted.
1843 // Always delete cell 0 as they get renumbered as they are deleted.
1846 this.delete_cell(0);
1844 this.delete_cell(0);
1847 }
1845 }
1848 // Save the metadata and name.
1846 // Save the metadata and name.
1849 this.metadata = content.metadata;
1847 this.metadata = content.metadata;
1850 this.notebook_name = data.name;
1848 this.notebook_name = data.name;
1851 this.notebook_path = data.path;
1849 this.notebook_path = data.path;
1852 var trusted = true;
1850 var trusted = true;
1853
1851
1854 // Trigger an event changing the kernel spec - this will set the default
1852 // Trigger an event changing the kernel spec - this will set the default
1855 // codemirror mode
1853 // codemirror mode
1856 if (this.metadata.kernelspec !== undefined) {
1854 if (this.metadata.kernelspec !== undefined) {
1857 this.events.trigger('spec_changed.Kernel', this.metadata.kernelspec);
1855 this.events.trigger('spec_changed.Kernel', this.metadata.kernelspec);
1858 }
1856 }
1859
1857
1860 // Set the codemirror mode from language_info metadata
1858 // Set the codemirror mode from language_info metadata
1861 if (this.metadata.language_info !== undefined) {
1859 if (this.metadata.language_info !== undefined) {
1862 var langinfo = this.metadata.language_info;
1860 var langinfo = this.metadata.language_info;
1863 // Mode 'null' should be plain, unhighlighted text.
1861 // Mode 'null' should be plain, unhighlighted text.
1864 var cm_mode = langinfo.codemirror_mode || langinfo.language || 'null';
1862 var cm_mode = langinfo.codemirror_mode || langinfo.name || 'null';
1865 this.set_codemirror_mode(cm_mode);
1863 this.set_codemirror_mode(cm_mode);
1866 }
1864 }
1867
1865
1868 var new_cells = content.cells;
1866 var new_cells = content.cells;
1869 ncells = new_cells.length;
1867 ncells = new_cells.length;
1870 var cell_data = null;
1868 var cell_data = null;
1871 var new_cell = null;
1869 var new_cell = null;
1872 for (i=0; i<ncells; i++) {
1870 for (i=0; i<ncells; i++) {
1873 cell_data = new_cells[i];
1871 cell_data = new_cells[i];
1874 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1872 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1875 new_cell.fromJSON(cell_data);
1873 new_cell.fromJSON(cell_data);
1876 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1874 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1877 trusted = false;
1875 trusted = false;
1878 }
1876 }
1879 }
1877 }
1880 if (trusted !== this.trusted) {
1878 if (trusted !== this.trusted) {
1881 this.trusted = trusted;
1879 this.trusted = trusted;
1882 this.events.trigger("trust_changed.Notebook", trusted);
1880 this.events.trigger("trust_changed.Notebook", trusted);
1883 }
1881 }
1884 };
1882 };
1885
1883
1886 /**
1884 /**
1887 * Dump this notebook into a JSON-friendly object.
1885 * Dump this notebook into a JSON-friendly object.
1888 *
1886 *
1889 * @method toJSON
1887 * @method toJSON
1890 * @return {Object} A JSON-friendly representation of this notebook.
1888 * @return {Object} A JSON-friendly representation of this notebook.
1891 */
1889 */
1892 Notebook.prototype.toJSON = function () {
1890 Notebook.prototype.toJSON = function () {
1893 // remove the conversion indicator, which only belongs in-memory
1891 // remove the conversion indicator, which only belongs in-memory
1894 delete this.metadata.orig_nbformat;
1892 delete this.metadata.orig_nbformat;
1895 delete this.metadata.orig_nbformat_minor;
1893 delete this.metadata.orig_nbformat_minor;
1896
1894
1897 var cells = this.get_cells();
1895 var cells = this.get_cells();
1898 var ncells = cells.length;
1896 var ncells = cells.length;
1899 var cell_array = new Array(ncells);
1897 var cell_array = new Array(ncells);
1900 var trusted = true;
1898 var trusted = true;
1901 for (var i=0; i<ncells; i++) {
1899 for (var i=0; i<ncells; i++) {
1902 var cell = cells[i];
1900 var cell = cells[i];
1903 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1901 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1904 trusted = false;
1902 trusted = false;
1905 }
1903 }
1906 cell_array[i] = cell.toJSON();
1904 cell_array[i] = cell.toJSON();
1907 }
1905 }
1908 var data = {
1906 var data = {
1909 cells: cell_array,
1907 cells: cell_array,
1910 metadata: this.metadata,
1908 metadata: this.metadata,
1911 nbformat: this.nbformat,
1909 nbformat: this.nbformat,
1912 nbformat_minor: this.nbformat_minor
1910 nbformat_minor: this.nbformat_minor
1913 };
1911 };
1914 if (trusted != this.trusted) {
1912 if (trusted != this.trusted) {
1915 this.trusted = trusted;
1913 this.trusted = trusted;
1916 this.events.trigger("trust_changed.Notebook", trusted);
1914 this.events.trigger("trust_changed.Notebook", trusted);
1917 }
1915 }
1918 return data;
1916 return data;
1919 };
1917 };
1920
1918
1921 /**
1919 /**
1922 * Start an autosave timer, for periodically saving the notebook.
1920 * Start an autosave timer, for periodically saving the notebook.
1923 *
1921 *
1924 * @method set_autosave_interval
1922 * @method set_autosave_interval
1925 * @param {Integer} interval the autosave interval in milliseconds
1923 * @param {Integer} interval the autosave interval in milliseconds
1926 */
1924 */
1927 Notebook.prototype.set_autosave_interval = function (interval) {
1925 Notebook.prototype.set_autosave_interval = function (interval) {
1928 var that = this;
1926 var that = this;
1929 // clear previous interval, so we don't get simultaneous timers
1927 // clear previous interval, so we don't get simultaneous timers
1930 if (this.autosave_timer) {
1928 if (this.autosave_timer) {
1931 clearInterval(this.autosave_timer);
1929 clearInterval(this.autosave_timer);
1932 }
1930 }
1933 if (!this.writable) {
1931 if (!this.writable) {
1934 // disable autosave if not writable
1932 // disable autosave if not writable
1935 interval = 0;
1933 interval = 0;
1936 }
1934 }
1937
1935
1938 this.autosave_interval = this.minimum_autosave_interval = interval;
1936 this.autosave_interval = this.minimum_autosave_interval = interval;
1939 if (interval) {
1937 if (interval) {
1940 this.autosave_timer = setInterval(function() {
1938 this.autosave_timer = setInterval(function() {
1941 if (that.dirty) {
1939 if (that.dirty) {
1942 that.save_notebook();
1940 that.save_notebook();
1943 }
1941 }
1944 }, interval);
1942 }, interval);
1945 this.events.trigger("autosave_enabled.Notebook", interval);
1943 this.events.trigger("autosave_enabled.Notebook", interval);
1946 } else {
1944 } else {
1947 this.autosave_timer = null;
1945 this.autosave_timer = null;
1948 this.events.trigger("autosave_disabled.Notebook");
1946 this.events.trigger("autosave_disabled.Notebook");
1949 }
1947 }
1950 };
1948 };
1951
1949
1952 /**
1950 /**
1953 * Save this notebook on the server. This becomes a notebook instance's
1951 * Save this notebook on the server. This becomes a notebook instance's
1954 * .save_notebook method *after* the entire notebook has been loaded.
1952 * .save_notebook method *after* the entire notebook has been loaded.
1955 *
1953 *
1956 * @method save_notebook
1954 * @method save_notebook
1957 */
1955 */
1958 Notebook.prototype.save_notebook = function () {
1956 Notebook.prototype.save_notebook = function () {
1959 if (!this._fully_loaded) {
1957 if (!this._fully_loaded) {
1960 this.events.trigger('notebook_save_failed.Notebook',
1958 this.events.trigger('notebook_save_failed.Notebook',
1961 new Error("Load failed, save is disabled")
1959 new Error("Load failed, save is disabled")
1962 );
1960 );
1963 return;
1961 return;
1964 } else if (!this.writable) {
1962 } else if (!this.writable) {
1965 this.events.trigger('notebook_save_failed.Notebook',
1963 this.events.trigger('notebook_save_failed.Notebook',
1966 new Error("Notebook is read-only")
1964 new Error("Notebook is read-only")
1967 );
1965 );
1968 return;
1966 return;
1969 }
1967 }
1970
1968
1971 // Create a JSON model to be sent to the server.
1969 // Create a JSON model to be sent to the server.
1972 var model = {
1970 var model = {
1973 type : "notebook",
1971 type : "notebook",
1974 content : this.toJSON()
1972 content : this.toJSON()
1975 };
1973 };
1976 // time the ajax call for autosave tuning purposes.
1974 // time the ajax call for autosave tuning purposes.
1977 var start = new Date().getTime();
1975 var start = new Date().getTime();
1978
1976
1979 var that = this;
1977 var that = this;
1980 return this.contents.save(this.notebook_path, model).then(
1978 return this.contents.save(this.notebook_path, model).then(
1981 $.proxy(this.save_notebook_success, this, start),
1979 $.proxy(this.save_notebook_success, this, start),
1982 function (error) {
1980 function (error) {
1983 that.events.trigger('notebook_save_failed.Notebook', error);
1981 that.events.trigger('notebook_save_failed.Notebook', error);
1984 }
1982 }
1985 );
1983 );
1986 };
1984 };
1987
1985
1988 /**
1986 /**
1989 * Success callback for saving a notebook.
1987 * Success callback for saving a notebook.
1990 *
1988 *
1991 * @method save_notebook_success
1989 * @method save_notebook_success
1992 * @param {Integer} start Time when the save request start
1990 * @param {Integer} start Time when the save request start
1993 * @param {Object} data JSON representation of a notebook
1991 * @param {Object} data JSON representation of a notebook
1994 */
1992 */
1995 Notebook.prototype.save_notebook_success = function (start, data) {
1993 Notebook.prototype.save_notebook_success = function (start, data) {
1996 this.set_dirty(false);
1994 this.set_dirty(false);
1997 if (data.message) {
1995 if (data.message) {
1998 // save succeeded, but validation failed.
1996 // save succeeded, but validation failed.
1999 var body = $("<div>");
1997 var body = $("<div>");
2000 var title = "Notebook validation failed";
1998 var title = "Notebook validation failed";
2001
1999
2002 body.append($("<p>").text(
2000 body.append($("<p>").text(
2003 "The save operation succeeded," +
2001 "The save operation succeeded," +
2004 " but the notebook does not appear to be valid." +
2002 " but the notebook does not appear to be valid." +
2005 " The validation error was:"
2003 " The validation error was:"
2006 )).append($("<div>").addClass("validation-error").append(
2004 )).append($("<div>").addClass("validation-error").append(
2007 $("<pre>").text(data.message)
2005 $("<pre>").text(data.message)
2008 ));
2006 ));
2009 dialog.modal({
2007 dialog.modal({
2010 notebook: this,
2008 notebook: this,
2011 keyboard_manager: this.keyboard_manager,
2009 keyboard_manager: this.keyboard_manager,
2012 title: title,
2010 title: title,
2013 body: body,
2011 body: body,
2014 buttons : {
2012 buttons : {
2015 OK : {
2013 OK : {
2016 "class" : "btn-primary"
2014 "class" : "btn-primary"
2017 }
2015 }
2018 }
2016 }
2019 });
2017 });
2020 }
2018 }
2021 this.events.trigger('notebook_saved.Notebook');
2019 this.events.trigger('notebook_saved.Notebook');
2022 this._update_autosave_interval(start);
2020 this._update_autosave_interval(start);
2023 if (this._checkpoint_after_save) {
2021 if (this._checkpoint_after_save) {
2024 this.create_checkpoint();
2022 this.create_checkpoint();
2025 this._checkpoint_after_save = false;
2023 this._checkpoint_after_save = false;
2026 }
2024 }
2027 };
2025 };
2028
2026
2029 /**
2027 /**
2030 * update the autosave interval based on how long the last save took
2028 * update the autosave interval based on how long the last save took
2031 *
2029 *
2032 * @method _update_autosave_interval
2030 * @method _update_autosave_interval
2033 * @param {Integer} timestamp when the save request started
2031 * @param {Integer} timestamp when the save request started
2034 */
2032 */
2035 Notebook.prototype._update_autosave_interval = function (start) {
2033 Notebook.prototype._update_autosave_interval = function (start) {
2036 var duration = (new Date().getTime() - start);
2034 var duration = (new Date().getTime() - start);
2037 if (this.autosave_interval) {
2035 if (this.autosave_interval) {
2038 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
2036 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
2039 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
2037 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
2040 // round to 10 seconds, otherwise we will be setting a new interval too often
2038 // round to 10 seconds, otherwise we will be setting a new interval too often
2041 interval = 10000 * Math.round(interval / 10000);
2039 interval = 10000 * Math.round(interval / 10000);
2042 // set new interval, if it's changed
2040 // set new interval, if it's changed
2043 if (interval != this.autosave_interval) {
2041 if (interval != this.autosave_interval) {
2044 this.set_autosave_interval(interval);
2042 this.set_autosave_interval(interval);
2045 }
2043 }
2046 }
2044 }
2047 };
2045 };
2048
2046
2049 /**
2047 /**
2050 * Explicitly trust the output of this notebook.
2048 * Explicitly trust the output of this notebook.
2051 *
2049 *
2052 * @method trust_notebook
2050 * @method trust_notebook
2053 */
2051 */
2054 Notebook.prototype.trust_notebook = function () {
2052 Notebook.prototype.trust_notebook = function () {
2055 var body = $("<div>").append($("<p>")
2053 var body = $("<div>").append($("<p>")
2056 .text("A trusted IPython notebook may execute hidden malicious code ")
2054 .text("A trusted IPython notebook may execute hidden malicious code ")
2057 .append($("<strong>")
2055 .append($("<strong>")
2058 .append(
2056 .append(
2059 $("<em>").text("when you open it")
2057 $("<em>").text("when you open it")
2060 )
2058 )
2061 ).append(".").append(
2059 ).append(".").append(
2062 " Selecting trust will immediately reload this notebook in a trusted state."
2060 " Selecting trust will immediately reload this notebook in a trusted state."
2063 ).append(
2061 ).append(
2064 " For more information, see the "
2062 " For more information, see the "
2065 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
2063 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
2066 .text("IPython security documentation")
2064 .text("IPython security documentation")
2067 ).append(".")
2065 ).append(".")
2068 );
2066 );
2069
2067
2070 var nb = this;
2068 var nb = this;
2071 dialog.modal({
2069 dialog.modal({
2072 notebook: this,
2070 notebook: this,
2073 keyboard_manager: this.keyboard_manager,
2071 keyboard_manager: this.keyboard_manager,
2074 title: "Trust this notebook?",
2072 title: "Trust this notebook?",
2075 body: body,
2073 body: body,
2076
2074
2077 buttons: {
2075 buttons: {
2078 Cancel : {},
2076 Cancel : {},
2079 Trust : {
2077 Trust : {
2080 class : "btn-danger",
2078 class : "btn-danger",
2081 click : function () {
2079 click : function () {
2082 var cells = nb.get_cells();
2080 var cells = nb.get_cells();
2083 for (var i = 0; i < cells.length; i++) {
2081 for (var i = 0; i < cells.length; i++) {
2084 var cell = cells[i];
2082 var cell = cells[i];
2085 if (cell.cell_type == 'code') {
2083 if (cell.cell_type == 'code') {
2086 cell.output_area.trusted = true;
2084 cell.output_area.trusted = true;
2087 }
2085 }
2088 }
2086 }
2089 nb.events.on('notebook_saved.Notebook', function () {
2087 nb.events.on('notebook_saved.Notebook', function () {
2090 window.location.reload();
2088 window.location.reload();
2091 });
2089 });
2092 nb.save_notebook();
2090 nb.save_notebook();
2093 }
2091 }
2094 }
2092 }
2095 }
2093 }
2096 });
2094 });
2097 };
2095 };
2098
2096
2099 Notebook.prototype.copy_notebook = function () {
2097 Notebook.prototype.copy_notebook = function () {
2100 var that = this;
2098 var that = this;
2101 var base_url = this.base_url;
2099 var base_url = this.base_url;
2102 var w = window.open();
2100 var w = window.open();
2103 var parent = utils.url_path_split(this.notebook_path)[0];
2101 var parent = utils.url_path_split(this.notebook_path)[0];
2104 this.contents.copy(this.notebook_path, parent).then(
2102 this.contents.copy(this.notebook_path, parent).then(
2105 function (data) {
2103 function (data) {
2106 w.location = utils.url_join_encode(
2104 w.location = utils.url_join_encode(
2107 base_url, 'notebooks', data.path
2105 base_url, 'notebooks', data.path
2108 );
2106 );
2109 },
2107 },
2110 function(error) {
2108 function(error) {
2111 w.close();
2109 w.close();
2112 that.events.trigger('notebook_copy_failed', error);
2110 that.events.trigger('notebook_copy_failed', error);
2113 }
2111 }
2114 );
2112 );
2115 };
2113 };
2116
2114
2117 Notebook.prototype.rename = function (new_name) {
2115 Notebook.prototype.rename = function (new_name) {
2118 if (!new_name.match(/\.ipynb$/)) {
2116 if (!new_name.match(/\.ipynb$/)) {
2119 new_name = new_name + ".ipynb";
2117 new_name = new_name + ".ipynb";
2120 }
2118 }
2121
2119
2122 var that = this;
2120 var that = this;
2123 var parent = utils.url_path_split(this.notebook_path)[0];
2121 var parent = utils.url_path_split(this.notebook_path)[0];
2124 var new_path = utils.url_path_join(parent, new_name);
2122 var new_path = utils.url_path_join(parent, new_name);
2125 return this.contents.rename(this.notebook_path, new_path).then(
2123 return this.contents.rename(this.notebook_path, new_path).then(
2126 function (json) {
2124 function (json) {
2127 that.notebook_name = json.name;
2125 that.notebook_name = json.name;
2128 that.notebook_path = json.path;
2126 that.notebook_path = json.path;
2129 that.session.rename_notebook(json.path);
2127 that.session.rename_notebook(json.path);
2130 that.events.trigger('notebook_renamed.Notebook', json);
2128 that.events.trigger('notebook_renamed.Notebook', json);
2131 }
2129 }
2132 );
2130 );
2133 };
2131 };
2134
2132
2135 Notebook.prototype.delete = function () {
2133 Notebook.prototype.delete = function () {
2136 this.contents.delete(this.notebook_path);
2134 this.contents.delete(this.notebook_path);
2137 };
2135 };
2138
2136
2139 /**
2137 /**
2140 * Request a notebook's data from the server.
2138 * Request a notebook's data from the server.
2141 *
2139 *
2142 * @method load_notebook
2140 * @method load_notebook
2143 * @param {String} notebook_path A notebook to load
2141 * @param {String} notebook_path A notebook to load
2144 */
2142 */
2145 Notebook.prototype.load_notebook = function (notebook_path) {
2143 Notebook.prototype.load_notebook = function (notebook_path) {
2146 this.notebook_path = notebook_path;
2144 this.notebook_path = notebook_path;
2147 this.notebook_name = utils.url_path_split(this.notebook_path)[1];
2145 this.notebook_name = utils.url_path_split(this.notebook_path)[1];
2148 this.events.trigger('notebook_loading.Notebook');
2146 this.events.trigger('notebook_loading.Notebook');
2149 this.contents.get(notebook_path, {type: 'notebook'}).then(
2147 this.contents.get(notebook_path, {type: 'notebook'}).then(
2150 $.proxy(this.load_notebook_success, this),
2148 $.proxy(this.load_notebook_success, this),
2151 $.proxy(this.load_notebook_error, this)
2149 $.proxy(this.load_notebook_error, this)
2152 );
2150 );
2153 };
2151 };
2154
2152
2155 /**
2153 /**
2156 * Success callback for loading a notebook from the server.
2154 * Success callback for loading a notebook from the server.
2157 *
2155 *
2158 * Load notebook data from the JSON response.
2156 * Load notebook data from the JSON response.
2159 *
2157 *
2160 * @method load_notebook_success
2158 * @method load_notebook_success
2161 * @param {Object} data JSON representation of a notebook
2159 * @param {Object} data JSON representation of a notebook
2162 */
2160 */
2163 Notebook.prototype.load_notebook_success = function (data) {
2161 Notebook.prototype.load_notebook_success = function (data) {
2164 var failed, msg;
2162 var failed, msg;
2165 try {
2163 try {
2166 this.fromJSON(data);
2164 this.fromJSON(data);
2167 } catch (e) {
2165 } catch (e) {
2168 failed = e;
2166 failed = e;
2169 console.log("Notebook failed to load from JSON:", e);
2167 console.log("Notebook failed to load from JSON:", e);
2170 }
2168 }
2171 if (failed || data.message) {
2169 if (failed || data.message) {
2172 // *either* fromJSON failed or validation failed
2170 // *either* fromJSON failed or validation failed
2173 var body = $("<div>");
2171 var body = $("<div>");
2174 var title;
2172 var title;
2175 if (failed) {
2173 if (failed) {
2176 title = "Notebook failed to load";
2174 title = "Notebook failed to load";
2177 body.append($("<p>").text(
2175 body.append($("<p>").text(
2178 "The error was: "
2176 "The error was: "
2179 )).append($("<div>").addClass("js-error").text(
2177 )).append($("<div>").addClass("js-error").text(
2180 failed.toString()
2178 failed.toString()
2181 )).append($("<p>").text(
2179 )).append($("<p>").text(
2182 "See the error console for details."
2180 "See the error console for details."
2183 ));
2181 ));
2184 } else {
2182 } else {
2185 title = "Notebook validation failed";
2183 title = "Notebook validation failed";
2186 }
2184 }
2187
2185
2188 if (data.message) {
2186 if (data.message) {
2189 if (failed) {
2187 if (failed) {
2190 msg = "The notebook also failed validation:";
2188 msg = "The notebook also failed validation:";
2191 } else {
2189 } else {
2192 msg = "An invalid notebook may not function properly." +
2190 msg = "An invalid notebook may not function properly." +
2193 " The validation error was:";
2191 " The validation error was:";
2194 }
2192 }
2195 body.append($("<p>").text(
2193 body.append($("<p>").text(
2196 msg
2194 msg
2197 )).append($("<div>").addClass("validation-error").append(
2195 )).append($("<div>").addClass("validation-error").append(
2198 $("<pre>").text(data.message)
2196 $("<pre>").text(data.message)
2199 ));
2197 ));
2200 }
2198 }
2201
2199
2202 dialog.modal({
2200 dialog.modal({
2203 notebook: this,
2201 notebook: this,
2204 keyboard_manager: this.keyboard_manager,
2202 keyboard_manager: this.keyboard_manager,
2205 title: title,
2203 title: title,
2206 body: body,
2204 body: body,
2207 buttons : {
2205 buttons : {
2208 OK : {
2206 OK : {
2209 "class" : "btn-primary"
2207 "class" : "btn-primary"
2210 }
2208 }
2211 }
2209 }
2212 });
2210 });
2213 }
2211 }
2214 if (this.ncells() === 0) {
2212 if (this.ncells() === 0) {
2215 this.insert_cell_below('code');
2213 this.insert_cell_below('code');
2216 this.edit_mode(0);
2214 this.edit_mode(0);
2217 } else {
2215 } else {
2218 this.select(0);
2216 this.select(0);
2219 this.handle_command_mode(this.get_cell(0));
2217 this.handle_command_mode(this.get_cell(0));
2220 }
2218 }
2221 this.set_dirty(false);
2219 this.set_dirty(false);
2222 this.scroll_to_top();
2220 this.scroll_to_top();
2223 this.writable = data.writable || false;
2221 this.writable = data.writable || false;
2224 var nbmodel = data.content;
2222 var nbmodel = data.content;
2225 var orig_nbformat = nbmodel.metadata.orig_nbformat;
2223 var orig_nbformat = nbmodel.metadata.orig_nbformat;
2226 var orig_nbformat_minor = nbmodel.metadata.orig_nbformat_minor;
2224 var orig_nbformat_minor = nbmodel.metadata.orig_nbformat_minor;
2227 if (orig_nbformat !== undefined && nbmodel.nbformat !== orig_nbformat) {
2225 if (orig_nbformat !== undefined && nbmodel.nbformat !== orig_nbformat) {
2228 var src;
2226 var src;
2229 if (nbmodel.nbformat > orig_nbformat) {
2227 if (nbmodel.nbformat > orig_nbformat) {
2230 src = " an older notebook format ";
2228 src = " an older notebook format ";
2231 } else {
2229 } else {
2232 src = " a newer notebook format ";
2230 src = " a newer notebook format ";
2233 }
2231 }
2234
2232
2235 msg = "This notebook has been converted from" + src +
2233 msg = "This notebook has been converted from" + src +
2236 "(v"+orig_nbformat+") to the current notebook " +
2234 "(v"+orig_nbformat+") to the current notebook " +
2237 "format (v"+nbmodel.nbformat+"). The next time you save this notebook, the " +
2235 "format (v"+nbmodel.nbformat+"). The next time you save this notebook, the " +
2238 "current notebook format will be used.";
2236 "current notebook format will be used.";
2239
2237
2240 if (nbmodel.nbformat > orig_nbformat) {
2238 if (nbmodel.nbformat > orig_nbformat) {
2241 msg += " Older versions of IPython may not be able to read the new format.";
2239 msg += " Older versions of IPython may not be able to read the new format.";
2242 } else {
2240 } else {
2243 msg += " Some features of the original notebook may not be available.";
2241 msg += " Some features of the original notebook may not be available.";
2244 }
2242 }
2245 msg += " To preserve the original version, close the " +
2243 msg += " To preserve the original version, close the " +
2246 "notebook without saving it.";
2244 "notebook without saving it.";
2247 dialog.modal({
2245 dialog.modal({
2248 notebook: this,
2246 notebook: this,
2249 keyboard_manager: this.keyboard_manager,
2247 keyboard_manager: this.keyboard_manager,
2250 title : "Notebook converted",
2248 title : "Notebook converted",
2251 body : msg,
2249 body : msg,
2252 buttons : {
2250 buttons : {
2253 OK : {
2251 OK : {
2254 class : "btn-primary"
2252 class : "btn-primary"
2255 }
2253 }
2256 }
2254 }
2257 });
2255 });
2258 } else if (this.nbformat_minor < nbmodel.nbformat_minor) {
2256 } else if (this.nbformat_minor < nbmodel.nbformat_minor) {
2259 this.nbformat_minor = nbmodel.nbformat_minor;
2257 this.nbformat_minor = nbmodel.nbformat_minor;
2260 }
2258 }
2261
2259
2262 // Create the session after the notebook is completely loaded to prevent
2260 // Create the session after the notebook is completely loaded to prevent
2263 // code execution upon loading, which is a security risk.
2261 // code execution upon loading, which is a security risk.
2264 if (this.session === null) {
2262 if (this.session === null) {
2265 var kernelspec = this.metadata.kernelspec || {};
2263 var kernelspec = this.metadata.kernelspec || {};
2266 var kernel_name = kernelspec.name;
2264 var kernel_name = kernelspec.name;
2267
2265
2268 this.start_session(kernel_name);
2266 this.start_session(kernel_name);
2269 }
2267 }
2270 // load our checkpoint list
2268 // load our checkpoint list
2271 this.list_checkpoints();
2269 this.list_checkpoints();
2272
2270
2273 // load toolbar state
2271 // load toolbar state
2274 if (this.metadata.celltoolbar) {
2272 if (this.metadata.celltoolbar) {
2275 celltoolbar.CellToolbar.global_show();
2273 celltoolbar.CellToolbar.global_show();
2276 celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar);
2274 celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar);
2277 } else {
2275 } else {
2278 celltoolbar.CellToolbar.global_hide();
2276 celltoolbar.CellToolbar.global_hide();
2279 }
2277 }
2280
2278
2281 if (!this.writable) {
2279 if (!this.writable) {
2282 this.set_autosave_interval(0);
2280 this.set_autosave_interval(0);
2283 this.events.trigger('notebook_read_only.Notebook');
2281 this.events.trigger('notebook_read_only.Notebook');
2284 }
2282 }
2285
2283
2286 // now that we're fully loaded, it is safe to restore save functionality
2284 // now that we're fully loaded, it is safe to restore save functionality
2287 this._fully_loaded = true;
2285 this._fully_loaded = true;
2288 this.events.trigger('notebook_loaded.Notebook');
2286 this.events.trigger('notebook_loaded.Notebook');
2289 };
2287 };
2290
2288
2291 /**
2289 /**
2292 * Failure callback for loading a notebook from the server.
2290 * Failure callback for loading a notebook from the server.
2293 *
2291 *
2294 * @method load_notebook_error
2292 * @method load_notebook_error
2295 * @param {Error} error
2293 * @param {Error} error
2296 */
2294 */
2297 Notebook.prototype.load_notebook_error = function (error) {
2295 Notebook.prototype.load_notebook_error = function (error) {
2298 this.events.trigger('notebook_load_failed.Notebook', error);
2296 this.events.trigger('notebook_load_failed.Notebook', error);
2299 var msg;
2297 var msg;
2300 if (error.name === utils.XHR_ERROR && error.xhr.status === 500) {
2298 if (error.name === utils.XHR_ERROR && error.xhr.status === 500) {
2301 utils.log_ajax_error(error.xhr, error.xhr_status, error.xhr_error);
2299 utils.log_ajax_error(error.xhr, error.xhr_status, error.xhr_error);
2302 msg = "An unknown error occurred while loading this notebook. " +
2300 msg = "An unknown error occurred while loading this notebook. " +
2303 "This version can load notebook formats " +
2301 "This version can load notebook formats " +
2304 "v" + this.nbformat + " or earlier. See the server log for details.";
2302 "v" + this.nbformat + " or earlier. See the server log for details.";
2305 } else {
2303 } else {
2306 msg = error.message;
2304 msg = error.message;
2307 }
2305 }
2308 dialog.modal({
2306 dialog.modal({
2309 notebook: this,
2307 notebook: this,
2310 keyboard_manager: this.keyboard_manager,
2308 keyboard_manager: this.keyboard_manager,
2311 title: "Error loading notebook",
2309 title: "Error loading notebook",
2312 body : msg,
2310 body : msg,
2313 buttons : {
2311 buttons : {
2314 "OK": {}
2312 "OK": {}
2315 }
2313 }
2316 });
2314 });
2317 };
2315 };
2318
2316
2319 /********************* checkpoint-related *********************/
2317 /********************* checkpoint-related *********************/
2320
2318
2321 /**
2319 /**
2322 * Save the notebook then immediately create a checkpoint.
2320 * Save the notebook then immediately create a checkpoint.
2323 *
2321 *
2324 * @method save_checkpoint
2322 * @method save_checkpoint
2325 */
2323 */
2326 Notebook.prototype.save_checkpoint = function () {
2324 Notebook.prototype.save_checkpoint = function () {
2327 this._checkpoint_after_save = true;
2325 this._checkpoint_after_save = true;
2328 this.save_notebook();
2326 this.save_notebook();
2329 };
2327 };
2330
2328
2331 /**
2329 /**
2332 * Add a checkpoint for this notebook.
2330 * Add a checkpoint for this notebook.
2333 * for use as a callback from checkpoint creation.
2331 * for use as a callback from checkpoint creation.
2334 *
2332 *
2335 * @method add_checkpoint
2333 * @method add_checkpoint
2336 */
2334 */
2337 Notebook.prototype.add_checkpoint = function (checkpoint) {
2335 Notebook.prototype.add_checkpoint = function (checkpoint) {
2338 var found = false;
2336 var found = false;
2339 for (var i = 0; i < this.checkpoints.length; i++) {
2337 for (var i = 0; i < this.checkpoints.length; i++) {
2340 var existing = this.checkpoints[i];
2338 var existing = this.checkpoints[i];
2341 if (existing.id == checkpoint.id) {
2339 if (existing.id == checkpoint.id) {
2342 found = true;
2340 found = true;
2343 this.checkpoints[i] = checkpoint;
2341 this.checkpoints[i] = checkpoint;
2344 break;
2342 break;
2345 }
2343 }
2346 }
2344 }
2347 if (!found) {
2345 if (!found) {
2348 this.checkpoints.push(checkpoint);
2346 this.checkpoints.push(checkpoint);
2349 }
2347 }
2350 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2348 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2351 };
2349 };
2352
2350
2353 /**
2351 /**
2354 * List checkpoints for this notebook.
2352 * List checkpoints for this notebook.
2355 *
2353 *
2356 * @method list_checkpoints
2354 * @method list_checkpoints
2357 */
2355 */
2358 Notebook.prototype.list_checkpoints = function () {
2356 Notebook.prototype.list_checkpoints = function () {
2359 var that = this;
2357 var that = this;
2360 this.contents.list_checkpoints(this.notebook_path).then(
2358 this.contents.list_checkpoints(this.notebook_path).then(
2361 $.proxy(this.list_checkpoints_success, this),
2359 $.proxy(this.list_checkpoints_success, this),
2362 function(error) {
2360 function(error) {
2363 that.events.trigger('list_checkpoints_failed.Notebook', error);
2361 that.events.trigger('list_checkpoints_failed.Notebook', error);
2364 }
2362 }
2365 );
2363 );
2366 };
2364 };
2367
2365
2368 /**
2366 /**
2369 * Success callback for listing checkpoints.
2367 * Success callback for listing checkpoints.
2370 *
2368 *
2371 * @method list_checkpoint_success
2369 * @method list_checkpoint_success
2372 * @param {Object} data JSON representation of a checkpoint
2370 * @param {Object} data JSON representation of a checkpoint
2373 */
2371 */
2374 Notebook.prototype.list_checkpoints_success = function (data) {
2372 Notebook.prototype.list_checkpoints_success = function (data) {
2375 this.checkpoints = data;
2373 this.checkpoints = data;
2376 if (data.length) {
2374 if (data.length) {
2377 this.last_checkpoint = data[data.length - 1];
2375 this.last_checkpoint = data[data.length - 1];
2378 } else {
2376 } else {
2379 this.last_checkpoint = null;
2377 this.last_checkpoint = null;
2380 }
2378 }
2381 this.events.trigger('checkpoints_listed.Notebook', [data]);
2379 this.events.trigger('checkpoints_listed.Notebook', [data]);
2382 };
2380 };
2383
2381
2384 /**
2382 /**
2385 * Create a checkpoint of this notebook on the server from the most recent save.
2383 * Create a checkpoint of this notebook on the server from the most recent save.
2386 *
2384 *
2387 * @method create_checkpoint
2385 * @method create_checkpoint
2388 */
2386 */
2389 Notebook.prototype.create_checkpoint = function () {
2387 Notebook.prototype.create_checkpoint = function () {
2390 var that = this;
2388 var that = this;
2391 this.contents.create_checkpoint(this.notebook_path).then(
2389 this.contents.create_checkpoint(this.notebook_path).then(
2392 $.proxy(this.create_checkpoint_success, this),
2390 $.proxy(this.create_checkpoint_success, this),
2393 function (error) {
2391 function (error) {
2394 that.events.trigger('checkpoint_failed.Notebook', error);
2392 that.events.trigger('checkpoint_failed.Notebook', error);
2395 }
2393 }
2396 );
2394 );
2397 };
2395 };
2398
2396
2399 /**
2397 /**
2400 * Success callback for creating a checkpoint.
2398 * Success callback for creating a checkpoint.
2401 *
2399 *
2402 * @method create_checkpoint_success
2400 * @method create_checkpoint_success
2403 * @param {Object} data JSON representation of a checkpoint
2401 * @param {Object} data JSON representation of a checkpoint
2404 */
2402 */
2405 Notebook.prototype.create_checkpoint_success = function (data) {
2403 Notebook.prototype.create_checkpoint_success = function (data) {
2406 this.add_checkpoint(data);
2404 this.add_checkpoint(data);
2407 this.events.trigger('checkpoint_created.Notebook', data);
2405 this.events.trigger('checkpoint_created.Notebook', data);
2408 };
2406 };
2409
2407
2410 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2408 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2411 var that = this;
2409 var that = this;
2412 checkpoint = checkpoint || this.last_checkpoint;
2410 checkpoint = checkpoint || this.last_checkpoint;
2413 if ( ! checkpoint ) {
2411 if ( ! checkpoint ) {
2414 console.log("restore dialog, but no checkpoint to restore to!");
2412 console.log("restore dialog, but no checkpoint to restore to!");
2415 return;
2413 return;
2416 }
2414 }
2417 var body = $('<div/>').append(
2415 var body = $('<div/>').append(
2418 $('<p/>').addClass("p-space").text(
2416 $('<p/>').addClass("p-space").text(
2419 "Are you sure you want to revert the notebook to " +
2417 "Are you sure you want to revert the notebook to " +
2420 "the latest checkpoint?"
2418 "the latest checkpoint?"
2421 ).append(
2419 ).append(
2422 $("<strong/>").text(
2420 $("<strong/>").text(
2423 " This cannot be undone."
2421 " This cannot be undone."
2424 )
2422 )
2425 )
2423 )
2426 ).append(
2424 ).append(
2427 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2425 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2428 ).append(
2426 ).append(
2429 $('<p/>').addClass("p-space").text(
2427 $('<p/>').addClass("p-space").text(
2430 Date(checkpoint.last_modified)
2428 Date(checkpoint.last_modified)
2431 ).css("text-align", "center")
2429 ).css("text-align", "center")
2432 );
2430 );
2433
2431
2434 dialog.modal({
2432 dialog.modal({
2435 notebook: this,
2433 notebook: this,
2436 keyboard_manager: this.keyboard_manager,
2434 keyboard_manager: this.keyboard_manager,
2437 title : "Revert notebook to checkpoint",
2435 title : "Revert notebook to checkpoint",
2438 body : body,
2436 body : body,
2439 buttons : {
2437 buttons : {
2440 Revert : {
2438 Revert : {
2441 class : "btn-danger",
2439 class : "btn-danger",
2442 click : function () {
2440 click : function () {
2443 that.restore_checkpoint(checkpoint.id);
2441 that.restore_checkpoint(checkpoint.id);
2444 }
2442 }
2445 },
2443 },
2446 Cancel : {}
2444 Cancel : {}
2447 }
2445 }
2448 });
2446 });
2449 };
2447 };
2450
2448
2451 /**
2449 /**
2452 * Restore the notebook to a checkpoint state.
2450 * Restore the notebook to a checkpoint state.
2453 *
2451 *
2454 * @method restore_checkpoint
2452 * @method restore_checkpoint
2455 * @param {String} checkpoint ID
2453 * @param {String} checkpoint ID
2456 */
2454 */
2457 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2455 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2458 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2456 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2459 var that = this;
2457 var that = this;
2460 this.contents.restore_checkpoint(this.notebook_path, checkpoint).then(
2458 this.contents.restore_checkpoint(this.notebook_path, checkpoint).then(
2461 $.proxy(this.restore_checkpoint_success, this),
2459 $.proxy(this.restore_checkpoint_success, this),
2462 function (error) {
2460 function (error) {
2463 that.events.trigger('checkpoint_restore_failed.Notebook', error);
2461 that.events.trigger('checkpoint_restore_failed.Notebook', error);
2464 }
2462 }
2465 );
2463 );
2466 };
2464 };
2467
2465
2468 /**
2466 /**
2469 * Success callback for restoring a notebook to a checkpoint.
2467 * Success callback for restoring a notebook to a checkpoint.
2470 *
2468 *
2471 * @method restore_checkpoint_success
2469 * @method restore_checkpoint_success
2472 */
2470 */
2473 Notebook.prototype.restore_checkpoint_success = function () {
2471 Notebook.prototype.restore_checkpoint_success = function () {
2474 this.events.trigger('checkpoint_restored.Notebook');
2472 this.events.trigger('checkpoint_restored.Notebook');
2475 this.load_notebook(this.notebook_path);
2473 this.load_notebook(this.notebook_path);
2476 };
2474 };
2477
2475
2478 /**
2476 /**
2479 * Delete a notebook checkpoint.
2477 * Delete a notebook checkpoint.
2480 *
2478 *
2481 * @method delete_checkpoint
2479 * @method delete_checkpoint
2482 * @param {String} checkpoint ID
2480 * @param {String} checkpoint ID
2483 */
2481 */
2484 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2482 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2485 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2483 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2486 var that = this;
2484 var that = this;
2487 this.contents.delete_checkpoint(this.notebook_path, checkpoint).then(
2485 this.contents.delete_checkpoint(this.notebook_path, checkpoint).then(
2488 $.proxy(this.delete_checkpoint_success, this),
2486 $.proxy(this.delete_checkpoint_success, this),
2489 function (error) {
2487 function (error) {
2490 that.events.trigger('checkpoint_delete_failed.Notebook', error);
2488 that.events.trigger('checkpoint_delete_failed.Notebook', error);
2491 }
2489 }
2492 );
2490 );
2493 };
2491 };
2494
2492
2495 /**
2493 /**
2496 * Success callback for deleting a notebook checkpoint
2494 * Success callback for deleting a notebook checkpoint
2497 *
2495 *
2498 * @method delete_checkpoint_success
2496 * @method delete_checkpoint_success
2499 */
2497 */
2500 Notebook.prototype.delete_checkpoint_success = function () {
2498 Notebook.prototype.delete_checkpoint_success = function () {
2501 this.events.trigger('checkpoint_deleted.Notebook');
2499 this.events.trigger('checkpoint_deleted.Notebook');
2502 this.load_notebook(this.notebook_path);
2500 this.load_notebook(this.notebook_path);
2503 };
2501 };
2504
2502
2505
2503
2506 // For backwards compatability.
2504 // For backwards compatability.
2507 IPython.Notebook = Notebook;
2505 IPython.Notebook = Notebook;
2508
2506
2509 return {'Notebook': Notebook};
2507 return {'Notebook': Notebook};
2510 });
2508 });
@@ -1,356 +1,369 b''
1 """Adapters for IPython msg spec versions."""
1 """Adapters for IPython msg spec versions."""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 import json
6 import json
7
7
8 from IPython.core.release import kernel_protocol_version_info
8 from IPython.core.release import kernel_protocol_version_info
9 from IPython.utils.tokenutil import token_at_cursor
9 from IPython.utils.tokenutil import token_at_cursor
10
10
11
11
12 def code_to_line(code, cursor_pos):
12 def code_to_line(code, cursor_pos):
13 """Turn a multiline code block and cursor position into a single line
13 """Turn a multiline code block and cursor position into a single line
14 and new cursor position.
14 and new cursor position.
15
15
16 For adapting ``complete_`` and ``object_info_request``.
16 For adapting ``complete_`` and ``object_info_request``.
17 """
17 """
18 if not code:
18 if not code:
19 return "", 0
19 return "", 0
20 for line in code.splitlines(True):
20 for line in code.splitlines(True):
21 n = len(line)
21 n = len(line)
22 if cursor_pos > n:
22 if cursor_pos > n:
23 cursor_pos -= n
23 cursor_pos -= n
24 else:
24 else:
25 break
25 break
26 return line, cursor_pos
26 return line, cursor_pos
27
27
28
28
29 class Adapter(object):
29 class Adapter(object):
30 """Base class for adapting messages
30 """Base class for adapting messages
31
31
32 Override message_type(msg) methods to create adapters.
32 Override message_type(msg) methods to create adapters.
33 """
33 """
34
34
35 msg_type_map = {}
35 msg_type_map = {}
36
36
37 def update_header(self, msg):
37 def update_header(self, msg):
38 return msg
38 return msg
39
39
40 def update_metadata(self, msg):
40 def update_metadata(self, msg):
41 return msg
41 return msg
42
42
43 def update_msg_type(self, msg):
43 def update_msg_type(self, msg):
44 header = msg['header']
44 header = msg['header']
45 msg_type = header['msg_type']
45 msg_type = header['msg_type']
46 if msg_type in self.msg_type_map:
46 if msg_type in self.msg_type_map:
47 msg['msg_type'] = header['msg_type'] = self.msg_type_map[msg_type]
47 msg['msg_type'] = header['msg_type'] = self.msg_type_map[msg_type]
48 return msg
48 return msg
49
49
50 def handle_reply_status_error(self, msg):
50 def handle_reply_status_error(self, msg):
51 """This will be called *instead of* the regular handler
51 """This will be called *instead of* the regular handler
52
52
53 on any reply with status != ok
53 on any reply with status != ok
54 """
54 """
55 return msg
55 return msg
56
56
57 def __call__(self, msg):
57 def __call__(self, msg):
58 msg = self.update_header(msg)
58 msg = self.update_header(msg)
59 msg = self.update_metadata(msg)
59 msg = self.update_metadata(msg)
60 msg = self.update_msg_type(msg)
60 msg = self.update_msg_type(msg)
61 header = msg['header']
61 header = msg['header']
62
62
63 handler = getattr(self, header['msg_type'], None)
63 handler = getattr(self, header['msg_type'], None)
64 if handler is None:
64 if handler is None:
65 return msg
65 return msg
66
66
67 # handle status=error replies separately (no change, at present)
67 # handle status=error replies separately (no change, at present)
68 if msg['content'].get('status', None) in {'error', 'aborted'}:
68 if msg['content'].get('status', None) in {'error', 'aborted'}:
69 return self.handle_reply_status_error(msg)
69 return self.handle_reply_status_error(msg)
70 return handler(msg)
70 return handler(msg)
71
71
72 def _version_str_to_list(version):
72 def _version_str_to_list(version):
73 """convert a version string to a list of ints
73 """convert a version string to a list of ints
74
74
75 non-int segments are excluded
75 non-int segments are excluded
76 """
76 """
77 v = []
77 v = []
78 for part in version.split('.'):
78 for part in version.split('.'):
79 try:
79 try:
80 v.append(int(part))
80 v.append(int(part))
81 except ValueError:
81 except ValueError:
82 pass
82 pass
83 return v
83 return v
84
84
85 class V5toV4(Adapter):
85 class V5toV4(Adapter):
86 """Adapt msg protocol v5 to v4"""
86 """Adapt msg protocol v5 to v4"""
87
87
88 version = '4.1'
88 version = '4.1'
89
89
90 msg_type_map = {
90 msg_type_map = {
91 'execute_result' : 'pyout',
91 'execute_result' : 'pyout',
92 'execute_input' : 'pyin',
92 'execute_input' : 'pyin',
93 'error' : 'pyerr',
93 'error' : 'pyerr',
94 'inspect_request' : 'object_info_request',
94 'inspect_request' : 'object_info_request',
95 'inspect_reply' : 'object_info_reply',
95 'inspect_reply' : 'object_info_reply',
96 }
96 }
97
97
98 def update_header(self, msg):
98 def update_header(self, msg):
99 msg['header'].pop('version', None)
99 msg['header'].pop('version', None)
100 return msg
100 return msg
101
101
102 # shell channel
102 # shell channel
103
103
104 def kernel_info_reply(self, msg):
104 def kernel_info_reply(self, msg):
105 v4c = {}
105 content = msg['content']
106 content = msg['content']
106 content.pop('banner', None)
107 for key in ('language_version', 'protocol_version'):
107 for key in ('language_version', 'protocol_version'):
108 if key in content:
108 if key in content:
109 content[key] = _version_str_to_list(content[key])
109 v4c[key] = _version_str_to_list(content[key])
110 if content.pop('implementation', '') == 'ipython' \
110 if content.get('implementation', '') == 'ipython' \
111 and 'implementation_version' in content:
111 and 'implementation_version' in content:
112 content['ipython_version'] = content.pop('implmentation_version')
112 v4c['ipython_version'] = _version_str_to_list(content['implementation_version'])
113 content.pop('implementation_version', None)
113 language_info = content.get('language_info', {})
114 content.setdefault("implmentation", content['language'])
114 language = language_info.get('name', '')
115 v4c.setdefault('language', language)
116 if 'version' in language_info:
117 v4c.setdefault('language_version', _version_str_to_list(language_info['version']))
118 msg['content'] = v4c
115 return msg
119 return msg
116
120
117 def execute_request(self, msg):
121 def execute_request(self, msg):
118 content = msg['content']
122 content = msg['content']
119 content.setdefault('user_variables', [])
123 content.setdefault('user_variables', [])
120 return msg
124 return msg
121
125
122 def execute_reply(self, msg):
126 def execute_reply(self, msg):
123 content = msg['content']
127 content = msg['content']
124 content.setdefault('user_variables', {})
128 content.setdefault('user_variables', {})
125 # TODO: handle payloads
129 # TODO: handle payloads
126 return msg
130 return msg
127
131
128 def complete_request(self, msg):
132 def complete_request(self, msg):
129 content = msg['content']
133 content = msg['content']
130 code = content['code']
134 code = content['code']
131 cursor_pos = content['cursor_pos']
135 cursor_pos = content['cursor_pos']
132 line, cursor_pos = code_to_line(code, cursor_pos)
136 line, cursor_pos = code_to_line(code, cursor_pos)
133
137
134 new_content = msg['content'] = {}
138 new_content = msg['content'] = {}
135 new_content['text'] = ''
139 new_content['text'] = ''
136 new_content['line'] = line
140 new_content['line'] = line
137 new_content['block'] = None
141 new_content['block'] = None
138 new_content['cursor_pos'] = cursor_pos
142 new_content['cursor_pos'] = cursor_pos
139 return msg
143 return msg
140
144
141 def complete_reply(self, msg):
145 def complete_reply(self, msg):
142 content = msg['content']
146 content = msg['content']
143 cursor_start = content.pop('cursor_start')
147 cursor_start = content.pop('cursor_start')
144 cursor_end = content.pop('cursor_end')
148 cursor_end = content.pop('cursor_end')
145 match_len = cursor_end - cursor_start
149 match_len = cursor_end - cursor_start
146 content['matched_text'] = content['matches'][0][:match_len]
150 content['matched_text'] = content['matches'][0][:match_len]
147 content.pop('metadata', None)
151 content.pop('metadata', None)
148 return msg
152 return msg
149
153
150 def object_info_request(self, msg):
154 def object_info_request(self, msg):
151 content = msg['content']
155 content = msg['content']
152 code = content['code']
156 code = content['code']
153 cursor_pos = content['cursor_pos']
157 cursor_pos = content['cursor_pos']
154 line, _ = code_to_line(code, cursor_pos)
158 line, _ = code_to_line(code, cursor_pos)
155
159
156 new_content = msg['content'] = {}
160 new_content = msg['content'] = {}
157 new_content['oname'] = token_at_cursor(code, cursor_pos)
161 new_content['oname'] = token_at_cursor(code, cursor_pos)
158 new_content['detail_level'] = content['detail_level']
162 new_content['detail_level'] = content['detail_level']
159 return msg
163 return msg
160
164
161 def object_info_reply(self, msg):
165 def object_info_reply(self, msg):
162 """inspect_reply can't be easily backward compatible"""
166 """inspect_reply can't be easily backward compatible"""
163 msg['content'] = {'found' : False, 'oname' : 'unknown'}
167 msg['content'] = {'found' : False, 'oname' : 'unknown'}
164 return msg
168 return msg
165
169
166 # iopub channel
170 # iopub channel
167
171
168 def stream(self, msg):
172 def stream(self, msg):
169 content = msg['content']
173 content = msg['content']
170 content['data'] = content.pop('text')
174 content['data'] = content.pop('text')
171 return msg
175 return msg
172
176
173 def display_data(self, msg):
177 def display_data(self, msg):
174 content = msg['content']
178 content = msg['content']
175 content.setdefault("source", "display")
179 content.setdefault("source", "display")
176 data = content['data']
180 data = content['data']
177 if 'application/json' in data:
181 if 'application/json' in data:
178 try:
182 try:
179 data['application/json'] = json.dumps(data['application/json'])
183 data['application/json'] = json.dumps(data['application/json'])
180 except Exception:
184 except Exception:
181 # warn?
185 # warn?
182 pass
186 pass
183 return msg
187 return msg
184
188
185 # stdin channel
189 # stdin channel
186
190
187 def input_request(self, msg):
191 def input_request(self, msg):
188 msg['content'].pop('password', None)
192 msg['content'].pop('password', None)
189 return msg
193 return msg
190
194
191
195
192 class V4toV5(Adapter):
196 class V4toV5(Adapter):
193 """Convert msg spec V4 to V5"""
197 """Convert msg spec V4 to V5"""
194 version = '5.0'
198 version = '5.0'
195
199
196 # invert message renames above
200 # invert message renames above
197 msg_type_map = {v:k for k,v in V5toV4.msg_type_map.items()}
201 msg_type_map = {v:k for k,v in V5toV4.msg_type_map.items()}
198
202
199 def update_header(self, msg):
203 def update_header(self, msg):
200 msg['header']['version'] = self.version
204 msg['header']['version'] = self.version
201 return msg
205 return msg
202
206
203 # shell channel
207 # shell channel
204
208
205 def kernel_info_reply(self, msg):
209 def kernel_info_reply(self, msg):
206 content = msg['content']
210 content = msg['content']
207 for key in ('language_version', 'protocol_version', 'ipython_version'):
211 for key in ('protocol_version', 'ipython_version'):
208 if key in content:
212 if key in content:
209 content[key] = ".".join(map(str, content[key]))
213 content[key] = '.'.join(map(str, content[key]))
214
215 content.setdefault('protocol_version', '4.1')
210
216
211 if content['language'].startswith('python') and 'ipython_version' in content:
217 if content['language'].startswith('python') and 'ipython_version' in content:
212 content['implementation'] = 'ipython'
218 content['implementation'] = 'ipython'
213 content['implementation_version'] = content.pop('ipython_version')
219 content['implementation_version'] = content.pop('ipython_version')
214
220
221 language = content.pop('language')
222 language_info = content.setdefault('language_info', {})
223 language_info.setdefault('name', language)
224 if 'language_version' in content:
225 language_version = '.'.join(map(str, content.pop('language_version')))
226 language_info.setdefault('version', language_version)
227
215 content['banner'] = ''
228 content['banner'] = ''
216 return msg
229 return msg
217
230
218 def execute_request(self, msg):
231 def execute_request(self, msg):
219 content = msg['content']
232 content = msg['content']
220 user_variables = content.pop('user_variables', [])
233 user_variables = content.pop('user_variables', [])
221 user_expressions = content.setdefault('user_expressions', {})
234 user_expressions = content.setdefault('user_expressions', {})
222 for v in user_variables:
235 for v in user_variables:
223 user_expressions[v] = v
236 user_expressions[v] = v
224 return msg
237 return msg
225
238
226 def execute_reply(self, msg):
239 def execute_reply(self, msg):
227 content = msg['content']
240 content = msg['content']
228 user_expressions = content.setdefault('user_expressions', {})
241 user_expressions = content.setdefault('user_expressions', {})
229 user_variables = content.pop('user_variables', {})
242 user_variables = content.pop('user_variables', {})
230 if user_variables:
243 if user_variables:
231 user_expressions.update(user_variables)
244 user_expressions.update(user_variables)
232
245
233 # Pager payloads became a mime bundle
246 # Pager payloads became a mime bundle
234 for payload in content.get('payload', []):
247 for payload in content.get('payload', []):
235 if payload.get('source', None) == 'page' and ('text' in payload):
248 if payload.get('source', None) == 'page' and ('text' in payload):
236 if 'data' not in payload:
249 if 'data' not in payload:
237 payload['data'] = {}
250 payload['data'] = {}
238 payload['data']['text/plain'] = payload.pop('text')
251 payload['data']['text/plain'] = payload.pop('text')
239
252
240 return msg
253 return msg
241
254
242 def complete_request(self, msg):
255 def complete_request(self, msg):
243 old_content = msg['content']
256 old_content = msg['content']
244
257
245 new_content = msg['content'] = {}
258 new_content = msg['content'] = {}
246 new_content['code'] = old_content['line']
259 new_content['code'] = old_content['line']
247 new_content['cursor_pos'] = old_content['cursor_pos']
260 new_content['cursor_pos'] = old_content['cursor_pos']
248 return msg
261 return msg
249
262
250 def complete_reply(self, msg):
263 def complete_reply(self, msg):
251 # complete_reply needs more context than we have to get cursor_start and end.
264 # complete_reply needs more context than we have to get cursor_start and end.
252 # use special value of `-1` to indicate to frontend that it should be at
265 # use special value of `-1` to indicate to frontend that it should be at
253 # the current cursor position.
266 # the current cursor position.
254 content = msg['content']
267 content = msg['content']
255 new_content = msg['content'] = {'status' : 'ok'}
268 new_content = msg['content'] = {'status' : 'ok'}
256 new_content['matches'] = content['matches']
269 new_content['matches'] = content['matches']
257 new_content['cursor_start'] = -len(content['matched_text'])
270 new_content['cursor_start'] = -len(content['matched_text'])
258 new_content['cursor_end'] = None
271 new_content['cursor_end'] = None
259 new_content['metadata'] = {}
272 new_content['metadata'] = {}
260 return msg
273 return msg
261
274
262 def inspect_request(self, msg):
275 def inspect_request(self, msg):
263 content = msg['content']
276 content = msg['content']
264 name = content['oname']
277 name = content['oname']
265
278
266 new_content = msg['content'] = {}
279 new_content = msg['content'] = {}
267 new_content['code'] = name
280 new_content['code'] = name
268 new_content['cursor_pos'] = len(name)
281 new_content['cursor_pos'] = len(name)
269 new_content['detail_level'] = content['detail_level']
282 new_content['detail_level'] = content['detail_level']
270 return msg
283 return msg
271
284
272 def inspect_reply(self, msg):
285 def inspect_reply(self, msg):
273 """inspect_reply can't be easily backward compatible"""
286 """inspect_reply can't be easily backward compatible"""
274 content = msg['content']
287 content = msg['content']
275 new_content = msg['content'] = {'status' : 'ok'}
288 new_content = msg['content'] = {'status' : 'ok'}
276 found = new_content['found'] = content['found']
289 found = new_content['found'] = content['found']
277 new_content['name'] = content['oname']
290 new_content['name'] = content['oname']
278 new_content['data'] = data = {}
291 new_content['data'] = data = {}
279 new_content['metadata'] = {}
292 new_content['metadata'] = {}
280 if found:
293 if found:
281 lines = []
294 lines = []
282 for key in ('call_def', 'init_definition', 'definition'):
295 for key in ('call_def', 'init_definition', 'definition'):
283 if content.get(key, False):
296 if content.get(key, False):
284 lines.append(content[key])
297 lines.append(content[key])
285 break
298 break
286 for key in ('call_docstring', 'init_docstring', 'docstring'):
299 for key in ('call_docstring', 'init_docstring', 'docstring'):
287 if content.get(key, False):
300 if content.get(key, False):
288 lines.append(content[key])
301 lines.append(content[key])
289 break
302 break
290 if not lines:
303 if not lines:
291 lines.append("<empty docstring>")
304 lines.append("<empty docstring>")
292 data['text/plain'] = '\n'.join(lines)
305 data['text/plain'] = '\n'.join(lines)
293 return msg
306 return msg
294
307
295 # iopub channel
308 # iopub channel
296
309
297 def stream(self, msg):
310 def stream(self, msg):
298 content = msg['content']
311 content = msg['content']
299 content['text'] = content.pop('data')
312 content['text'] = content.pop('data')
300 return msg
313 return msg
301
314
302 def display_data(self, msg):
315 def display_data(self, msg):
303 content = msg['content']
316 content = msg['content']
304 content.pop("source", None)
317 content.pop("source", None)
305 data = content['data']
318 data = content['data']
306 if 'application/json' in data:
319 if 'application/json' in data:
307 try:
320 try:
308 data['application/json'] = json.loads(data['application/json'])
321 data['application/json'] = json.loads(data['application/json'])
309 except Exception:
322 except Exception:
310 # warn?
323 # warn?
311 pass
324 pass
312 return msg
325 return msg
313
326
314 # stdin channel
327 # stdin channel
315
328
316 def input_request(self, msg):
329 def input_request(self, msg):
317 msg['content'].setdefault('password', False)
330 msg['content'].setdefault('password', False)
318 return msg
331 return msg
319
332
320
333
321
334
322 def adapt(msg, to_version=kernel_protocol_version_info[0]):
335 def adapt(msg, to_version=kernel_protocol_version_info[0]):
323 """Adapt a single message to a target version
336 """Adapt a single message to a target version
324
337
325 Parameters
338 Parameters
326 ----------
339 ----------
327
340
328 msg : dict
341 msg : dict
329 An IPython message.
342 An IPython message.
330 to_version : int, optional
343 to_version : int, optional
331 The target major version.
344 The target major version.
332 If unspecified, adapt to the current version for IPython.
345 If unspecified, adapt to the current version for IPython.
333
346
334 Returns
347 Returns
335 -------
348 -------
336
349
337 msg : dict
350 msg : dict
338 An IPython message appropriate in the new version.
351 An IPython message appropriate in the new version.
339 """
352 """
340 header = msg['header']
353 header = msg['header']
341 if 'version' in header:
354 if 'version' in header:
342 from_version = int(header['version'].split('.')[0])
355 from_version = int(header['version'].split('.')[0])
343 else:
356 else:
344 # assume last version before adding the key to the header
357 # assume last version before adding the key to the header
345 from_version = 4
358 from_version = 4
346 adapter = adapters.get((from_version, to_version), None)
359 adapter = adapters.get((from_version, to_version), None)
347 if adapter is None:
360 if adapter is None:
348 return msg
361 return msg
349 return adapter(msg)
362 return adapter(msg)
350
363
351
364
352 # one adapter per major version from,to
365 # one adapter per major version from,to
353 adapters = {
366 adapters = {
354 (5,4) : V5toV4(),
367 (5,4) : V5toV4(),
355 (4,5) : V4toV5(),
368 (4,5) : V4toV5(),
356 }
369 }
@@ -1,334 +1,377 b''
1 """Tests for adapting IPython msg spec versions"""
1 """Tests for adapting IPython msg spec versions"""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 import copy
6 import copy
7 import json
7 import json
8 from unittest import TestCase
8 from unittest import TestCase
9 import nose.tools as nt
9 import nose.tools as nt
10
10
11 from IPython.kernel.adapter import adapt, V4toV5, V5toV4, code_to_line
11 from IPython.kernel.adapter import adapt, V4toV5, V5toV4, code_to_line
12 from IPython.kernel.zmq.session import Session
12 from IPython.kernel.zmq.session import Session
13
13
14
14
15 def test_default_version():
15 def test_default_version():
16 s = Session()
16 s = Session()
17 msg = s.msg("msg_type")
17 msg = s.msg("msg_type")
18 msg['header'].pop('version')
18 msg['header'].pop('version')
19 original = copy.deepcopy(msg)
19 original = copy.deepcopy(msg)
20 adapted = adapt(original)
20 adapted = adapt(original)
21 nt.assert_equal(adapted['header']['version'], V4toV5.version)
21 nt.assert_equal(adapted['header']['version'], V4toV5.version)
22
22
23 def test_code_to_line_no_code():
23 def test_code_to_line_no_code():
24 line, pos = code_to_line("", 0)
24 line, pos = code_to_line("", 0)
25 nt.assert_equal(line, "")
25 nt.assert_equal(line, "")
26 nt.assert_equal(pos, 0)
26 nt.assert_equal(pos, 0)
27
27
28 class AdapterTest(TestCase):
28 class AdapterTest(TestCase):
29
29
30 def setUp(self):
30 def setUp(self):
31 self.session = Session()
31 self.session = Session()
32
32
33 def adapt(self, msg, version=None):
33 def adapt(self, msg, version=None):
34 original = copy.deepcopy(msg)
34 original = copy.deepcopy(msg)
35 adapted = adapt(msg, version or self.to_version)
35 adapted = adapt(msg, version or self.to_version)
36 return original, adapted
36 return original, adapted
37
37
38 def check_header(self, msg):
38 def check_header(self, msg):
39 pass
39 pass
40
40
41
41
42 class V4toV5TestCase(AdapterTest):
42 class V4toV5TestCase(AdapterTest):
43 from_version = 4
43 from_version = 4
44 to_version = 5
44 to_version = 5
45
45
46 def msg(self, msg_type, content):
46 def msg(self, msg_type, content):
47 """Create a v4 msg (same as v5, minus version header)"""
47 """Create a v4 msg (same as v5, minus version header)"""
48 msg = self.session.msg(msg_type, content)
48 msg = self.session.msg(msg_type, content)
49 msg['header'].pop('version')
49 msg['header'].pop('version')
50 return msg
50 return msg
51
51
52 def test_same_version(self):
52 def test_same_version(self):
53 msg = self.msg("execute_result",
53 msg = self.msg("execute_result",
54 content={'status' : 'ok'}
54 content={'status' : 'ok'}
55 )
55 )
56 original, adapted = self.adapt(msg, self.from_version)
56 original, adapted = self.adapt(msg, self.from_version)
57
57
58 self.assertEqual(original, adapted)
58 self.assertEqual(original, adapted)
59
59
60 def test_no_adapt(self):
60 def test_no_adapt(self):
61 msg = self.msg("input_reply", {'value' : 'some text'})
61 msg = self.msg("input_reply", {'value' : 'some text'})
62 v4, v5 = self.adapt(msg)
62 v4, v5 = self.adapt(msg)
63 self.assertEqual(v5['header']['version'], V4toV5.version)
63 self.assertEqual(v5['header']['version'], V4toV5.version)
64 v5['header'].pop('version')
64 v5['header'].pop('version')
65 self.assertEqual(v4, v5)
65 self.assertEqual(v4, v5)
66
66
67 def test_rename_type(self):
67 def test_rename_type(self):
68 for v5_type, v4_type in [
68 for v5_type, v4_type in [
69 ('execute_result', 'pyout'),
69 ('execute_result', 'pyout'),
70 ('execute_input', 'pyin'),
70 ('execute_input', 'pyin'),
71 ('error', 'pyerr'),
71 ('error', 'pyerr'),
72 ]:
72 ]:
73 msg = self.msg(v4_type, {'key' : 'value'})
73 msg = self.msg(v4_type, {'key' : 'value'})
74 v4, v5 = self.adapt(msg)
74 v4, v5 = self.adapt(msg)
75 self.assertEqual(v5['header']['version'], V4toV5.version)
75 self.assertEqual(v5['header']['version'], V4toV5.version)
76 self.assertEqual(v5['header']['msg_type'], v5_type)
76 self.assertEqual(v5['header']['msg_type'], v5_type)
77 self.assertEqual(v4['content'], v5['content'])
77 self.assertEqual(v4['content'], v5['content'])
78
78
79 def test_execute_request(self):
79 def test_execute_request(self):
80 msg = self.msg("execute_request", {
80 msg = self.msg("execute_request", {
81 'code' : 'a=5',
81 'code' : 'a=5',
82 'silent' : False,
82 'silent' : False,
83 'user_expressions' : {'a' : 'apple'},
83 'user_expressions' : {'a' : 'apple'},
84 'user_variables' : ['b'],
84 'user_variables' : ['b'],
85 })
85 })
86 v4, v5 = self.adapt(msg)
86 v4, v5 = self.adapt(msg)
87 self.assertEqual(v4['header']['msg_type'], v5['header']['msg_type'])
87 self.assertEqual(v4['header']['msg_type'], v5['header']['msg_type'])
88 v4c = v4['content']
88 v4c = v4['content']
89 v5c = v5['content']
89 v5c = v5['content']
90 self.assertEqual(v5c['user_expressions'], {'a' : 'apple', 'b': 'b'})
90 self.assertEqual(v5c['user_expressions'], {'a' : 'apple', 'b': 'b'})
91 self.assertNotIn('user_variables', v5c)
91 self.assertNotIn('user_variables', v5c)
92 self.assertEqual(v5c['code'], v4c['code'])
92 self.assertEqual(v5c['code'], v4c['code'])
93
93
94 def test_execute_reply(self):
94 def test_execute_reply(self):
95 msg = self.msg("execute_reply", {
95 msg = self.msg("execute_reply", {
96 'status': 'ok',
96 'status': 'ok',
97 'execution_count': 7,
97 'execution_count': 7,
98 'user_variables': {'a': 1},
98 'user_variables': {'a': 1},
99 'user_expressions': {'a+a': 2},
99 'user_expressions': {'a+a': 2},
100 'payload': [{'source':'page', 'text':'blah'}]
100 'payload': [{'source':'page', 'text':'blah'}]
101 })
101 })
102 v4, v5 = self.adapt(msg)
102 v4, v5 = self.adapt(msg)
103 v5c = v5['content']
103 v5c = v5['content']
104 self.assertNotIn('user_variables', v5c)
104 self.assertNotIn('user_variables', v5c)
105 self.assertEqual(v5c['user_expressions'], {'a': 1, 'a+a': 2})
105 self.assertEqual(v5c['user_expressions'], {'a': 1, 'a+a': 2})
106 self.assertEqual(v5c['payload'], [{'source': 'page',
106 self.assertEqual(v5c['payload'], [{'source': 'page',
107 'data': {'text/plain': 'blah'}}
107 'data': {'text/plain': 'blah'}}
108 ])
108 ])
109
109
110 def test_complete_request(self):
110 def test_complete_request(self):
111 msg = self.msg("complete_request", {
111 msg = self.msg("complete_request", {
112 'text' : 'a.is',
112 'text' : 'a.is',
113 'line' : 'foo = a.is',
113 'line' : 'foo = a.is',
114 'block' : None,
114 'block' : None,
115 'cursor_pos' : 10,
115 'cursor_pos' : 10,
116 })
116 })
117 v4, v5 = self.adapt(msg)
117 v4, v5 = self.adapt(msg)
118 v4c = v4['content']
118 v4c = v4['content']
119 v5c = v5['content']
119 v5c = v5['content']
120 for key in ('text', 'line', 'block'):
120 for key in ('text', 'line', 'block'):
121 self.assertNotIn(key, v5c)
121 self.assertNotIn(key, v5c)
122 self.assertEqual(v5c['cursor_pos'], v4c['cursor_pos'])
122 self.assertEqual(v5c['cursor_pos'], v4c['cursor_pos'])
123 self.assertEqual(v5c['code'], v4c['line'])
123 self.assertEqual(v5c['code'], v4c['line'])
124
124
125 def test_complete_reply(self):
125 def test_complete_reply(self):
126 msg = self.msg("complete_reply", {
126 msg = self.msg("complete_reply", {
127 'matched_text' : 'a.is',
127 'matched_text' : 'a.is',
128 'matches' : ['a.isalnum',
128 'matches' : ['a.isalnum',
129 'a.isalpha',
129 'a.isalpha',
130 'a.isdigit',
130 'a.isdigit',
131 'a.islower',
131 'a.islower',
132 ],
132 ],
133 })
133 })
134 v4, v5 = self.adapt(msg)
134 v4, v5 = self.adapt(msg)
135 v4c = v4['content']
135 v4c = v4['content']
136 v5c = v5['content']
136 v5c = v5['content']
137
137
138 self.assertEqual(v5c['matches'], v4c['matches'])
138 self.assertEqual(v5c['matches'], v4c['matches'])
139 self.assertEqual(v5c['metadata'], {})
139 self.assertEqual(v5c['metadata'], {})
140 self.assertEqual(v5c['cursor_start'], -4)
140 self.assertEqual(v5c['cursor_start'], -4)
141 self.assertEqual(v5c['cursor_end'], None)
141 self.assertEqual(v5c['cursor_end'], None)
142
142
143 def test_object_info_request(self):
143 def test_object_info_request(self):
144 msg = self.msg("object_info_request", {
144 msg = self.msg("object_info_request", {
145 'oname' : 'foo',
145 'oname' : 'foo',
146 'detail_level' : 1,
146 'detail_level' : 1,
147 })
147 })
148 v4, v5 = self.adapt(msg)
148 v4, v5 = self.adapt(msg)
149 self.assertEqual(v5['header']['msg_type'], 'inspect_request')
149 self.assertEqual(v5['header']['msg_type'], 'inspect_request')
150 v4c = v4['content']
150 v4c = v4['content']
151 v5c = v5['content']
151 v5c = v5['content']
152 self.assertEqual(v5c['code'], v4c['oname'])
152 self.assertEqual(v5c['code'], v4c['oname'])
153 self.assertEqual(v5c['cursor_pos'], len(v4c['oname']))
153 self.assertEqual(v5c['cursor_pos'], len(v4c['oname']))
154 self.assertEqual(v5c['detail_level'], v4c['detail_level'])
154 self.assertEqual(v5c['detail_level'], v4c['detail_level'])
155
155
156 def test_object_info_reply(self):
156 def test_object_info_reply(self):
157 msg = self.msg("object_info_reply", {
157 msg = self.msg("object_info_reply", {
158 'oname' : 'foo',
158 'oname' : 'foo',
159 'found' : True,
159 'found' : True,
160 'status' : 'ok',
160 'status' : 'ok',
161 'definition' : 'foo(a=5)',
161 'definition' : 'foo(a=5)',
162 'docstring' : "the docstring",
162 'docstring' : "the docstring",
163 })
163 })
164 v4, v5 = self.adapt(msg)
164 v4, v5 = self.adapt(msg)
165 self.assertEqual(v5['header']['msg_type'], 'inspect_reply')
165 self.assertEqual(v5['header']['msg_type'], 'inspect_reply')
166 v4c = v4['content']
166 v4c = v4['content']
167 v5c = v5['content']
167 v5c = v5['content']
168 self.assertEqual(sorted(v5c), [ 'data', 'found', 'metadata', 'name', 'status'])
168 self.assertEqual(sorted(v5c), [ 'data', 'found', 'metadata', 'name', 'status'])
169 text = v5c['data']['text/plain']
169 text = v5c['data']['text/plain']
170 self.assertEqual(text, '\n'.join([v4c['definition'], v4c['docstring']]))
170 self.assertEqual(text, '\n'.join([v4c['definition'], v4c['docstring']]))
171
171
172 def test_kernel_info_reply(self):
173 msg = self.msg("kernel_info_reply", {
174 'language': 'python',
175 'language_version': [2,8,0],
176 'ipython_version': [1,2,3],
177 })
178 v4, v5 = self.adapt(msg)
179 v4c = v4['content']
180 v5c = v5['content']
181 self.assertEqual(v5c, {
182 'protocol_version': '4.1',
183 'implementation': 'ipython',
184 'implementation_version': '1.2.3',
185 'language_info': {
186 'name': 'python',
187 'version': '2.8.0',
188 },
189 'banner' : '',
190 })
191
172 # iopub channel
192 # iopub channel
173
193
174 def test_display_data(self):
194 def test_display_data(self):
175 jsondata = dict(a=5)
195 jsondata = dict(a=5)
176 msg = self.msg("display_data", {
196 msg = self.msg("display_data", {
177 'data' : {
197 'data' : {
178 'text/plain' : 'some text',
198 'text/plain' : 'some text',
179 'application/json' : json.dumps(jsondata)
199 'application/json' : json.dumps(jsondata)
180 },
200 },
181 'metadata' : {'text/plain' : { 'key' : 'value' }},
201 'metadata' : {'text/plain' : { 'key' : 'value' }},
182 })
202 })
183 v4, v5 = self.adapt(msg)
203 v4, v5 = self.adapt(msg)
184 v4c = v4['content']
204 v4c = v4['content']
185 v5c = v5['content']
205 v5c = v5['content']
186 self.assertEqual(v5c['metadata'], v4c['metadata'])
206 self.assertEqual(v5c['metadata'], v4c['metadata'])
187 self.assertEqual(v5c['data']['text/plain'], v4c['data']['text/plain'])
207 self.assertEqual(v5c['data']['text/plain'], v4c['data']['text/plain'])
188 self.assertEqual(v5c['data']['application/json'], jsondata)
208 self.assertEqual(v5c['data']['application/json'], jsondata)
189
209
190 # stdin channel
210 # stdin channel
191
211
192 def test_input_request(self):
212 def test_input_request(self):
193 msg = self.msg('input_request', {'prompt': "$>"})
213 msg = self.msg('input_request', {'prompt': "$>"})
194 v4, v5 = self.adapt(msg)
214 v4, v5 = self.adapt(msg)
195 self.assertEqual(v5['content']['prompt'], v4['content']['prompt'])
215 self.assertEqual(v5['content']['prompt'], v4['content']['prompt'])
196 self.assertFalse(v5['content']['password'])
216 self.assertFalse(v5['content']['password'])
197
217
198
218
199 class V5toV4TestCase(AdapterTest):
219 class V5toV4TestCase(AdapterTest):
200 from_version = 5
220 from_version = 5
201 to_version = 4
221 to_version = 4
202
222
203 def msg(self, msg_type, content):
223 def msg(self, msg_type, content):
204 return self.session.msg(msg_type, content)
224 return self.session.msg(msg_type, content)
205
225
206 def test_same_version(self):
226 def test_same_version(self):
207 msg = self.msg("execute_result",
227 msg = self.msg("execute_result",
208 content={'status' : 'ok'}
228 content={'status' : 'ok'}
209 )
229 )
210 original, adapted = self.adapt(msg, self.from_version)
230 original, adapted = self.adapt(msg, self.from_version)
211
231
212 self.assertEqual(original, adapted)
232 self.assertEqual(original, adapted)
213
233
214 def test_no_adapt(self):
234 def test_no_adapt(self):
215 msg = self.msg("input_reply", {'value' : 'some text'})
235 msg = self.msg("input_reply", {'value' : 'some text'})
216 v5, v4 = self.adapt(msg)
236 v5, v4 = self.adapt(msg)
217 self.assertNotIn('version', v4['header'])
237 self.assertNotIn('version', v4['header'])
218 v5['header'].pop('version')
238 v5['header'].pop('version')
219 self.assertEqual(v4, v5)
239 self.assertEqual(v4, v5)
220
240
221 def test_rename_type(self):
241 def test_rename_type(self):
222 for v5_type, v4_type in [
242 for v5_type, v4_type in [
223 ('execute_result', 'pyout'),
243 ('execute_result', 'pyout'),
224 ('execute_input', 'pyin'),
244 ('execute_input', 'pyin'),
225 ('error', 'pyerr'),
245 ('error', 'pyerr'),
226 ]:
246 ]:
227 msg = self.msg(v5_type, {'key' : 'value'})
247 msg = self.msg(v5_type, {'key' : 'value'})
228 v5, v4 = self.adapt(msg)
248 v5, v4 = self.adapt(msg)
229 self.assertEqual(v4['header']['msg_type'], v4_type)
249 self.assertEqual(v4['header']['msg_type'], v4_type)
230 nt.assert_not_in('version', v4['header'])
250 nt.assert_not_in('version', v4['header'])
231 self.assertEqual(v4['content'], v5['content'])
251 self.assertEqual(v4['content'], v5['content'])
232
252
233 def test_execute_request(self):
253 def test_execute_request(self):
234 msg = self.msg("execute_request", {
254 msg = self.msg("execute_request", {
235 'code' : 'a=5',
255 'code' : 'a=5',
236 'silent' : False,
256 'silent' : False,
237 'user_expressions' : {'a' : 'apple'},
257 'user_expressions' : {'a' : 'apple'},
238 })
258 })
239 v5, v4 = self.adapt(msg)
259 v5, v4 = self.adapt(msg)
240 self.assertEqual(v4['header']['msg_type'], v5['header']['msg_type'])
260 self.assertEqual(v4['header']['msg_type'], v5['header']['msg_type'])
241 v4c = v4['content']
261 v4c = v4['content']
242 v5c = v5['content']
262 v5c = v5['content']
243 self.assertEqual(v4c['user_variables'], [])
263 self.assertEqual(v4c['user_variables'], [])
244 self.assertEqual(v5c['code'], v4c['code'])
264 self.assertEqual(v5c['code'], v4c['code'])
245
265
246 def test_complete_request(self):
266 def test_complete_request(self):
247 msg = self.msg("complete_request", {
267 msg = self.msg("complete_request", {
248 'code' : 'def foo():\n'
268 'code' : 'def foo():\n'
249 ' a.is\n'
269 ' a.is\n'
250 'foo()',
270 'foo()',
251 'cursor_pos': 19,
271 'cursor_pos': 19,
252 })
272 })
253 v5, v4 = self.adapt(msg)
273 v5, v4 = self.adapt(msg)
254 v4c = v4['content']
274 v4c = v4['content']
255 v5c = v5['content']
275 v5c = v5['content']
256 self.assertNotIn('code', v4c)
276 self.assertNotIn('code', v4c)
257 self.assertEqual(v4c['line'], v5c['code'].splitlines(True)[1])
277 self.assertEqual(v4c['line'], v5c['code'].splitlines(True)[1])
258 self.assertEqual(v4c['cursor_pos'], 8)
278 self.assertEqual(v4c['cursor_pos'], 8)
259 self.assertEqual(v4c['text'], '')
279 self.assertEqual(v4c['text'], '')
260 self.assertEqual(v4c['block'], None)
280 self.assertEqual(v4c['block'], None)
261
281
262 def test_complete_reply(self):
282 def test_complete_reply(self):
263 msg = self.msg("complete_reply", {
283 msg = self.msg("complete_reply", {
264 'cursor_start' : 10,
284 'cursor_start' : 10,
265 'cursor_end' : 14,
285 'cursor_end' : 14,
266 'matches' : ['a.isalnum',
286 'matches' : ['a.isalnum',
267 'a.isalpha',
287 'a.isalpha',
268 'a.isdigit',
288 'a.isdigit',
269 'a.islower',
289 'a.islower',
270 ],
290 ],
271 'metadata' : {},
291 'metadata' : {},
272 })
292 })
273 v5, v4 = self.adapt(msg)
293 v5, v4 = self.adapt(msg)
274 v4c = v4['content']
294 v4c = v4['content']
275 v5c = v5['content']
295 v5c = v5['content']
276 self.assertEqual(v4c['matched_text'], 'a.is')
296 self.assertEqual(v4c['matched_text'], 'a.is')
277 self.assertEqual(v4c['matches'], v5c['matches'])
297 self.assertEqual(v4c['matches'], v5c['matches'])
278
298
279 def test_inspect_request(self):
299 def test_inspect_request(self):
280 msg = self.msg("inspect_request", {
300 msg = self.msg("inspect_request", {
281 'code' : 'def foo():\n'
301 'code' : 'def foo():\n'
282 ' apple\n'
302 ' apple\n'
283 'bar()',
303 'bar()',
284 'cursor_pos': 18,
304 'cursor_pos': 18,
285 'detail_level' : 1,
305 'detail_level' : 1,
286 })
306 })
287 v5, v4 = self.adapt(msg)
307 v5, v4 = self.adapt(msg)
288 self.assertEqual(v4['header']['msg_type'], 'object_info_request')
308 self.assertEqual(v4['header']['msg_type'], 'object_info_request')
289 v4c = v4['content']
309 v4c = v4['content']
290 v5c = v5['content']
310 v5c = v5['content']
291 self.assertEqual(v4c['oname'], 'apple')
311 self.assertEqual(v4c['oname'], 'apple')
292 self.assertEqual(v5c['detail_level'], v4c['detail_level'])
312 self.assertEqual(v5c['detail_level'], v4c['detail_level'])
293
313
294 def test_inspect_reply(self):
314 def test_inspect_reply(self):
295 msg = self.msg("inspect_reply", {
315 msg = self.msg("inspect_reply", {
296 'name' : 'foo',
316 'name' : 'foo',
297 'found' : True,
317 'found' : True,
298 'data' : {'text/plain' : 'some text'},
318 'data' : {'text/plain' : 'some text'},
299 'metadata' : {},
319 'metadata' : {},
300 })
320 })
301 v5, v4 = self.adapt(msg)
321 v5, v4 = self.adapt(msg)
302 self.assertEqual(v4['header']['msg_type'], 'object_info_reply')
322 self.assertEqual(v4['header']['msg_type'], 'object_info_reply')
303 v4c = v4['content']
323 v4c = v4['content']
304 v5c = v5['content']
324 v5c = v5['content']
305 self.assertEqual(sorted(v4c), ['found', 'oname'])
325 self.assertEqual(sorted(v4c), ['found', 'oname'])
306 self.assertEqual(v4c['found'], False)
326 self.assertEqual(v4c['found'], False)
307
327
328 def test_kernel_info_reply(self):
329 msg = self.msg("kernel_info_reply", {
330 'protocol_version': '5.0',
331 'implementation': 'ipython',
332 'implementation_version': '1.2.3',
333 'language_info': {
334 'name': 'python',
335 'version': '2.8.0',
336 'mimetype': 'text/x-python',
337 },
338 'banner' : 'the banner',
339 })
340 v5, v4 = self.adapt(msg)
341 v4c = v4['content']
342 v5c = v5['content']
343 info = v5c['language_info']
344 self.assertEqual(v4c, {
345 'protocol_version': [5,0],
346 'language': 'python',
347 'language_version': [2,8,0],
348 'ipython_version': [1,2,3],
349 })
350
308 # iopub channel
351 # iopub channel
309
352
310 def test_display_data(self):
353 def test_display_data(self):
311 jsondata = dict(a=5)
354 jsondata = dict(a=5)
312 msg = self.msg("display_data", {
355 msg = self.msg("display_data", {
313 'data' : {
356 'data' : {
314 'text/plain' : 'some text',
357 'text/plain' : 'some text',
315 'application/json' : jsondata,
358 'application/json' : jsondata,
316 },
359 },
317 'metadata' : {'text/plain' : { 'key' : 'value' }},
360 'metadata' : {'text/plain' : { 'key' : 'value' }},
318 })
361 })
319 v5, v4 = self.adapt(msg)
362 v5, v4 = self.adapt(msg)
320 v4c = v4['content']
363 v4c = v4['content']
321 v5c = v5['content']
364 v5c = v5['content']
322 self.assertEqual(v5c['metadata'], v4c['metadata'])
365 self.assertEqual(v5c['metadata'], v4c['metadata'])
323 self.assertEqual(v5c['data']['text/plain'], v4c['data']['text/plain'])
366 self.assertEqual(v5c['data']['text/plain'], v4c['data']['text/plain'])
324 self.assertEqual(v4c['data']['application/json'], json.dumps(jsondata))
367 self.assertEqual(v4c['data']['application/json'], json.dumps(jsondata))
325
368
326 # stdin channel
369 # stdin channel
327
370
328 def test_input_request(self):
371 def test_input_request(self):
329 msg = self.msg('input_request', {'prompt': "$>", 'password' : True})
372 msg = self.msg('input_request', {'prompt': "$>", 'password' : True})
330 v5, v4 = self.adapt(msg)
373 v5, v4 = self.adapt(msg)
331 self.assertEqual(v5['content']['prompt'], v4['content']['prompt'])
374 self.assertEqual(v5['content']['prompt'], v4['content']['prompt'])
332 self.assertNotIn('password', v4['content'])
375 self.assertNotIn('password', v4['content'])
333
376
334
377
@@ -1,429 +1,432 b''
1 """Test suite for our zeromq-based message specification."""
1 """Test suite for our zeromq-based message specification."""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 import re
6 import re
7 import sys
7 from distutils.version import LooseVersion as V
8 from distutils.version import LooseVersion as V
8 from subprocess import PIPE
9 try:
9 try:
10 from queue import Empty # Py 3
10 from queue import Empty # Py 3
11 except ImportError:
11 except ImportError:
12 from Queue import Empty # Py 2
12 from Queue import Empty # Py 2
13
13
14 import nose.tools as nt
14 import nose.tools as nt
15
15
16 from IPython.kernel import KernelManager
17
18 from IPython.utils.traitlets import (
16 from IPython.utils.traitlets import (
19 HasTraits, TraitError, Bool, Unicode, Dict, Integer, List, Enum, Any,
17 HasTraits, TraitError, Bool, Unicode, Dict, Integer, List, Enum,
20 )
18 )
21 from IPython.utils.py3compat import string_types, iteritems
19 from IPython.utils.py3compat import string_types, iteritems
22
20
23 from .utils import TIMEOUT, start_global_kernel, flush_channels, execute
21 from .utils import TIMEOUT, start_global_kernel, flush_channels, execute
24
22
25 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
26 # Globals
24 # Globals
27 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
28 KC = None
26 KC = None
29
27
30 def setup():
28 def setup():
31 global KC
29 global KC
32 KC = start_global_kernel()
30 KC = start_global_kernel()
33
31
34 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
35 # Message Spec References
33 # Message Spec References
36 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
37
35
38 class Reference(HasTraits):
36 class Reference(HasTraits):
39
37
40 """
38 """
41 Base class for message spec specification testing.
39 Base class for message spec specification testing.
42
40
43 This class is the core of the message specification test. The
41 This class is the core of the message specification test. The
44 idea is that child classes implement trait attributes for each
42 idea is that child classes implement trait attributes for each
45 message keys, so that message keys can be tested against these
43 message keys, so that message keys can be tested against these
46 traits using :meth:`check` method.
44 traits using :meth:`check` method.
47
45
48 """
46 """
49
47
50 def check(self, d):
48 def check(self, d):
51 """validate a dict against our traits"""
49 """validate a dict against our traits"""
52 for key in self.trait_names():
50 for key in self.trait_names():
53 nt.assert_in(key, d)
51 nt.assert_in(key, d)
54 # FIXME: always allow None, probably not a good idea
52 # FIXME: always allow None, probably not a good idea
55 if d[key] is None:
53 if d[key] is None:
56 continue
54 continue
57 try:
55 try:
58 setattr(self, key, d[key])
56 setattr(self, key, d[key])
59 except TraitError as e:
57 except TraitError as e:
60 assert False, str(e)
58 assert False, str(e)
61
59
62
60
63 class Version(Unicode):
61 class Version(Unicode):
64 def __init__(self, *args, **kwargs):
62 def __init__(self, *args, **kwargs):
65 self.min = kwargs.pop('min', None)
63 self.min = kwargs.pop('min', None)
66 self.max = kwargs.pop('max', None)
64 self.max = kwargs.pop('max', None)
67 kwargs['default_value'] = self.min
65 kwargs['default_value'] = self.min
68 super(Version, self).__init__(*args, **kwargs)
66 super(Version, self).__init__(*args, **kwargs)
69
67
70 def validate(self, obj, value):
68 def validate(self, obj, value):
71 if self.min and V(value) < V(self.min):
69 if self.min and V(value) < V(self.min):
72 raise TraitError("bad version: %s < %s" % (value, self.min))
70 raise TraitError("bad version: %s < %s" % (value, self.min))
73 if self.max and (V(value) > V(self.max)):
71 if self.max and (V(value) > V(self.max)):
74 raise TraitError("bad version: %s > %s" % (value, self.max))
72 raise TraitError("bad version: %s > %s" % (value, self.max))
75
73
76
74
77 class RMessage(Reference):
75 class RMessage(Reference):
78 msg_id = Unicode()
76 msg_id = Unicode()
79 msg_type = Unicode()
77 msg_type = Unicode()
80 header = Dict()
78 header = Dict()
81 parent_header = Dict()
79 parent_header = Dict()
82 content = Dict()
80 content = Dict()
83
81
84 def check(self, d):
82 def check(self, d):
85 super(RMessage, self).check(d)
83 super(RMessage, self).check(d)
86 RHeader().check(self.header)
84 RHeader().check(self.header)
87 if self.parent_header:
85 if self.parent_header:
88 RHeader().check(self.parent_header)
86 RHeader().check(self.parent_header)
89
87
90 class RHeader(Reference):
88 class RHeader(Reference):
91 msg_id = Unicode()
89 msg_id = Unicode()
92 msg_type = Unicode()
90 msg_type = Unicode()
93 session = Unicode()
91 session = Unicode()
94 username = Unicode()
92 username = Unicode()
95 version = Version(min='5.0')
93 version = Version(min='5.0')
96
94
97 mime_pat = re.compile(r'^[\w\-\+\.]+/[\w\-\+\.]+$')
95 mime_pat = re.compile(r'^[\w\-\+\.]+/[\w\-\+\.]+$')
98
96
99 class MimeBundle(Reference):
97 class MimeBundle(Reference):
100 metadata = Dict()
98 metadata = Dict()
101 data = Dict()
99 data = Dict()
102 def _data_changed(self, name, old, new):
100 def _data_changed(self, name, old, new):
103 for k,v in iteritems(new):
101 for k,v in iteritems(new):
104 assert mime_pat.match(k)
102 assert mime_pat.match(k)
105 nt.assert_is_instance(v, string_types)
103 nt.assert_is_instance(v, string_types)
106
104
107 # shell replies
105 # shell replies
108
106
109 class ExecuteReply(Reference):
107 class ExecuteReply(Reference):
110 execution_count = Integer()
108 execution_count = Integer()
111 status = Enum((u'ok', u'error'))
109 status = Enum((u'ok', u'error'))
112
110
113 def check(self, d):
111 def check(self, d):
114 Reference.check(self, d)
112 Reference.check(self, d)
115 if d['status'] == 'ok':
113 if d['status'] == 'ok':
116 ExecuteReplyOkay().check(d)
114 ExecuteReplyOkay().check(d)
117 elif d['status'] == 'error':
115 elif d['status'] == 'error':
118 ExecuteReplyError().check(d)
116 ExecuteReplyError().check(d)
119
117
120
118
121 class ExecuteReplyOkay(Reference):
119 class ExecuteReplyOkay(Reference):
122 payload = List(Dict)
120 payload = List(Dict)
123 user_expressions = Dict()
121 user_expressions = Dict()
124
122
125
123
126 class ExecuteReplyError(Reference):
124 class ExecuteReplyError(Reference):
127 ename = Unicode()
125 ename = Unicode()
128 evalue = Unicode()
126 evalue = Unicode()
129 traceback = List(Unicode)
127 traceback = List(Unicode)
130
128
131
129
132 class InspectReply(MimeBundle):
130 class InspectReply(MimeBundle):
133 found = Bool()
131 found = Bool()
134
132
135
133
136 class ArgSpec(Reference):
134 class ArgSpec(Reference):
137 args = List(Unicode)
135 args = List(Unicode)
138 varargs = Unicode()
136 varargs = Unicode()
139 varkw = Unicode()
137 varkw = Unicode()
140 defaults = List()
138 defaults = List()
141
139
142
140
143 class Status(Reference):
141 class Status(Reference):
144 execution_state = Enum((u'busy', u'idle', u'starting'))
142 execution_state = Enum((u'busy', u'idle', u'starting'))
145
143
146
144
147 class CompleteReply(Reference):
145 class CompleteReply(Reference):
148 matches = List(Unicode)
146 matches = List(Unicode)
149 cursor_start = Integer()
147 cursor_start = Integer()
150 cursor_end = Integer()
148 cursor_end = Integer()
151 status = Unicode()
149 status = Unicode()
152
150
151 class LanguageInfo(Reference):
152 name = Unicode('python')
153 version = Unicode(sys.version.split()[0])
153
154
154 class KernelInfoReply(Reference):
155 class KernelInfoReply(Reference):
155 protocol_version = Version(min='5.0')
156 protocol_version = Version(min='5.0')
156 implementation = Unicode('ipython')
157 implementation = Unicode('ipython')
157 implementation_version = Version(min='2.1')
158 implementation_version = Version(min='2.1')
158 language_version = Version(min='2.7')
159 language = Unicode('python')
160 language_info = Dict()
159 language_info = Dict()
161 banner = Unicode()
160 banner = Unicode()
161
162 def check(self, d):
163 Reference.check(self, d)
164 LanguageInfo().check(d['language_info'])
162
165
163
166
164 class IsCompleteReply(Reference):
167 class IsCompleteReply(Reference):
165 status = Enum((u'complete', u'incomplete', u'invalid', u'unknown'))
168 status = Enum((u'complete', u'incomplete', u'invalid', u'unknown'))
166
169
167 def check(self, d):
170 def check(self, d):
168 Reference.check(self, d)
171 Reference.check(self, d)
169 if d['status'] == 'incomplete':
172 if d['status'] == 'incomplete':
170 IsCompleteReplyIncomplete().check(d)
173 IsCompleteReplyIncomplete().check(d)
171
174
172 class IsCompleteReplyIncomplete(Reference):
175 class IsCompleteReplyIncomplete(Reference):
173 indent = Unicode()
176 indent = Unicode()
174
177
175
178
176 # IOPub messages
179 # IOPub messages
177
180
178 class ExecuteInput(Reference):
181 class ExecuteInput(Reference):
179 code = Unicode()
182 code = Unicode()
180 execution_count = Integer()
183 execution_count = Integer()
181
184
182
185
183 Error = ExecuteReplyError
186 Error = ExecuteReplyError
184
187
185
188
186 class Stream(Reference):
189 class Stream(Reference):
187 name = Enum((u'stdout', u'stderr'))
190 name = Enum((u'stdout', u'stderr'))
188 text = Unicode()
191 text = Unicode()
189
192
190
193
191 class DisplayData(MimeBundle):
194 class DisplayData(MimeBundle):
192 pass
195 pass
193
196
194
197
195 class ExecuteResult(MimeBundle):
198 class ExecuteResult(MimeBundle):
196 execution_count = Integer()
199 execution_count = Integer()
197
200
198
201
199 references = {
202 references = {
200 'execute_reply' : ExecuteReply(),
203 'execute_reply' : ExecuteReply(),
201 'inspect_reply' : InspectReply(),
204 'inspect_reply' : InspectReply(),
202 'status' : Status(),
205 'status' : Status(),
203 'complete_reply' : CompleteReply(),
206 'complete_reply' : CompleteReply(),
204 'kernel_info_reply': KernelInfoReply(),
207 'kernel_info_reply': KernelInfoReply(),
205 'is_complete_reply': IsCompleteReply(),
208 'is_complete_reply': IsCompleteReply(),
206 'execute_input' : ExecuteInput(),
209 'execute_input' : ExecuteInput(),
207 'execute_result' : ExecuteResult(),
210 'execute_result' : ExecuteResult(),
208 'error' : Error(),
211 'error' : Error(),
209 'stream' : Stream(),
212 'stream' : Stream(),
210 'display_data' : DisplayData(),
213 'display_data' : DisplayData(),
211 'header' : RHeader(),
214 'header' : RHeader(),
212 }
215 }
213 """
216 """
214 Specifications of `content` part of the reply messages.
217 Specifications of `content` part of the reply messages.
215 """
218 """
216
219
217
220
218 def validate_message(msg, msg_type=None, parent=None):
221 def validate_message(msg, msg_type=None, parent=None):
219 """validate a message
222 """validate a message
220
223
221 This is a generator, and must be iterated through to actually
224 This is a generator, and must be iterated through to actually
222 trigger each test.
225 trigger each test.
223
226
224 If msg_type and/or parent are given, the msg_type and/or parent msg_id
227 If msg_type and/or parent are given, the msg_type and/or parent msg_id
225 are compared with the given values.
228 are compared with the given values.
226 """
229 """
227 RMessage().check(msg)
230 RMessage().check(msg)
228 if msg_type:
231 if msg_type:
229 nt.assert_equal(msg['msg_type'], msg_type)
232 nt.assert_equal(msg['msg_type'], msg_type)
230 if parent:
233 if parent:
231 nt.assert_equal(msg['parent_header']['msg_id'], parent)
234 nt.assert_equal(msg['parent_header']['msg_id'], parent)
232 content = msg['content']
235 content = msg['content']
233 ref = references[msg['msg_type']]
236 ref = references[msg['msg_type']]
234 ref.check(content)
237 ref.check(content)
235
238
236
239
237 #-----------------------------------------------------------------------------
240 #-----------------------------------------------------------------------------
238 # Tests
241 # Tests
239 #-----------------------------------------------------------------------------
242 #-----------------------------------------------------------------------------
240
243
241 # Shell channel
244 # Shell channel
242
245
243 def test_execute():
246 def test_execute():
244 flush_channels()
247 flush_channels()
245
248
246 msg_id = KC.execute(code='x=1')
249 msg_id = KC.execute(code='x=1')
247 reply = KC.get_shell_msg(timeout=TIMEOUT)
250 reply = KC.get_shell_msg(timeout=TIMEOUT)
248 validate_message(reply, 'execute_reply', msg_id)
251 validate_message(reply, 'execute_reply', msg_id)
249
252
250
253
251 def test_execute_silent():
254 def test_execute_silent():
252 flush_channels()
255 flush_channels()
253 msg_id, reply = execute(code='x=1', silent=True)
256 msg_id, reply = execute(code='x=1', silent=True)
254
257
255 # flush status=idle
258 # flush status=idle
256 status = KC.iopub_channel.get_msg(timeout=TIMEOUT)
259 status = KC.iopub_channel.get_msg(timeout=TIMEOUT)
257 validate_message(status, 'status', msg_id)
260 validate_message(status, 'status', msg_id)
258 nt.assert_equal(status['content']['execution_state'], 'idle')
261 nt.assert_equal(status['content']['execution_state'], 'idle')
259
262
260 nt.assert_raises(Empty, KC.iopub_channel.get_msg, timeout=0.1)
263 nt.assert_raises(Empty, KC.iopub_channel.get_msg, timeout=0.1)
261 count = reply['execution_count']
264 count = reply['execution_count']
262
265
263 msg_id, reply = execute(code='x=2', silent=True)
266 msg_id, reply = execute(code='x=2', silent=True)
264
267
265 # flush status=idle
268 # flush status=idle
266 status = KC.iopub_channel.get_msg(timeout=TIMEOUT)
269 status = KC.iopub_channel.get_msg(timeout=TIMEOUT)
267 validate_message(status, 'status', msg_id)
270 validate_message(status, 'status', msg_id)
268 nt.assert_equal(status['content']['execution_state'], 'idle')
271 nt.assert_equal(status['content']['execution_state'], 'idle')
269
272
270 nt.assert_raises(Empty, KC.iopub_channel.get_msg, timeout=0.1)
273 nt.assert_raises(Empty, KC.iopub_channel.get_msg, timeout=0.1)
271 count_2 = reply['execution_count']
274 count_2 = reply['execution_count']
272 nt.assert_equal(count_2, count)
275 nt.assert_equal(count_2, count)
273
276
274
277
275 def test_execute_error():
278 def test_execute_error():
276 flush_channels()
279 flush_channels()
277
280
278 msg_id, reply = execute(code='1/0')
281 msg_id, reply = execute(code='1/0')
279 nt.assert_equal(reply['status'], 'error')
282 nt.assert_equal(reply['status'], 'error')
280 nt.assert_equal(reply['ename'], 'ZeroDivisionError')
283 nt.assert_equal(reply['ename'], 'ZeroDivisionError')
281
284
282 error = KC.iopub_channel.get_msg(timeout=TIMEOUT)
285 error = KC.iopub_channel.get_msg(timeout=TIMEOUT)
283 validate_message(error, 'error', msg_id)
286 validate_message(error, 'error', msg_id)
284
287
285
288
286 def test_execute_inc():
289 def test_execute_inc():
287 """execute request should increment execution_count"""
290 """execute request should increment execution_count"""
288 flush_channels()
291 flush_channels()
289
292
290 msg_id, reply = execute(code='x=1')
293 msg_id, reply = execute(code='x=1')
291 count = reply['execution_count']
294 count = reply['execution_count']
292
295
293 flush_channels()
296 flush_channels()
294
297
295 msg_id, reply = execute(code='x=2')
298 msg_id, reply = execute(code='x=2')
296 count_2 = reply['execution_count']
299 count_2 = reply['execution_count']
297 nt.assert_equal(count_2, count+1)
300 nt.assert_equal(count_2, count+1)
298
301
299
302
300 def test_user_expressions():
303 def test_user_expressions():
301 flush_channels()
304 flush_channels()
302
305
303 msg_id, reply = execute(code='x=1', user_expressions=dict(foo='x+1'))
306 msg_id, reply = execute(code='x=1', user_expressions=dict(foo='x+1'))
304 user_expressions = reply['user_expressions']
307 user_expressions = reply['user_expressions']
305 nt.assert_equal(user_expressions, {u'foo': {
308 nt.assert_equal(user_expressions, {u'foo': {
306 u'status': u'ok',
309 u'status': u'ok',
307 u'data': {u'text/plain': u'2'},
310 u'data': {u'text/plain': u'2'},
308 u'metadata': {},
311 u'metadata': {},
309 }})
312 }})
310
313
311
314
312 def test_user_expressions_fail():
315 def test_user_expressions_fail():
313 flush_channels()
316 flush_channels()
314
317
315 msg_id, reply = execute(code='x=0', user_expressions=dict(foo='nosuchname'))
318 msg_id, reply = execute(code='x=0', user_expressions=dict(foo='nosuchname'))
316 user_expressions = reply['user_expressions']
319 user_expressions = reply['user_expressions']
317 foo = user_expressions['foo']
320 foo = user_expressions['foo']
318 nt.assert_equal(foo['status'], 'error')
321 nt.assert_equal(foo['status'], 'error')
319 nt.assert_equal(foo['ename'], 'NameError')
322 nt.assert_equal(foo['ename'], 'NameError')
320
323
321
324
322 def test_oinfo():
325 def test_oinfo():
323 flush_channels()
326 flush_channels()
324
327
325 msg_id = KC.inspect('a')
328 msg_id = KC.inspect('a')
326 reply = KC.get_shell_msg(timeout=TIMEOUT)
329 reply = KC.get_shell_msg(timeout=TIMEOUT)
327 validate_message(reply, 'inspect_reply', msg_id)
330 validate_message(reply, 'inspect_reply', msg_id)
328
331
329
332
330 def test_oinfo_found():
333 def test_oinfo_found():
331 flush_channels()
334 flush_channels()
332
335
333 msg_id, reply = execute(code='a=5')
336 msg_id, reply = execute(code='a=5')
334
337
335 msg_id = KC.inspect('a')
338 msg_id = KC.inspect('a')
336 reply = KC.get_shell_msg(timeout=TIMEOUT)
339 reply = KC.get_shell_msg(timeout=TIMEOUT)
337 validate_message(reply, 'inspect_reply', msg_id)
340 validate_message(reply, 'inspect_reply', msg_id)
338 content = reply['content']
341 content = reply['content']
339 assert content['found']
342 assert content['found']
340 text = content['data']['text/plain']
343 text = content['data']['text/plain']
341 nt.assert_in('Type:', text)
344 nt.assert_in('Type:', text)
342 nt.assert_in('Docstring:', text)
345 nt.assert_in('Docstring:', text)
343
346
344
347
345 def test_oinfo_detail():
348 def test_oinfo_detail():
346 flush_channels()
349 flush_channels()
347
350
348 msg_id, reply = execute(code='ip=get_ipython()')
351 msg_id, reply = execute(code='ip=get_ipython()')
349
352
350 msg_id = KC.inspect('ip.object_inspect', cursor_pos=10, detail_level=1)
353 msg_id = KC.inspect('ip.object_inspect', cursor_pos=10, detail_level=1)
351 reply = KC.get_shell_msg(timeout=TIMEOUT)
354 reply = KC.get_shell_msg(timeout=TIMEOUT)
352 validate_message(reply, 'inspect_reply', msg_id)
355 validate_message(reply, 'inspect_reply', msg_id)
353 content = reply['content']
356 content = reply['content']
354 assert content['found']
357 assert content['found']
355 text = content['data']['text/plain']
358 text = content['data']['text/plain']
356 nt.assert_in('Definition:', text)
359 nt.assert_in('Definition:', text)
357 nt.assert_in('Source:', text)
360 nt.assert_in('Source:', text)
358
361
359
362
360 def test_oinfo_not_found():
363 def test_oinfo_not_found():
361 flush_channels()
364 flush_channels()
362
365
363 msg_id = KC.inspect('dne')
366 msg_id = KC.inspect('dne')
364 reply = KC.get_shell_msg(timeout=TIMEOUT)
367 reply = KC.get_shell_msg(timeout=TIMEOUT)
365 validate_message(reply, 'inspect_reply', msg_id)
368 validate_message(reply, 'inspect_reply', msg_id)
366 content = reply['content']
369 content = reply['content']
367 nt.assert_false(content['found'])
370 nt.assert_false(content['found'])
368
371
369
372
370 def test_complete():
373 def test_complete():
371 flush_channels()
374 flush_channels()
372
375
373 msg_id, reply = execute(code="alpha = albert = 5")
376 msg_id, reply = execute(code="alpha = albert = 5")
374
377
375 msg_id = KC.complete('al', 2)
378 msg_id = KC.complete('al', 2)
376 reply = KC.get_shell_msg(timeout=TIMEOUT)
379 reply = KC.get_shell_msg(timeout=TIMEOUT)
377 validate_message(reply, 'complete_reply', msg_id)
380 validate_message(reply, 'complete_reply', msg_id)
378 matches = reply['content']['matches']
381 matches = reply['content']['matches']
379 for name in ('alpha', 'albert'):
382 for name in ('alpha', 'albert'):
380 nt.assert_in(name, matches)
383 nt.assert_in(name, matches)
381
384
382
385
383 def test_kernel_info_request():
386 def test_kernel_info_request():
384 flush_channels()
387 flush_channels()
385
388
386 msg_id = KC.kernel_info()
389 msg_id = KC.kernel_info()
387 reply = KC.get_shell_msg(timeout=TIMEOUT)
390 reply = KC.get_shell_msg(timeout=TIMEOUT)
388 validate_message(reply, 'kernel_info_reply', msg_id)
391 validate_message(reply, 'kernel_info_reply', msg_id)
389
392
390
393
391 def test_single_payload():
394 def test_single_payload():
392 flush_channels()
395 flush_channels()
393 msg_id, reply = execute(code="for i in range(3):\n"+
396 msg_id, reply = execute(code="for i in range(3):\n"+
394 " x=range?\n")
397 " x=range?\n")
395 payload = reply['payload']
398 payload = reply['payload']
396 next_input_pls = [pl for pl in payload if pl["source"] == "set_next_input"]
399 next_input_pls = [pl for pl in payload if pl["source"] == "set_next_input"]
397 nt.assert_equal(len(next_input_pls), 1)
400 nt.assert_equal(len(next_input_pls), 1)
398
401
399 def test_is_complete():
402 def test_is_complete():
400 flush_channels()
403 flush_channels()
401
404
402 msg_id = KC.is_complete("a = 1")
405 msg_id = KC.is_complete("a = 1")
403 reply = KC.get_shell_msg(timeout=TIMEOUT)
406 reply = KC.get_shell_msg(timeout=TIMEOUT)
404 validate_message(reply, 'is_complete_reply', msg_id)
407 validate_message(reply, 'is_complete_reply', msg_id)
405
408
406 # IOPub channel
409 # IOPub channel
407
410
408
411
409 def test_stream():
412 def test_stream():
410 flush_channels()
413 flush_channels()
411
414
412 msg_id, reply = execute("print('hi')")
415 msg_id, reply = execute("print('hi')")
413
416
414 stdout = KC.iopub_channel.get_msg(timeout=TIMEOUT)
417 stdout = KC.iopub_channel.get_msg(timeout=TIMEOUT)
415 validate_message(stdout, 'stream', msg_id)
418 validate_message(stdout, 'stream', msg_id)
416 content = stdout['content']
419 content = stdout['content']
417 nt.assert_equal(content['text'], u'hi\n')
420 nt.assert_equal(content['text'], u'hi\n')
418
421
419
422
420 def test_display_data():
423 def test_display_data():
421 flush_channels()
424 flush_channels()
422
425
423 msg_id, reply = execute("from IPython.core.display import display; display(1)")
426 msg_id, reply = execute("from IPython.core.display import display; display(1)")
424
427
425 display = KC.iopub_channel.get_msg(timeout=TIMEOUT)
428 display = KC.iopub_channel.get_msg(timeout=TIMEOUT)
426 validate_message(display, 'display_data', parent=msg_id)
429 validate_message(display, 'display_data', parent=msg_id)
427 data = display['content']['data']
430 data = display['content']['data']
428 nt.assert_equal(data['text/plain'], u'1')
431 nt.assert_equal(data['text/plain'], u'1')
429
432
@@ -1,330 +1,331 b''
1 """The IPython kernel implementation"""
1 """The IPython kernel implementation"""
2
2
3 import getpass
3 import getpass
4 import sys
4 import sys
5 import traceback
5 import traceback
6
6
7 from IPython.core import release
7 from IPython.core import release
8 from IPython.html.widgets import Widget
8 from IPython.html.widgets import Widget
9 from IPython.utils.py3compat import builtin_mod, PY3
9 from IPython.utils.py3compat import builtin_mod, PY3
10 from IPython.utils.tokenutil import token_at_cursor, line_at_cursor
10 from IPython.utils.tokenutil import token_at_cursor, line_at_cursor
11 from IPython.utils.traitlets import Instance, Type, Any
11 from IPython.utils.traitlets import Instance, Type, Any
12 from IPython.utils.decorators import undoc
12 from IPython.utils.decorators import undoc
13
13
14 from ..comm import CommManager
14 from ..comm import CommManager
15 from .kernelbase import Kernel as KernelBase
15 from .kernelbase import Kernel as KernelBase
16 from .serialize import serialize_object, unpack_apply_message
16 from .serialize import serialize_object, unpack_apply_message
17 from .zmqshell import ZMQInteractiveShell
17 from .zmqshell import ZMQInteractiveShell
18
18
19 class IPythonKernel(KernelBase):
19 class IPythonKernel(KernelBase):
20 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
20 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
21 shell_class = Type(ZMQInteractiveShell)
21 shell_class = Type(ZMQInteractiveShell)
22
22
23 user_module = Any()
23 user_module = Any()
24 def _user_module_changed(self, name, old, new):
24 def _user_module_changed(self, name, old, new):
25 if self.shell is not None:
25 if self.shell is not None:
26 self.shell.user_module = new
26 self.shell.user_module = new
27
27
28 user_ns = Instance(dict, args=None, allow_none=True)
28 user_ns = Instance(dict, args=None, allow_none=True)
29 def _user_ns_changed(self, name, old, new):
29 def _user_ns_changed(self, name, old, new):
30 if self.shell is not None:
30 if self.shell is not None:
31 self.shell.user_ns = new
31 self.shell.user_ns = new
32 self.shell.init_user_ns()
32 self.shell.init_user_ns()
33
33
34 # A reference to the Python builtin 'raw_input' function.
34 # A reference to the Python builtin 'raw_input' function.
35 # (i.e., __builtin__.raw_input for Python 2.7, builtins.input for Python 3)
35 # (i.e., __builtin__.raw_input for Python 2.7, builtins.input for Python 3)
36 _sys_raw_input = Any()
36 _sys_raw_input = Any()
37 _sys_eval_input = Any()
37 _sys_eval_input = Any()
38
38
39 def __init__(self, **kwargs):
39 def __init__(self, **kwargs):
40 super(IPythonKernel, self).__init__(**kwargs)
40 super(IPythonKernel, self).__init__(**kwargs)
41
41
42 # Initialize the InteractiveShell subclass
42 # Initialize the InteractiveShell subclass
43 self.shell = self.shell_class.instance(parent=self,
43 self.shell = self.shell_class.instance(parent=self,
44 profile_dir = self.profile_dir,
44 profile_dir = self.profile_dir,
45 user_module = self.user_module,
45 user_module = self.user_module,
46 user_ns = self.user_ns,
46 user_ns = self.user_ns,
47 kernel = self,
47 kernel = self,
48 )
48 )
49 self.shell.displayhook.session = self.session
49 self.shell.displayhook.session = self.session
50 self.shell.displayhook.pub_socket = self.iopub_socket
50 self.shell.displayhook.pub_socket = self.iopub_socket
51 self.shell.displayhook.topic = self._topic('execute_result')
51 self.shell.displayhook.topic = self._topic('execute_result')
52 self.shell.display_pub.session = self.session
52 self.shell.display_pub.session = self.session
53 self.shell.display_pub.pub_socket = self.iopub_socket
53 self.shell.display_pub.pub_socket = self.iopub_socket
54 self.shell.data_pub.session = self.session
54 self.shell.data_pub.session = self.session
55 self.shell.data_pub.pub_socket = self.iopub_socket
55 self.shell.data_pub.pub_socket = self.iopub_socket
56
56
57 # TMP - hack while developing
57 # TMP - hack while developing
58 self.shell._reply_content = None
58 self.shell._reply_content = None
59
59
60 self.comm_manager = CommManager(shell=self.shell, parent=self,
60 self.comm_manager = CommManager(shell=self.shell, parent=self,
61 kernel=self)
61 kernel=self)
62 self.comm_manager.register_target('ipython.widget', Widget.handle_comm_opened)
62 self.comm_manager.register_target('ipython.widget', Widget.handle_comm_opened)
63
63
64 self.shell.configurables.append(self.comm_manager)
64 self.shell.configurables.append(self.comm_manager)
65 comm_msg_types = [ 'comm_open', 'comm_msg', 'comm_close' ]
65 comm_msg_types = [ 'comm_open', 'comm_msg', 'comm_close' ]
66 for msg_type in comm_msg_types:
66 for msg_type in comm_msg_types:
67 self.shell_handlers[msg_type] = getattr(self.comm_manager, msg_type)
67 self.shell_handlers[msg_type] = getattr(self.comm_manager, msg_type)
68
68
69 # Kernel info fields
69 # Kernel info fields
70 implementation = 'ipython'
70 implementation = 'ipython'
71 implementation_version = release.version
71 implementation_version = release.version
72 language = 'python'
72 language_info = {
73 language_version = sys.version.split()[0]
73 'name': 'python',
74 language_info = {'mimetype': 'text/x-python',
74 'version': sys.version.split()[0],
75 'mimetype': 'text/x-python',
75 'codemirror_mode': {'name': 'ipython',
76 'codemirror_mode': {'name': 'ipython',
76 'version': sys.version_info[0]},
77 'version': sys.version_info[0]},
77 'pygments_lexer': 'ipython%d' % (3 if PY3 else 2),
78 'pygments_lexer': 'ipython%d' % (3 if PY3 else 2),
78 'nbconvert_exporter': 'python',
79 'nbconvert_exporter': 'python',
79 'file_extension': '.py'
80 'file_extension': '.py'
80 }
81 }
81 @property
82 @property
82 def banner(self):
83 def banner(self):
83 return self.shell.banner
84 return self.shell.banner
84
85
85 def start(self):
86 def start(self):
86 self.shell.exit_now = False
87 self.shell.exit_now = False
87 super(IPythonKernel, self).start()
88 super(IPythonKernel, self).start()
88
89
89 def set_parent(self, ident, parent):
90 def set_parent(self, ident, parent):
90 """Overridden from parent to tell the display hook and output streams
91 """Overridden from parent to tell the display hook and output streams
91 about the parent message.
92 about the parent message.
92 """
93 """
93 super(IPythonKernel, self).set_parent(ident, parent)
94 super(IPythonKernel, self).set_parent(ident, parent)
94 self.shell.set_parent(parent)
95 self.shell.set_parent(parent)
95
96
96 def _forward_input(self, allow_stdin=False):
97 def _forward_input(self, allow_stdin=False):
97 """Forward raw_input and getpass to the current frontend.
98 """Forward raw_input and getpass to the current frontend.
98
99
99 via input_request
100 via input_request
100 """
101 """
101 self._allow_stdin = allow_stdin
102 self._allow_stdin = allow_stdin
102
103
103 if PY3:
104 if PY3:
104 self._sys_raw_input = builtin_mod.input
105 self._sys_raw_input = builtin_mod.input
105 builtin_mod.input = self.raw_input
106 builtin_mod.input = self.raw_input
106 else:
107 else:
107 self._sys_raw_input = builtin_mod.raw_input
108 self._sys_raw_input = builtin_mod.raw_input
108 self._sys_eval_input = builtin_mod.input
109 self._sys_eval_input = builtin_mod.input
109 builtin_mod.raw_input = self.raw_input
110 builtin_mod.raw_input = self.raw_input
110 builtin_mod.input = lambda prompt='': eval(self.raw_input(prompt))
111 builtin_mod.input = lambda prompt='': eval(self.raw_input(prompt))
111 self._save_getpass = getpass.getpass
112 self._save_getpass = getpass.getpass
112 getpass.getpass = self.getpass
113 getpass.getpass = self.getpass
113
114
114 def _restore_input(self):
115 def _restore_input(self):
115 """Restore raw_input, getpass"""
116 """Restore raw_input, getpass"""
116 if PY3:
117 if PY3:
117 builtin_mod.input = self._sys_raw_input
118 builtin_mod.input = self._sys_raw_input
118 else:
119 else:
119 builtin_mod.raw_input = self._sys_raw_input
120 builtin_mod.raw_input = self._sys_raw_input
120 builtin_mod.input = self._sys_eval_input
121 builtin_mod.input = self._sys_eval_input
121
122
122 getpass.getpass = self._save_getpass
123 getpass.getpass = self._save_getpass
123
124
124 @property
125 @property
125 def execution_count(self):
126 def execution_count(self):
126 return self.shell.execution_count
127 return self.shell.execution_count
127
128
128 @execution_count.setter
129 @execution_count.setter
129 def execution_count(self, value):
130 def execution_count(self, value):
130 # Ignore the incrememnting done by KernelBase, in favour of our shell's
131 # Ignore the incrememnting done by KernelBase, in favour of our shell's
131 # execution counter.
132 # execution counter.
132 pass
133 pass
133
134
134 def do_execute(self, code, silent, store_history=True,
135 def do_execute(self, code, silent, store_history=True,
135 user_expressions=None, allow_stdin=False):
136 user_expressions=None, allow_stdin=False):
136 shell = self.shell # we'll need this a lot here
137 shell = self.shell # we'll need this a lot here
137
138
138 self._forward_input(allow_stdin)
139 self._forward_input(allow_stdin)
139
140
140 reply_content = {}
141 reply_content = {}
141 # FIXME: the shell calls the exception handler itself.
142 # FIXME: the shell calls the exception handler itself.
142 shell._reply_content = None
143 shell._reply_content = None
143 try:
144 try:
144 shell.run_cell(code, store_history=store_history, silent=silent)
145 shell.run_cell(code, store_history=store_history, silent=silent)
145 except:
146 except:
146 status = u'error'
147 status = u'error'
147 # FIXME: this code right now isn't being used yet by default,
148 # FIXME: this code right now isn't being used yet by default,
148 # because the run_cell() call above directly fires off exception
149 # because the run_cell() call above directly fires off exception
149 # reporting. This code, therefore, is only active in the scenario
150 # reporting. This code, therefore, is only active in the scenario
150 # where runlines itself has an unhandled exception. We need to
151 # where runlines itself has an unhandled exception. We need to
151 # uniformize this, for all exception construction to come from a
152 # uniformize this, for all exception construction to come from a
152 # single location in the codbase.
153 # single location in the codbase.
153 etype, evalue, tb = sys.exc_info()
154 etype, evalue, tb = sys.exc_info()
154 tb_list = traceback.format_exception(etype, evalue, tb)
155 tb_list = traceback.format_exception(etype, evalue, tb)
155 reply_content.update(shell._showtraceback(etype, evalue, tb_list))
156 reply_content.update(shell._showtraceback(etype, evalue, tb_list))
156 else:
157 else:
157 status = u'ok'
158 status = u'ok'
158 finally:
159 finally:
159 self._restore_input()
160 self._restore_input()
160
161
161 reply_content[u'status'] = status
162 reply_content[u'status'] = status
162
163
163 # Return the execution counter so clients can display prompts
164 # Return the execution counter so clients can display prompts
164 reply_content['execution_count'] = shell.execution_count - 1
165 reply_content['execution_count'] = shell.execution_count - 1
165
166
166 # FIXME - fish exception info out of shell, possibly left there by
167 # FIXME - fish exception info out of shell, possibly left there by
167 # runlines. We'll need to clean up this logic later.
168 # runlines. We'll need to clean up this logic later.
168 if shell._reply_content is not None:
169 if shell._reply_content is not None:
169 reply_content.update(shell._reply_content)
170 reply_content.update(shell._reply_content)
170 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='execute')
171 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='execute')
171 reply_content['engine_info'] = e_info
172 reply_content['engine_info'] = e_info
172 # reset after use
173 # reset after use
173 shell._reply_content = None
174 shell._reply_content = None
174
175
175 if 'traceback' in reply_content:
176 if 'traceback' in reply_content:
176 self.log.info("Exception in execute request:\n%s", '\n'.join(reply_content['traceback']))
177 self.log.info("Exception in execute request:\n%s", '\n'.join(reply_content['traceback']))
177
178
178
179
179 # At this point, we can tell whether the main code execution succeeded
180 # At this point, we can tell whether the main code execution succeeded
180 # or not. If it did, we proceed to evaluate user_expressions
181 # or not. If it did, we proceed to evaluate user_expressions
181 if reply_content['status'] == 'ok':
182 if reply_content['status'] == 'ok':
182 reply_content[u'user_expressions'] = \
183 reply_content[u'user_expressions'] = \
183 shell.user_expressions(user_expressions or {})
184 shell.user_expressions(user_expressions or {})
184 else:
185 else:
185 # If there was an error, don't even try to compute expressions
186 # If there was an error, don't even try to compute expressions
186 reply_content[u'user_expressions'] = {}
187 reply_content[u'user_expressions'] = {}
187
188
188 # Payloads should be retrieved regardless of outcome, so we can both
189 # Payloads should be retrieved regardless of outcome, so we can both
189 # recover partial output (that could have been generated early in a
190 # recover partial output (that could have been generated early in a
190 # block, before an error) and clear the payload system always.
191 # block, before an error) and clear the payload system always.
191 reply_content[u'payload'] = shell.payload_manager.read_payload()
192 reply_content[u'payload'] = shell.payload_manager.read_payload()
192 # Be agressive about clearing the payload because we don't want
193 # Be agressive about clearing the payload because we don't want
193 # it to sit in memory until the next execute_request comes in.
194 # it to sit in memory until the next execute_request comes in.
194 shell.payload_manager.clear_payload()
195 shell.payload_manager.clear_payload()
195
196
196 return reply_content
197 return reply_content
197
198
198 def do_complete(self, code, cursor_pos):
199 def do_complete(self, code, cursor_pos):
199 # FIXME: IPython completers currently assume single line,
200 # FIXME: IPython completers currently assume single line,
200 # but completion messages give multi-line context
201 # but completion messages give multi-line context
201 # For now, extract line from cell, based on cursor_pos:
202 # For now, extract line from cell, based on cursor_pos:
202 if cursor_pos is None:
203 if cursor_pos is None:
203 cursor_pos = len(code)
204 cursor_pos = len(code)
204 line, offset = line_at_cursor(code, cursor_pos)
205 line, offset = line_at_cursor(code, cursor_pos)
205 line_cursor = cursor_pos - offset
206 line_cursor = cursor_pos - offset
206
207
207 txt, matches = self.shell.complete('', line, line_cursor)
208 txt, matches = self.shell.complete('', line, line_cursor)
208 return {'matches' : matches,
209 return {'matches' : matches,
209 'cursor_end' : cursor_pos,
210 'cursor_end' : cursor_pos,
210 'cursor_start' : cursor_pos - len(txt),
211 'cursor_start' : cursor_pos - len(txt),
211 'metadata' : {},
212 'metadata' : {},
212 'status' : 'ok'}
213 'status' : 'ok'}
213
214
214 def do_inspect(self, code, cursor_pos, detail_level=0):
215 def do_inspect(self, code, cursor_pos, detail_level=0):
215 name = token_at_cursor(code, cursor_pos)
216 name = token_at_cursor(code, cursor_pos)
216 info = self.shell.object_inspect(name)
217 info = self.shell.object_inspect(name)
217
218
218 reply_content = {'status' : 'ok'}
219 reply_content = {'status' : 'ok'}
219 reply_content['data'] = data = {}
220 reply_content['data'] = data = {}
220 reply_content['metadata'] = {}
221 reply_content['metadata'] = {}
221 reply_content['found'] = info['found']
222 reply_content['found'] = info['found']
222 if info['found']:
223 if info['found']:
223 info_text = self.shell.object_inspect_text(
224 info_text = self.shell.object_inspect_text(
224 name,
225 name,
225 detail_level=detail_level,
226 detail_level=detail_level,
226 )
227 )
227 data['text/plain'] = info_text
228 data['text/plain'] = info_text
228
229
229 return reply_content
230 return reply_content
230
231
231 def do_history(self, hist_access_type, output, raw, session=None, start=None,
232 def do_history(self, hist_access_type, output, raw, session=None, start=None,
232 stop=None, n=None, pattern=None, unique=False):
233 stop=None, n=None, pattern=None, unique=False):
233 if hist_access_type == 'tail':
234 if hist_access_type == 'tail':
234 hist = self.shell.history_manager.get_tail(n, raw=raw, output=output,
235 hist = self.shell.history_manager.get_tail(n, raw=raw, output=output,
235 include_latest=True)
236 include_latest=True)
236
237
237 elif hist_access_type == 'range':
238 elif hist_access_type == 'range':
238 hist = self.shell.history_manager.get_range(session, start, stop,
239 hist = self.shell.history_manager.get_range(session, start, stop,
239 raw=raw, output=output)
240 raw=raw, output=output)
240
241
241 elif hist_access_type == 'search':
242 elif hist_access_type == 'search':
242 hist = self.shell.history_manager.search(
243 hist = self.shell.history_manager.search(
243 pattern, raw=raw, output=output, n=n, unique=unique)
244 pattern, raw=raw, output=output, n=n, unique=unique)
244 else:
245 else:
245 hist = []
246 hist = []
246
247
247 return {'history' : list(hist)}
248 return {'history' : list(hist)}
248
249
249 def do_shutdown(self, restart):
250 def do_shutdown(self, restart):
250 self.shell.exit_now = True
251 self.shell.exit_now = True
251 return dict(status='ok', restart=restart)
252 return dict(status='ok', restart=restart)
252
253
253 def do_is_complete(self, code):
254 def do_is_complete(self, code):
254 status, indent_spaces = self.shell.input_transformer_manager.check_complete(code)
255 status, indent_spaces = self.shell.input_transformer_manager.check_complete(code)
255 r = {'status': status}
256 r = {'status': status}
256 if status == 'incomplete':
257 if status == 'incomplete':
257 r['indent'] = ' ' * indent_spaces
258 r['indent'] = ' ' * indent_spaces
258 return r
259 return r
259
260
260 def do_apply(self, content, bufs, msg_id, reply_metadata):
261 def do_apply(self, content, bufs, msg_id, reply_metadata):
261 shell = self.shell
262 shell = self.shell
262 try:
263 try:
263 working = shell.user_ns
264 working = shell.user_ns
264
265
265 prefix = "_"+str(msg_id).replace("-","")+"_"
266 prefix = "_"+str(msg_id).replace("-","")+"_"
266
267
267 f,args,kwargs = unpack_apply_message(bufs, working, copy=False)
268 f,args,kwargs = unpack_apply_message(bufs, working, copy=False)
268
269
269 fname = getattr(f, '__name__', 'f')
270 fname = getattr(f, '__name__', 'f')
270
271
271 fname = prefix+"f"
272 fname = prefix+"f"
272 argname = prefix+"args"
273 argname = prefix+"args"
273 kwargname = prefix+"kwargs"
274 kwargname = prefix+"kwargs"
274 resultname = prefix+"result"
275 resultname = prefix+"result"
275
276
276 ns = { fname : f, argname : args, kwargname : kwargs , resultname : None }
277 ns = { fname : f, argname : args, kwargname : kwargs , resultname : None }
277 # print ns
278 # print ns
278 working.update(ns)
279 working.update(ns)
279 code = "%s = %s(*%s,**%s)" % (resultname, fname, argname, kwargname)
280 code = "%s = %s(*%s,**%s)" % (resultname, fname, argname, kwargname)
280 try:
281 try:
281 exec(code, shell.user_global_ns, shell.user_ns)
282 exec(code, shell.user_global_ns, shell.user_ns)
282 result = working.get(resultname)
283 result = working.get(resultname)
283 finally:
284 finally:
284 for key in ns:
285 for key in ns:
285 working.pop(key)
286 working.pop(key)
286
287
287 result_buf = serialize_object(result,
288 result_buf = serialize_object(result,
288 buffer_threshold=self.session.buffer_threshold,
289 buffer_threshold=self.session.buffer_threshold,
289 item_threshold=self.session.item_threshold,
290 item_threshold=self.session.item_threshold,
290 )
291 )
291
292
292 except:
293 except:
293 # invoke IPython traceback formatting
294 # invoke IPython traceback formatting
294 shell.showtraceback()
295 shell.showtraceback()
295 # FIXME - fish exception info out of shell, possibly left there by
296 # FIXME - fish exception info out of shell, possibly left there by
296 # run_code. We'll need to clean up this logic later.
297 # run_code. We'll need to clean up this logic later.
297 reply_content = {}
298 reply_content = {}
298 if shell._reply_content is not None:
299 if shell._reply_content is not None:
299 reply_content.update(shell._reply_content)
300 reply_content.update(shell._reply_content)
300 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='apply')
301 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='apply')
301 reply_content['engine_info'] = e_info
302 reply_content['engine_info'] = e_info
302 # reset after use
303 # reset after use
303 shell._reply_content = None
304 shell._reply_content = None
304
305
305 self.send_response(self.iopub_socket, u'error', reply_content,
306 self.send_response(self.iopub_socket, u'error', reply_content,
306 ident=self._topic('error'))
307 ident=self._topic('error'))
307 self.log.info("Exception in apply request:\n%s", '\n'.join(reply_content['traceback']))
308 self.log.info("Exception in apply request:\n%s", '\n'.join(reply_content['traceback']))
308 result_buf = []
309 result_buf = []
309
310
310 if reply_content['ename'] == 'UnmetDependency':
311 if reply_content['ename'] == 'UnmetDependency':
311 reply_metadata['dependencies_met'] = False
312 reply_metadata['dependencies_met'] = False
312 else:
313 else:
313 reply_content = {'status' : 'ok'}
314 reply_content = {'status' : 'ok'}
314
315
315 return reply_content, result_buf
316 return reply_content, result_buf
316
317
317 def do_clear(self):
318 def do_clear(self):
318 self.shell.reset(False)
319 self.shell.reset(False)
319 return dict(status='ok')
320 return dict(status='ok')
320
321
321
322
322 # This exists only for backwards compatibility - use IPythonKernel instead
323 # This exists only for backwards compatibility - use IPythonKernel instead
323
324
324 @undoc
325 @undoc
325 class Kernel(IPythonKernel):
326 class Kernel(IPythonKernel):
326 def __init__(self, *args, **kwargs):
327 def __init__(self, *args, **kwargs):
327 import warnings
328 import warnings
328 warnings.warn('Kernel is a deprecated alias of IPython.kernel.zmq.ipkernel.IPythonKernel',
329 warnings.warn('Kernel is a deprecated alias of IPython.kernel.zmq.ipkernel.IPythonKernel',
329 DeprecationWarning)
330 DeprecationWarning)
330 super(Kernel, self).__init__(*args, **kwargs) No newline at end of file
331 super(Kernel, self).__init__(*args, **kwargs)
@@ -1,697 +1,695 b''
1 """Base class for a kernel that talks to frontends over 0MQ."""
1 """Base class for a kernel that talks to frontends over 0MQ."""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 from __future__ import print_function
6 from __future__ import print_function
7
7
8 import sys
8 import sys
9 import time
9 import time
10 import logging
10 import logging
11 import uuid
11 import uuid
12
12
13 from datetime import datetime
13 from datetime import datetime
14 from signal import (
14 from signal import (
15 signal, default_int_handler, SIGINT
15 signal, default_int_handler, SIGINT
16 )
16 )
17
17
18 import zmq
18 import zmq
19 from zmq.eventloop import ioloop
19 from zmq.eventloop import ioloop
20 from zmq.eventloop.zmqstream import ZMQStream
20 from zmq.eventloop.zmqstream import ZMQStream
21
21
22 from IPython.config.configurable import SingletonConfigurable
22 from IPython.config.configurable import SingletonConfigurable
23 from IPython.core.error import StdinNotImplementedError
23 from IPython.core.error import StdinNotImplementedError
24 from IPython.core import release
24 from IPython.core import release
25 from IPython.utils import py3compat
25 from IPython.utils import py3compat
26 from IPython.utils.py3compat import unicode_type, string_types
26 from IPython.utils.py3compat import unicode_type, string_types
27 from IPython.utils.jsonutil import json_clean
27 from IPython.utils.jsonutil import json_clean
28 from IPython.utils.traitlets import (
28 from IPython.utils.traitlets import (
29 Any, Instance, Float, Dict, List, Set, Integer, Unicode, Bool,
29 Any, Instance, Float, Dict, List, Set, Integer, Unicode, Bool,
30 )
30 )
31
31
32 from .session import Session
32 from .session import Session
33
33
34
34
35 class Kernel(SingletonConfigurable):
35 class Kernel(SingletonConfigurable):
36
36
37 #---------------------------------------------------------------------------
37 #---------------------------------------------------------------------------
38 # Kernel interface
38 # Kernel interface
39 #---------------------------------------------------------------------------
39 #---------------------------------------------------------------------------
40
40
41 # attribute to override with a GUI
41 # attribute to override with a GUI
42 eventloop = Any(None)
42 eventloop = Any(None)
43 def _eventloop_changed(self, name, old, new):
43 def _eventloop_changed(self, name, old, new):
44 """schedule call to eventloop from IOLoop"""
44 """schedule call to eventloop from IOLoop"""
45 loop = ioloop.IOLoop.instance()
45 loop = ioloop.IOLoop.instance()
46 loop.add_callback(self.enter_eventloop)
46 loop.add_callback(self.enter_eventloop)
47
47
48 session = Instance(Session)
48 session = Instance(Session)
49 profile_dir = Instance('IPython.core.profiledir.ProfileDir')
49 profile_dir = Instance('IPython.core.profiledir.ProfileDir')
50 shell_streams = List()
50 shell_streams = List()
51 control_stream = Instance(ZMQStream)
51 control_stream = Instance(ZMQStream)
52 iopub_socket = Instance(zmq.Socket)
52 iopub_socket = Instance(zmq.Socket)
53 stdin_socket = Instance(zmq.Socket)
53 stdin_socket = Instance(zmq.Socket)
54 log = Instance(logging.Logger)
54 log = Instance(logging.Logger)
55
55
56 # identities:
56 # identities:
57 int_id = Integer(-1)
57 int_id = Integer(-1)
58 ident = Unicode()
58 ident = Unicode()
59
59
60 def _ident_default(self):
60 def _ident_default(self):
61 return unicode_type(uuid.uuid4())
61 return unicode_type(uuid.uuid4())
62
62
63 # This should be overridden by wrapper kernels that implement any real
63 # This should be overridden by wrapper kernels that implement any real
64 # language.
64 # language.
65 language_info = {}
65 language_info = {}
66
66
67 # Private interface
67 # Private interface
68
68
69 _darwin_app_nap = Bool(True, config=True,
69 _darwin_app_nap = Bool(True, config=True,
70 help="""Whether to use appnope for compatiblity with OS X App Nap.
70 help="""Whether to use appnope for compatiblity with OS X App Nap.
71
71
72 Only affects OS X >= 10.9.
72 Only affects OS X >= 10.9.
73 """
73 """
74 )
74 )
75
75
76 # track associations with current request
76 # track associations with current request
77 _allow_stdin = Bool(False)
77 _allow_stdin = Bool(False)
78 _parent_header = Dict()
78 _parent_header = Dict()
79 _parent_ident = Any(b'')
79 _parent_ident = Any(b'')
80 # Time to sleep after flushing the stdout/err buffers in each execute
80 # Time to sleep after flushing the stdout/err buffers in each execute
81 # cycle. While this introduces a hard limit on the minimal latency of the
81 # cycle. While this introduces a hard limit on the minimal latency of the
82 # execute cycle, it helps prevent output synchronization problems for
82 # execute cycle, it helps prevent output synchronization problems for
83 # clients.
83 # clients.
84 # Units are in seconds. The minimum zmq latency on local host is probably
84 # Units are in seconds. The minimum zmq latency on local host is probably
85 # ~150 microseconds, set this to 500us for now. We may need to increase it
85 # ~150 microseconds, set this to 500us for now. We may need to increase it
86 # a little if it's not enough after more interactive testing.
86 # a little if it's not enough after more interactive testing.
87 _execute_sleep = Float(0.0005, config=True)
87 _execute_sleep = Float(0.0005, config=True)
88
88
89 # Frequency of the kernel's event loop.
89 # Frequency of the kernel's event loop.
90 # Units are in seconds, kernel subclasses for GUI toolkits may need to
90 # Units are in seconds, kernel subclasses for GUI toolkits may need to
91 # adapt to milliseconds.
91 # adapt to milliseconds.
92 _poll_interval = Float(0.05, config=True)
92 _poll_interval = Float(0.05, config=True)
93
93
94 # If the shutdown was requested over the network, we leave here the
94 # If the shutdown was requested over the network, we leave here the
95 # necessary reply message so it can be sent by our registered atexit
95 # necessary reply message so it can be sent by our registered atexit
96 # handler. This ensures that the reply is only sent to clients truly at
96 # handler. This ensures that the reply is only sent to clients truly at
97 # the end of our shutdown process (which happens after the underlying
97 # the end of our shutdown process (which happens after the underlying
98 # IPython shell's own shutdown).
98 # IPython shell's own shutdown).
99 _shutdown_message = None
99 _shutdown_message = None
100
100
101 # This is a dict of port number that the kernel is listening on. It is set
101 # This is a dict of port number that the kernel is listening on. It is set
102 # by record_ports and used by connect_request.
102 # by record_ports and used by connect_request.
103 _recorded_ports = Dict()
103 _recorded_ports = Dict()
104
104
105 # set of aborted msg_ids
105 # set of aborted msg_ids
106 aborted = Set()
106 aborted = Set()
107
107
108 # Track execution count here. For IPython, we override this to use the
108 # Track execution count here. For IPython, we override this to use the
109 # execution count we store in the shell.
109 # execution count we store in the shell.
110 execution_count = 0
110 execution_count = 0
111
111
112
112
113 def __init__(self, **kwargs):
113 def __init__(self, **kwargs):
114 super(Kernel, self).__init__(**kwargs)
114 super(Kernel, self).__init__(**kwargs)
115
115
116 # Build dict of handlers for message types
116 # Build dict of handlers for message types
117 msg_types = [ 'execute_request', 'complete_request',
117 msg_types = [ 'execute_request', 'complete_request',
118 'inspect_request', 'history_request',
118 'inspect_request', 'history_request',
119 'kernel_info_request',
119 'kernel_info_request',
120 'connect_request', 'shutdown_request',
120 'connect_request', 'shutdown_request',
121 'apply_request', 'is_complete_request',
121 'apply_request', 'is_complete_request',
122 ]
122 ]
123 self.shell_handlers = {}
123 self.shell_handlers = {}
124 for msg_type in msg_types:
124 for msg_type in msg_types:
125 self.shell_handlers[msg_type] = getattr(self, msg_type)
125 self.shell_handlers[msg_type] = getattr(self, msg_type)
126
126
127 control_msg_types = msg_types + [ 'clear_request', 'abort_request' ]
127 control_msg_types = msg_types + [ 'clear_request', 'abort_request' ]
128 self.control_handlers = {}
128 self.control_handlers = {}
129 for msg_type in control_msg_types:
129 for msg_type in control_msg_types:
130 self.control_handlers[msg_type] = getattr(self, msg_type)
130 self.control_handlers[msg_type] = getattr(self, msg_type)
131
131
132
132
133 def dispatch_control(self, msg):
133 def dispatch_control(self, msg):
134 """dispatch control requests"""
134 """dispatch control requests"""
135 idents,msg = self.session.feed_identities(msg, copy=False)
135 idents,msg = self.session.feed_identities(msg, copy=False)
136 try:
136 try:
137 msg = self.session.deserialize(msg, content=True, copy=False)
137 msg = self.session.deserialize(msg, content=True, copy=False)
138 except:
138 except:
139 self.log.error("Invalid Control Message", exc_info=True)
139 self.log.error("Invalid Control Message", exc_info=True)
140 return
140 return
141
141
142 self.log.debug("Control received: %s", msg)
142 self.log.debug("Control received: %s", msg)
143
143
144 # Set the parent message for side effects.
144 # Set the parent message for side effects.
145 self.set_parent(idents, msg)
145 self.set_parent(idents, msg)
146 self._publish_status(u'busy')
146 self._publish_status(u'busy')
147
147
148 header = msg['header']
148 header = msg['header']
149 msg_type = header['msg_type']
149 msg_type = header['msg_type']
150
150
151 handler = self.control_handlers.get(msg_type, None)
151 handler = self.control_handlers.get(msg_type, None)
152 if handler is None:
152 if handler is None:
153 self.log.error("UNKNOWN CONTROL MESSAGE TYPE: %r", msg_type)
153 self.log.error("UNKNOWN CONTROL MESSAGE TYPE: %r", msg_type)
154 else:
154 else:
155 try:
155 try:
156 handler(self.control_stream, idents, msg)
156 handler(self.control_stream, idents, msg)
157 except Exception:
157 except Exception:
158 self.log.error("Exception in control handler:", exc_info=True)
158 self.log.error("Exception in control handler:", exc_info=True)
159
159
160 sys.stdout.flush()
160 sys.stdout.flush()
161 sys.stderr.flush()
161 sys.stderr.flush()
162 self._publish_status(u'idle')
162 self._publish_status(u'idle')
163
163
164 def dispatch_shell(self, stream, msg):
164 def dispatch_shell(self, stream, msg):
165 """dispatch shell requests"""
165 """dispatch shell requests"""
166 # flush control requests first
166 # flush control requests first
167 if self.control_stream:
167 if self.control_stream:
168 self.control_stream.flush()
168 self.control_stream.flush()
169
169
170 idents,msg = self.session.feed_identities(msg, copy=False)
170 idents,msg = self.session.feed_identities(msg, copy=False)
171 try:
171 try:
172 msg = self.session.deserialize(msg, content=True, copy=False)
172 msg = self.session.deserialize(msg, content=True, copy=False)
173 except:
173 except:
174 self.log.error("Invalid Message", exc_info=True)
174 self.log.error("Invalid Message", exc_info=True)
175 return
175 return
176
176
177 # Set the parent message for side effects.
177 # Set the parent message for side effects.
178 self.set_parent(idents, msg)
178 self.set_parent(idents, msg)
179 self._publish_status(u'busy')
179 self._publish_status(u'busy')
180
180
181 header = msg['header']
181 header = msg['header']
182 msg_id = header['msg_id']
182 msg_id = header['msg_id']
183 msg_type = msg['header']['msg_type']
183 msg_type = msg['header']['msg_type']
184
184
185 # Print some info about this message and leave a '--->' marker, so it's
185 # Print some info about this message and leave a '--->' marker, so it's
186 # easier to trace visually the message chain when debugging. Each
186 # easier to trace visually the message chain when debugging. Each
187 # handler prints its message at the end.
187 # handler prints its message at the end.
188 self.log.debug('\n*** MESSAGE TYPE:%s***', msg_type)
188 self.log.debug('\n*** MESSAGE TYPE:%s***', msg_type)
189 self.log.debug(' Content: %s\n --->\n ', msg['content'])
189 self.log.debug(' Content: %s\n --->\n ', msg['content'])
190
190
191 if msg_id in self.aborted:
191 if msg_id in self.aborted:
192 self.aborted.remove(msg_id)
192 self.aborted.remove(msg_id)
193 # is it safe to assume a msg_id will not be resubmitted?
193 # is it safe to assume a msg_id will not be resubmitted?
194 reply_type = msg_type.split('_')[0] + '_reply'
194 reply_type = msg_type.split('_')[0] + '_reply'
195 status = {'status' : 'aborted'}
195 status = {'status' : 'aborted'}
196 md = {'engine' : self.ident}
196 md = {'engine' : self.ident}
197 md.update(status)
197 md.update(status)
198 self.session.send(stream, reply_type, metadata=md,
198 self.session.send(stream, reply_type, metadata=md,
199 content=status, parent=msg, ident=idents)
199 content=status, parent=msg, ident=idents)
200 return
200 return
201
201
202 handler = self.shell_handlers.get(msg_type, None)
202 handler = self.shell_handlers.get(msg_type, None)
203 if handler is None:
203 if handler is None:
204 self.log.error("UNKNOWN MESSAGE TYPE: %r", msg_type)
204 self.log.error("UNKNOWN MESSAGE TYPE: %r", msg_type)
205 else:
205 else:
206 # ensure default_int_handler during handler call
206 # ensure default_int_handler during handler call
207 sig = signal(SIGINT, default_int_handler)
207 sig = signal(SIGINT, default_int_handler)
208 self.log.debug("%s: %s", msg_type, msg)
208 self.log.debug("%s: %s", msg_type, msg)
209 try:
209 try:
210 handler(stream, idents, msg)
210 handler(stream, idents, msg)
211 except Exception:
211 except Exception:
212 self.log.error("Exception in message handler:", exc_info=True)
212 self.log.error("Exception in message handler:", exc_info=True)
213 finally:
213 finally:
214 signal(SIGINT, sig)
214 signal(SIGINT, sig)
215
215
216 sys.stdout.flush()
216 sys.stdout.flush()
217 sys.stderr.flush()
217 sys.stderr.flush()
218 self._publish_status(u'idle')
218 self._publish_status(u'idle')
219
219
220 def enter_eventloop(self):
220 def enter_eventloop(self):
221 """enter eventloop"""
221 """enter eventloop"""
222 self.log.info("entering eventloop %s", self.eventloop)
222 self.log.info("entering eventloop %s", self.eventloop)
223 for stream in self.shell_streams:
223 for stream in self.shell_streams:
224 # flush any pending replies,
224 # flush any pending replies,
225 # which may be skipped by entering the eventloop
225 # which may be skipped by entering the eventloop
226 stream.flush(zmq.POLLOUT)
226 stream.flush(zmq.POLLOUT)
227 # restore default_int_handler
227 # restore default_int_handler
228 signal(SIGINT, default_int_handler)
228 signal(SIGINT, default_int_handler)
229 while self.eventloop is not None:
229 while self.eventloop is not None:
230 try:
230 try:
231 self.eventloop(self)
231 self.eventloop(self)
232 except KeyboardInterrupt:
232 except KeyboardInterrupt:
233 # Ctrl-C shouldn't crash the kernel
233 # Ctrl-C shouldn't crash the kernel
234 self.log.error("KeyboardInterrupt caught in kernel")
234 self.log.error("KeyboardInterrupt caught in kernel")
235 continue
235 continue
236 else:
236 else:
237 # eventloop exited cleanly, this means we should stop (right?)
237 # eventloop exited cleanly, this means we should stop (right?)
238 self.eventloop = None
238 self.eventloop = None
239 break
239 break
240 self.log.info("exiting eventloop")
240 self.log.info("exiting eventloop")
241
241
242 def start(self):
242 def start(self):
243 """register dispatchers for streams"""
243 """register dispatchers for streams"""
244 if self.control_stream:
244 if self.control_stream:
245 self.control_stream.on_recv(self.dispatch_control, copy=False)
245 self.control_stream.on_recv(self.dispatch_control, copy=False)
246
246
247 def make_dispatcher(stream):
247 def make_dispatcher(stream):
248 def dispatcher(msg):
248 def dispatcher(msg):
249 return self.dispatch_shell(stream, msg)
249 return self.dispatch_shell(stream, msg)
250 return dispatcher
250 return dispatcher
251
251
252 for s in self.shell_streams:
252 for s in self.shell_streams:
253 s.on_recv(make_dispatcher(s), copy=False)
253 s.on_recv(make_dispatcher(s), copy=False)
254
254
255 # publish idle status
255 # publish idle status
256 self._publish_status('starting')
256 self._publish_status('starting')
257
257
258 def do_one_iteration(self):
258 def do_one_iteration(self):
259 """step eventloop just once"""
259 """step eventloop just once"""
260 if self.control_stream:
260 if self.control_stream:
261 self.control_stream.flush()
261 self.control_stream.flush()
262 for stream in self.shell_streams:
262 for stream in self.shell_streams:
263 # handle at most one request per iteration
263 # handle at most one request per iteration
264 stream.flush(zmq.POLLIN, 1)
264 stream.flush(zmq.POLLIN, 1)
265 stream.flush(zmq.POLLOUT)
265 stream.flush(zmq.POLLOUT)
266
266
267
267
268 def record_ports(self, ports):
268 def record_ports(self, ports):
269 """Record the ports that this kernel is using.
269 """Record the ports that this kernel is using.
270
270
271 The creator of the Kernel instance must call this methods if they
271 The creator of the Kernel instance must call this methods if they
272 want the :meth:`connect_request` method to return the port numbers.
272 want the :meth:`connect_request` method to return the port numbers.
273 """
273 """
274 self._recorded_ports = ports
274 self._recorded_ports = ports
275
275
276 #---------------------------------------------------------------------------
276 #---------------------------------------------------------------------------
277 # Kernel request handlers
277 # Kernel request handlers
278 #---------------------------------------------------------------------------
278 #---------------------------------------------------------------------------
279
279
280 def _make_metadata(self, other=None):
280 def _make_metadata(self, other=None):
281 """init metadata dict, for execute/apply_reply"""
281 """init metadata dict, for execute/apply_reply"""
282 new_md = {
282 new_md = {
283 'dependencies_met' : True,
283 'dependencies_met' : True,
284 'engine' : self.ident,
284 'engine' : self.ident,
285 'started': datetime.now(),
285 'started': datetime.now(),
286 }
286 }
287 if other:
287 if other:
288 new_md.update(other)
288 new_md.update(other)
289 return new_md
289 return new_md
290
290
291 def _publish_execute_input(self, code, parent, execution_count):
291 def _publish_execute_input(self, code, parent, execution_count):
292 """Publish the code request on the iopub stream."""
292 """Publish the code request on the iopub stream."""
293
293
294 self.session.send(self.iopub_socket, u'execute_input',
294 self.session.send(self.iopub_socket, u'execute_input',
295 {u'code':code, u'execution_count': execution_count},
295 {u'code':code, u'execution_count': execution_count},
296 parent=parent, ident=self._topic('execute_input')
296 parent=parent, ident=self._topic('execute_input')
297 )
297 )
298
298
299 def _publish_status(self, status, parent=None):
299 def _publish_status(self, status, parent=None):
300 """send status (busy/idle) on IOPub"""
300 """send status (busy/idle) on IOPub"""
301 self.session.send(self.iopub_socket,
301 self.session.send(self.iopub_socket,
302 u'status',
302 u'status',
303 {u'execution_state': status},
303 {u'execution_state': status},
304 parent=parent or self._parent_header,
304 parent=parent or self._parent_header,
305 ident=self._topic('status'),
305 ident=self._topic('status'),
306 )
306 )
307
307
308 def set_parent(self, ident, parent):
308 def set_parent(self, ident, parent):
309 """Set the current parent_header
309 """Set the current parent_header
310
310
311 Side effects (IOPub messages) and replies are associated with
311 Side effects (IOPub messages) and replies are associated with
312 the request that caused them via the parent_header.
312 the request that caused them via the parent_header.
313
313
314 The parent identity is used to route input_request messages
314 The parent identity is used to route input_request messages
315 on the stdin channel.
315 on the stdin channel.
316 """
316 """
317 self._parent_ident = ident
317 self._parent_ident = ident
318 self._parent_header = parent
318 self._parent_header = parent
319
319
320 def send_response(self, stream, msg_or_type, content=None, ident=None,
320 def send_response(self, stream, msg_or_type, content=None, ident=None,
321 buffers=None, track=False, header=None, metadata=None):
321 buffers=None, track=False, header=None, metadata=None):
322 """Send a response to the message we're currently processing.
322 """Send a response to the message we're currently processing.
323
323
324 This accepts all the parameters of :meth:`IPython.kernel.zmq.session.Session.send`
324 This accepts all the parameters of :meth:`IPython.kernel.zmq.session.Session.send`
325 except ``parent``.
325 except ``parent``.
326
326
327 This relies on :meth:`set_parent` having been called for the current
327 This relies on :meth:`set_parent` having been called for the current
328 message.
328 message.
329 """
329 """
330 return self.session.send(stream, msg_or_type, content, self._parent_header,
330 return self.session.send(stream, msg_or_type, content, self._parent_header,
331 ident, buffers, track, header, metadata)
331 ident, buffers, track, header, metadata)
332
332
333 def execute_request(self, stream, ident, parent):
333 def execute_request(self, stream, ident, parent):
334 """handle an execute_request"""
334 """handle an execute_request"""
335
335
336 try:
336 try:
337 content = parent[u'content']
337 content = parent[u'content']
338 code = py3compat.cast_unicode_py2(content[u'code'])
338 code = py3compat.cast_unicode_py2(content[u'code'])
339 silent = content[u'silent']
339 silent = content[u'silent']
340 store_history = content.get(u'store_history', not silent)
340 store_history = content.get(u'store_history', not silent)
341 user_expressions = content.get('user_expressions', {})
341 user_expressions = content.get('user_expressions', {})
342 allow_stdin = content.get('allow_stdin', False)
342 allow_stdin = content.get('allow_stdin', False)
343 except:
343 except:
344 self.log.error("Got bad msg: ")
344 self.log.error("Got bad msg: ")
345 self.log.error("%s", parent)
345 self.log.error("%s", parent)
346 return
346 return
347
347
348 md = self._make_metadata(parent['metadata'])
348 md = self._make_metadata(parent['metadata'])
349
349
350 # Re-broadcast our input for the benefit of listening clients, and
350 # Re-broadcast our input for the benefit of listening clients, and
351 # start computing output
351 # start computing output
352 if not silent:
352 if not silent:
353 self.execution_count += 1
353 self.execution_count += 1
354 self._publish_execute_input(code, parent, self.execution_count)
354 self._publish_execute_input(code, parent, self.execution_count)
355
355
356 reply_content = self.do_execute(code, silent, store_history,
356 reply_content = self.do_execute(code, silent, store_history,
357 user_expressions, allow_stdin)
357 user_expressions, allow_stdin)
358
358
359 # Flush output before sending the reply.
359 # Flush output before sending the reply.
360 sys.stdout.flush()
360 sys.stdout.flush()
361 sys.stderr.flush()
361 sys.stderr.flush()
362 # FIXME: on rare occasions, the flush doesn't seem to make it to the
362 # FIXME: on rare occasions, the flush doesn't seem to make it to the
363 # clients... This seems to mitigate the problem, but we definitely need
363 # clients... This seems to mitigate the problem, but we definitely need
364 # to better understand what's going on.
364 # to better understand what's going on.
365 if self._execute_sleep:
365 if self._execute_sleep:
366 time.sleep(self._execute_sleep)
366 time.sleep(self._execute_sleep)
367
367
368 # Send the reply.
368 # Send the reply.
369 reply_content = json_clean(reply_content)
369 reply_content = json_clean(reply_content)
370
370
371 md['status'] = reply_content['status']
371 md['status'] = reply_content['status']
372 if reply_content['status'] == 'error' and \
372 if reply_content['status'] == 'error' and \
373 reply_content['ename'] == 'UnmetDependency':
373 reply_content['ename'] == 'UnmetDependency':
374 md['dependencies_met'] = False
374 md['dependencies_met'] = False
375
375
376 reply_msg = self.session.send(stream, u'execute_reply',
376 reply_msg = self.session.send(stream, u'execute_reply',
377 reply_content, parent, metadata=md,
377 reply_content, parent, metadata=md,
378 ident=ident)
378 ident=ident)
379
379
380 self.log.debug("%s", reply_msg)
380 self.log.debug("%s", reply_msg)
381
381
382 if not silent and reply_msg['content']['status'] == u'error':
382 if not silent and reply_msg['content']['status'] == u'error':
383 self._abort_queues()
383 self._abort_queues()
384
384
385 def do_execute(self, code, silent, store_history=True,
385 def do_execute(self, code, silent, store_history=True,
386 user_experssions=None, allow_stdin=False):
386 user_experssions=None, allow_stdin=False):
387 """Execute user code. Must be overridden by subclasses.
387 """Execute user code. Must be overridden by subclasses.
388 """
388 """
389 raise NotImplementedError
389 raise NotImplementedError
390
390
391 def complete_request(self, stream, ident, parent):
391 def complete_request(self, stream, ident, parent):
392 content = parent['content']
392 content = parent['content']
393 code = content['code']
393 code = content['code']
394 cursor_pos = content['cursor_pos']
394 cursor_pos = content['cursor_pos']
395
395
396 matches = self.do_complete(code, cursor_pos)
396 matches = self.do_complete(code, cursor_pos)
397 matches = json_clean(matches)
397 matches = json_clean(matches)
398 completion_msg = self.session.send(stream, 'complete_reply',
398 completion_msg = self.session.send(stream, 'complete_reply',
399 matches, parent, ident)
399 matches, parent, ident)
400 self.log.debug("%s", completion_msg)
400 self.log.debug("%s", completion_msg)
401
401
402 def do_complete(self, code, cursor_pos):
402 def do_complete(self, code, cursor_pos):
403 """Override in subclasses to find completions.
403 """Override in subclasses to find completions.
404 """
404 """
405 return {'matches' : [],
405 return {'matches' : [],
406 'cursor_end' : cursor_pos,
406 'cursor_end' : cursor_pos,
407 'cursor_start' : cursor_pos,
407 'cursor_start' : cursor_pos,
408 'metadata' : {},
408 'metadata' : {},
409 'status' : 'ok'}
409 'status' : 'ok'}
410
410
411 def inspect_request(self, stream, ident, parent):
411 def inspect_request(self, stream, ident, parent):
412 content = parent['content']
412 content = parent['content']
413
413
414 reply_content = self.do_inspect(content['code'], content['cursor_pos'],
414 reply_content = self.do_inspect(content['code'], content['cursor_pos'],
415 content.get('detail_level', 0))
415 content.get('detail_level', 0))
416 # Before we send this object over, we scrub it for JSON usage
416 # Before we send this object over, we scrub it for JSON usage
417 reply_content = json_clean(reply_content)
417 reply_content = json_clean(reply_content)
418 msg = self.session.send(stream, 'inspect_reply',
418 msg = self.session.send(stream, 'inspect_reply',
419 reply_content, parent, ident)
419 reply_content, parent, ident)
420 self.log.debug("%s", msg)
420 self.log.debug("%s", msg)
421
421
422 def do_inspect(self, code, cursor_pos, detail_level=0):
422 def do_inspect(self, code, cursor_pos, detail_level=0):
423 """Override in subclasses to allow introspection.
423 """Override in subclasses to allow introspection.
424 """
424 """
425 return {'status': 'ok', 'data':{}, 'metadata':{}, 'found':False}
425 return {'status': 'ok', 'data':{}, 'metadata':{}, 'found':False}
426
426
427 def history_request(self, stream, ident, parent):
427 def history_request(self, stream, ident, parent):
428 content = parent['content']
428 content = parent['content']
429
429
430 reply_content = self.do_history(**content)
430 reply_content = self.do_history(**content)
431
431
432 reply_content = json_clean(reply_content)
432 reply_content = json_clean(reply_content)
433 msg = self.session.send(stream, 'history_reply',
433 msg = self.session.send(stream, 'history_reply',
434 reply_content, parent, ident)
434 reply_content, parent, ident)
435 self.log.debug("%s", msg)
435 self.log.debug("%s", msg)
436
436
437 def do_history(self, hist_access_type, output, raw, session=None, start=None,
437 def do_history(self, hist_access_type, output, raw, session=None, start=None,
438 stop=None, n=None, pattern=None, unique=False):
438 stop=None, n=None, pattern=None, unique=False):
439 """Override in subclasses to access history.
439 """Override in subclasses to access history.
440 """
440 """
441 return {'history': []}
441 return {'history': []}
442
442
443 def connect_request(self, stream, ident, parent):
443 def connect_request(self, stream, ident, parent):
444 if self._recorded_ports is not None:
444 if self._recorded_ports is not None:
445 content = self._recorded_ports.copy()
445 content = self._recorded_ports.copy()
446 else:
446 else:
447 content = {}
447 content = {}
448 msg = self.session.send(stream, 'connect_reply',
448 msg = self.session.send(stream, 'connect_reply',
449 content, parent, ident)
449 content, parent, ident)
450 self.log.debug("%s", msg)
450 self.log.debug("%s", msg)
451
451
452 @property
452 @property
453 def kernel_info(self):
453 def kernel_info(self):
454 return {
454 return {
455 'protocol_version': release.kernel_protocol_version,
455 'protocol_version': release.kernel_protocol_version,
456 'implementation': self.implementation,
456 'implementation': self.implementation,
457 'implementation_version': self.implementation_version,
457 'implementation_version': self.implementation_version,
458 'language': self.language,
459 'language_version': self.language_version,
460 'language_info': self.language_info,
458 'language_info': self.language_info,
461 'banner': self.banner,
459 'banner': self.banner,
462 }
460 }
463
461
464 def kernel_info_request(self, stream, ident, parent):
462 def kernel_info_request(self, stream, ident, parent):
465 msg = self.session.send(stream, 'kernel_info_reply',
463 msg = self.session.send(stream, 'kernel_info_reply',
466 self.kernel_info, parent, ident)
464 self.kernel_info, parent, ident)
467 self.log.debug("%s", msg)
465 self.log.debug("%s", msg)
468
466
469 def shutdown_request(self, stream, ident, parent):
467 def shutdown_request(self, stream, ident, parent):
470 content = self.do_shutdown(parent['content']['restart'])
468 content = self.do_shutdown(parent['content']['restart'])
471 self.session.send(stream, u'shutdown_reply', content, parent, ident=ident)
469 self.session.send(stream, u'shutdown_reply', content, parent, ident=ident)
472 # same content, but different msg_id for broadcasting on IOPub
470 # same content, but different msg_id for broadcasting on IOPub
473 self._shutdown_message = self.session.msg(u'shutdown_reply',
471 self._shutdown_message = self.session.msg(u'shutdown_reply',
474 content, parent
472 content, parent
475 )
473 )
476
474
477 self._at_shutdown()
475 self._at_shutdown()
478 # call sys.exit after a short delay
476 # call sys.exit after a short delay
479 loop = ioloop.IOLoop.instance()
477 loop = ioloop.IOLoop.instance()
480 loop.add_timeout(time.time()+0.1, loop.stop)
478 loop.add_timeout(time.time()+0.1, loop.stop)
481
479
482 def do_shutdown(self, restart):
480 def do_shutdown(self, restart):
483 """Override in subclasses to do things when the frontend shuts down the
481 """Override in subclasses to do things when the frontend shuts down the
484 kernel.
482 kernel.
485 """
483 """
486 return {'status': 'ok', 'restart': restart}
484 return {'status': 'ok', 'restart': restart}
487
485
488 def is_complete_request(self, stream, ident, parent):
486 def is_complete_request(self, stream, ident, parent):
489 content = parent['content']
487 content = parent['content']
490 code = content['code']
488 code = content['code']
491
489
492 reply_content = self.do_is_complete(code)
490 reply_content = self.do_is_complete(code)
493 reply_content = json_clean(reply_content)
491 reply_content = json_clean(reply_content)
494 reply_msg = self.session.send(stream, 'is_complete_reply',
492 reply_msg = self.session.send(stream, 'is_complete_reply',
495 reply_content, parent, ident)
493 reply_content, parent, ident)
496 self.log.debug("%s", reply_msg)
494 self.log.debug("%s", reply_msg)
497
495
498 def do_is_complete(self, code):
496 def do_is_complete(self, code):
499 """Override in subclasses to find completions.
497 """Override in subclasses to find completions.
500 """
498 """
501 return {'status' : 'unknown',
499 return {'status' : 'unknown',
502 }
500 }
503
501
504 #---------------------------------------------------------------------------
502 #---------------------------------------------------------------------------
505 # Engine methods
503 # Engine methods
506 #---------------------------------------------------------------------------
504 #---------------------------------------------------------------------------
507
505
508 def apply_request(self, stream, ident, parent):
506 def apply_request(self, stream, ident, parent):
509 try:
507 try:
510 content = parent[u'content']
508 content = parent[u'content']
511 bufs = parent[u'buffers']
509 bufs = parent[u'buffers']
512 msg_id = parent['header']['msg_id']
510 msg_id = parent['header']['msg_id']
513 except:
511 except:
514 self.log.error("Got bad msg: %s", parent, exc_info=True)
512 self.log.error("Got bad msg: %s", parent, exc_info=True)
515 return
513 return
516
514
517 md = self._make_metadata(parent['metadata'])
515 md = self._make_metadata(parent['metadata'])
518
516
519 reply_content, result_buf = self.do_apply(content, bufs, msg_id, md)
517 reply_content, result_buf = self.do_apply(content, bufs, msg_id, md)
520
518
521 # put 'ok'/'error' status in header, for scheduler introspection:
519 # put 'ok'/'error' status in header, for scheduler introspection:
522 md['status'] = reply_content['status']
520 md['status'] = reply_content['status']
523
521
524 # flush i/o
522 # flush i/o
525 sys.stdout.flush()
523 sys.stdout.flush()
526 sys.stderr.flush()
524 sys.stderr.flush()
527
525
528 self.session.send(stream, u'apply_reply', reply_content,
526 self.session.send(stream, u'apply_reply', reply_content,
529 parent=parent, ident=ident,buffers=result_buf, metadata=md)
527 parent=parent, ident=ident,buffers=result_buf, metadata=md)
530
528
531 def do_apply(self, content, bufs, msg_id, reply_metadata):
529 def do_apply(self, content, bufs, msg_id, reply_metadata):
532 """Override in subclasses to support the IPython parallel framework.
530 """Override in subclasses to support the IPython parallel framework.
533 """
531 """
534 raise NotImplementedError
532 raise NotImplementedError
535
533
536 #---------------------------------------------------------------------------
534 #---------------------------------------------------------------------------
537 # Control messages
535 # Control messages
538 #---------------------------------------------------------------------------
536 #---------------------------------------------------------------------------
539
537
540 def abort_request(self, stream, ident, parent):
538 def abort_request(self, stream, ident, parent):
541 """abort a specific msg by id"""
539 """abort a specific msg by id"""
542 msg_ids = parent['content'].get('msg_ids', None)
540 msg_ids = parent['content'].get('msg_ids', None)
543 if isinstance(msg_ids, string_types):
541 if isinstance(msg_ids, string_types):
544 msg_ids = [msg_ids]
542 msg_ids = [msg_ids]
545 if not msg_ids:
543 if not msg_ids:
546 self._abort_queues()
544 self._abort_queues()
547 for mid in msg_ids:
545 for mid in msg_ids:
548 self.aborted.add(str(mid))
546 self.aborted.add(str(mid))
549
547
550 content = dict(status='ok')
548 content = dict(status='ok')
551 reply_msg = self.session.send(stream, 'abort_reply', content=content,
549 reply_msg = self.session.send(stream, 'abort_reply', content=content,
552 parent=parent, ident=ident)
550 parent=parent, ident=ident)
553 self.log.debug("%s", reply_msg)
551 self.log.debug("%s", reply_msg)
554
552
555 def clear_request(self, stream, idents, parent):
553 def clear_request(self, stream, idents, parent):
556 """Clear our namespace."""
554 """Clear our namespace."""
557 content = self.do_clear()
555 content = self.do_clear()
558 self.session.send(stream, 'clear_reply', ident=idents, parent=parent,
556 self.session.send(stream, 'clear_reply', ident=idents, parent=parent,
559 content = content)
557 content = content)
560
558
561 def do_clear(self):
559 def do_clear(self):
562 """Override in subclasses to clear the namespace
560 """Override in subclasses to clear the namespace
563
561
564 This is only required for IPython.parallel.
562 This is only required for IPython.parallel.
565 """
563 """
566 raise NotImplementedError
564 raise NotImplementedError
567
565
568 #---------------------------------------------------------------------------
566 #---------------------------------------------------------------------------
569 # Protected interface
567 # Protected interface
570 #---------------------------------------------------------------------------
568 #---------------------------------------------------------------------------
571
569
572 def _topic(self, topic):
570 def _topic(self, topic):
573 """prefixed topic for IOPub messages"""
571 """prefixed topic for IOPub messages"""
574 if self.int_id >= 0:
572 if self.int_id >= 0:
575 base = "engine.%i" % self.int_id
573 base = "engine.%i" % self.int_id
576 else:
574 else:
577 base = "kernel.%s" % self.ident
575 base = "kernel.%s" % self.ident
578
576
579 return py3compat.cast_bytes("%s.%s" % (base, topic))
577 return py3compat.cast_bytes("%s.%s" % (base, topic))
580
578
581 def _abort_queues(self):
579 def _abort_queues(self):
582 for stream in self.shell_streams:
580 for stream in self.shell_streams:
583 if stream:
581 if stream:
584 self._abort_queue(stream)
582 self._abort_queue(stream)
585
583
586 def _abort_queue(self, stream):
584 def _abort_queue(self, stream):
587 poller = zmq.Poller()
585 poller = zmq.Poller()
588 poller.register(stream.socket, zmq.POLLIN)
586 poller.register(stream.socket, zmq.POLLIN)
589 while True:
587 while True:
590 idents,msg = self.session.recv(stream, zmq.NOBLOCK, content=True)
588 idents,msg = self.session.recv(stream, zmq.NOBLOCK, content=True)
591 if msg is None:
589 if msg is None:
592 return
590 return
593
591
594 self.log.info("Aborting:")
592 self.log.info("Aborting:")
595 self.log.info("%s", msg)
593 self.log.info("%s", msg)
596 msg_type = msg['header']['msg_type']
594 msg_type = msg['header']['msg_type']
597 reply_type = msg_type.split('_')[0] + '_reply'
595 reply_type = msg_type.split('_')[0] + '_reply'
598
596
599 status = {'status' : 'aborted'}
597 status = {'status' : 'aborted'}
600 md = {'engine' : self.ident}
598 md = {'engine' : self.ident}
601 md.update(status)
599 md.update(status)
602 reply_msg = self.session.send(stream, reply_type, metadata=md,
600 reply_msg = self.session.send(stream, reply_type, metadata=md,
603 content=status, parent=msg, ident=idents)
601 content=status, parent=msg, ident=idents)
604 self.log.debug("%s", reply_msg)
602 self.log.debug("%s", reply_msg)
605 # We need to wait a bit for requests to come in. This can probably
603 # We need to wait a bit for requests to come in. This can probably
606 # be set shorter for true asynchronous clients.
604 # be set shorter for true asynchronous clients.
607 poller.poll(50)
605 poller.poll(50)
608
606
609
607
610 def _no_raw_input(self):
608 def _no_raw_input(self):
611 """Raise StdinNotImplentedError if active frontend doesn't support
609 """Raise StdinNotImplentedError if active frontend doesn't support
612 stdin."""
610 stdin."""
613 raise StdinNotImplementedError("raw_input was called, but this "
611 raise StdinNotImplementedError("raw_input was called, but this "
614 "frontend does not support stdin.")
612 "frontend does not support stdin.")
615
613
616 def getpass(self, prompt=''):
614 def getpass(self, prompt=''):
617 """Forward getpass to frontends
615 """Forward getpass to frontends
618
616
619 Raises
617 Raises
620 ------
618 ------
621 StdinNotImplentedError if active frontend doesn't support stdin.
619 StdinNotImplentedError if active frontend doesn't support stdin.
622 """
620 """
623 if not self._allow_stdin:
621 if not self._allow_stdin:
624 raise StdinNotImplementedError(
622 raise StdinNotImplementedError(
625 "getpass was called, but this frontend does not support input requests."
623 "getpass was called, but this frontend does not support input requests."
626 )
624 )
627 return self._input_request(prompt,
625 return self._input_request(prompt,
628 self._parent_ident,
626 self._parent_ident,
629 self._parent_header,
627 self._parent_header,
630 password=True,
628 password=True,
631 )
629 )
632
630
633 def raw_input(self, prompt=''):
631 def raw_input(self, prompt=''):
634 """Forward raw_input to frontends
632 """Forward raw_input to frontends
635
633
636 Raises
634 Raises
637 ------
635 ------
638 StdinNotImplentedError if active frontend doesn't support stdin.
636 StdinNotImplentedError if active frontend doesn't support stdin.
639 """
637 """
640 if not self._allow_stdin:
638 if not self._allow_stdin:
641 raise StdinNotImplementedError(
639 raise StdinNotImplementedError(
642 "raw_input was called, but this frontend does not support input requests."
640 "raw_input was called, but this frontend does not support input requests."
643 )
641 )
644 return self._input_request(prompt,
642 return self._input_request(prompt,
645 self._parent_ident,
643 self._parent_ident,
646 self._parent_header,
644 self._parent_header,
647 password=False,
645 password=False,
648 )
646 )
649
647
650 def _input_request(self, prompt, ident, parent, password=False):
648 def _input_request(self, prompt, ident, parent, password=False):
651 # Flush output before making the request.
649 # Flush output before making the request.
652 sys.stderr.flush()
650 sys.stderr.flush()
653 sys.stdout.flush()
651 sys.stdout.flush()
654 # flush the stdin socket, to purge stale replies
652 # flush the stdin socket, to purge stale replies
655 while True:
653 while True:
656 try:
654 try:
657 self.stdin_socket.recv_multipart(zmq.NOBLOCK)
655 self.stdin_socket.recv_multipart(zmq.NOBLOCK)
658 except zmq.ZMQError as e:
656 except zmq.ZMQError as e:
659 if e.errno == zmq.EAGAIN:
657 if e.errno == zmq.EAGAIN:
660 break
658 break
661 else:
659 else:
662 raise
660 raise
663
661
664 # Send the input request.
662 # Send the input request.
665 content = json_clean(dict(prompt=prompt, password=password))
663 content = json_clean(dict(prompt=prompt, password=password))
666 self.session.send(self.stdin_socket, u'input_request', content, parent,
664 self.session.send(self.stdin_socket, u'input_request', content, parent,
667 ident=ident)
665 ident=ident)
668
666
669 # Await a response.
667 # Await a response.
670 while True:
668 while True:
671 try:
669 try:
672 ident, reply = self.session.recv(self.stdin_socket, 0)
670 ident, reply = self.session.recv(self.stdin_socket, 0)
673 except Exception:
671 except Exception:
674 self.log.warn("Invalid Message:", exc_info=True)
672 self.log.warn("Invalid Message:", exc_info=True)
675 except KeyboardInterrupt:
673 except KeyboardInterrupt:
676 # re-raise KeyboardInterrupt, to truncate traceback
674 # re-raise KeyboardInterrupt, to truncate traceback
677 raise KeyboardInterrupt
675 raise KeyboardInterrupt
678 else:
676 else:
679 break
677 break
680 try:
678 try:
681 value = py3compat.unicode_to_str(reply['content']['value'])
679 value = py3compat.unicode_to_str(reply['content']['value'])
682 except:
680 except:
683 self.log.error("Bad input_reply: %s", parent)
681 self.log.error("Bad input_reply: %s", parent)
684 value = ''
682 value = ''
685 if value == '\x04':
683 if value == '\x04':
686 # EOF
684 # EOF
687 raise EOFError
685 raise EOFError
688 return value
686 return value
689
687
690 def _at_shutdown(self):
688 def _at_shutdown(self):
691 """Actions taken at shutdown by the kernel, called by python's atexit.
689 """Actions taken at shutdown by the kernel, called by python's atexit.
692 """
690 """
693 # io.rprint("Kernel at_shutdown") # dbg
691 # io.rprint("Kernel at_shutdown") # dbg
694 if self._shutdown_message is not None:
692 if self._shutdown_message is not None:
695 self.session.send(self.iopub_socket, self._shutdown_message, ident=self._topic('shutdown'))
693 self.session.send(self.iopub_socket, self._shutdown_message, ident=self._topic('shutdown'))
696 self.log.debug("%s", self._shutdown_message)
694 self.log.debug("%s", self._shutdown_message)
697 [ s.flush(zmq.POLLOUT) for s in self.shell_streams ]
695 [ s.flush(zmq.POLLOUT) for s in self.shell_streams ]
@@ -1,349 +1,375 b''
1 {
1 {
2 "$schema": "http://json-schema.org/draft-04/schema#",
2 "$schema": "http://json-schema.org/draft-04/schema#",
3 "description": "IPython Notebook v4.0 JSON schema.",
3 "description": "IPython Notebook v4.0 JSON schema.",
4 "type": "object",
4 "type": "object",
5 "additionalProperties": false,
5 "additionalProperties": false,
6 "required": ["metadata", "nbformat_minor", "nbformat", "cells"],
6 "required": ["metadata", "nbformat_minor", "nbformat", "cells"],
7 "properties": {
7 "properties": {
8 "metadata": {
8 "metadata": {
9 "description": "Notebook root-level metadata.",
9 "description": "Notebook root-level metadata.",
10 "type": "object",
10 "type": "object",
11 "additionalProperties": true,
11 "additionalProperties": true,
12 "properties": {
12 "properties": {
13 "kernel_info": {
13 "kernelspec": {
14 "description": "Kernel information.",
14 "description": "Kernel information.",
15 "type": "object",
15 "type": "object",
16 "required": ["name", "language"],
16 "required": ["name", "display_name"],
17 "properties": {
17 "properties": {
18 "name": {
18 "name": {
19 "description": "Name of the kernel specification.",
19 "description": "Name of the kernel specification.",
20 "type": "string"
20 "type": "string"
21 },
21 },
22 "language": {
22 "display_name": {
23 "description": "The programming language which this kernel runs.",
23 "description": "Name to display in UI.",
24 "type": "string"
25 },
26 "codemirror_mode": {
27 "description": "The codemirror mode to use for code in this language.",
28 "type": "string"
24 "type": "string"
29 }
25 }
30 }
26 }
31 },
27 },
28 "language_info": {
29 "description": "Kernel information.",
30 "type": "object",
31 "required": ["name"],
32 "properties": {
33 "name": {
34 "description": "The programming language which this kernel runs.",
35 "type": "string"
36 },
37 "codemirror_mode": {
38 "description": "The codemirror mode to use for code in this language.",
39 "oneOf": [
40 {"type": "string"},
41 {"type": "object"}
42 ]
43 },
44 "file_extension": {
45 "description": "The file extension for files in this language.",
46 "type": "string"
47 },
48 "mimetype": {
49 "description": "The mimetype corresponding to files in this language.",
50 "type": "string"
51 },
52 "pygments_lexer": {
53 "description": "The pygments lexer to use for code in this language.",
54 "type": "string"
55 }
56 }
57 },
32 "signature": {
58 "signature": {
33 "description": "Hash of the notebook.",
59 "description": "Hash of the notebook.",
34 "type": "string"
60 "type": "string"
35 },
61 },
36 "orig_nbformat": {
62 "orig_nbformat": {
37 "description": "Original notebook format (major number) before converting the notebook between versions. This should never be written to a file.",
63 "description": "Original notebook format (major number) before converting the notebook between versions. This should never be written to a file.",
38 "type": "integer",
64 "type": "integer",
39 "minimum": 1
65 "minimum": 1
40 }
66 }
41 }
67 }
42 },
68 },
43 "nbformat_minor": {
69 "nbformat_minor": {
44 "description": "Notebook format (minor number). Incremented for backward compatible changes to the notebook format.",
70 "description": "Notebook format (minor number). Incremented for backward compatible changes to the notebook format.",
45 "type": "integer",
71 "type": "integer",
46 "minimum": 0
72 "minimum": 0
47 },
73 },
48 "nbformat": {
74 "nbformat": {
49 "description": "Notebook format (major number). Incremented between backwards incompatible changes to the notebook format.",
75 "description": "Notebook format (major number). Incremented between backwards incompatible changes to the notebook format.",
50 "type": "integer",
76 "type": "integer",
51 "minimum": 4,
77 "minimum": 4,
52 "maximum": 4
78 "maximum": 4
53 },
79 },
54 "cells": {
80 "cells": {
55 "description": "Array of cells of the current notebook.",
81 "description": "Array of cells of the current notebook.",
56 "type": "array",
82 "type": "array",
57 "items": {"$ref": "#/definitions/cell"}
83 "items": {"$ref": "#/definitions/cell"}
58 }
84 }
59 },
85 },
60
86
61 "definitions": {
87 "definitions": {
62 "cell": {
88 "cell": {
63 "type": "object",
89 "type": "object",
64 "oneOf": [
90 "oneOf": [
65 {"$ref": "#/definitions/raw_cell"},
91 {"$ref": "#/definitions/raw_cell"},
66 {"$ref": "#/definitions/markdown_cell"},
92 {"$ref": "#/definitions/markdown_cell"},
67 {"$ref": "#/definitions/code_cell"}
93 {"$ref": "#/definitions/code_cell"}
68 ]
94 ]
69 },
95 },
70
96
71 "raw_cell": {
97 "raw_cell": {
72 "description": "Notebook raw nbconvert cell.",
98 "description": "Notebook raw nbconvert cell.",
73 "type": "object",
99 "type": "object",
74 "additionalProperties": false,
100 "additionalProperties": false,
75 "required": ["cell_type", "metadata", "source"],
101 "required": ["cell_type", "metadata", "source"],
76 "properties": {
102 "properties": {
77 "cell_type": {
103 "cell_type": {
78 "description": "String identifying the type of cell.",
104 "description": "String identifying the type of cell.",
79 "enum": ["raw"]
105 "enum": ["raw"]
80 },
106 },
81 "metadata": {
107 "metadata": {
82 "description": "Cell-level metadata.",
108 "description": "Cell-level metadata.",
83 "type": "object",
109 "type": "object",
84 "additionalProperties": true,
110 "additionalProperties": true,
85 "properties": {
111 "properties": {
86 "format": {
112 "format": {
87 "description": "Raw cell metadata format for nbconvert.",
113 "description": "Raw cell metadata format for nbconvert.",
88 "type": "string"
114 "type": "string"
89 },
115 },
90 "name": {"$ref": "#/definitions/misc/metadata_name"},
116 "name": {"$ref": "#/definitions/misc/metadata_name"},
91 "tags": {"$ref": "#/definitions/misc/metadata_tags"}
117 "tags": {"$ref": "#/definitions/misc/metadata_tags"}
92 }
118 }
93 },
119 },
94 "source": {"$ref": "#/definitions/misc/source"}
120 "source": {"$ref": "#/definitions/misc/source"}
95 }
121 }
96 },
122 },
97
123
98 "markdown_cell": {
124 "markdown_cell": {
99 "description": "Notebook markdown cell.",
125 "description": "Notebook markdown cell.",
100 "type": "object",
126 "type": "object",
101 "additionalProperties": false,
127 "additionalProperties": false,
102 "required": ["cell_type", "metadata", "source"],
128 "required": ["cell_type", "metadata", "source"],
103 "properties": {
129 "properties": {
104 "cell_type": {
130 "cell_type": {
105 "description": "String identifying the type of cell.",
131 "description": "String identifying the type of cell.",
106 "enum": ["markdown"]
132 "enum": ["markdown"]
107 },
133 },
108 "metadata": {
134 "metadata": {
109 "description": "Cell-level metadata.",
135 "description": "Cell-level metadata.",
110 "type": "object",
136 "type": "object",
111 "properties": {
137 "properties": {
112 "name": {"$ref": "#/definitions/misc/metadata_name"},
138 "name": {"$ref": "#/definitions/misc/metadata_name"},
113 "tags": {"$ref": "#/definitions/misc/metadata_tags"}
139 "tags": {"$ref": "#/definitions/misc/metadata_tags"}
114 },
140 },
115 "additionalProperties": true
141 "additionalProperties": true
116 },
142 },
117 "source": {"$ref": "#/definitions/misc/source"}
143 "source": {"$ref": "#/definitions/misc/source"}
118 }
144 }
119 },
145 },
120
146
121 "code_cell": {
147 "code_cell": {
122 "description": "Notebook code cell.",
148 "description": "Notebook code cell.",
123 "type": "object",
149 "type": "object",
124 "additionalProperties": false,
150 "additionalProperties": false,
125 "required": ["cell_type", "metadata", "source", "outputs", "execution_count"],
151 "required": ["cell_type", "metadata", "source", "outputs", "execution_count"],
126 "properties": {
152 "properties": {
127 "cell_type": {
153 "cell_type": {
128 "description": "String identifying the type of cell.",
154 "description": "String identifying the type of cell.",
129 "enum": ["code"]
155 "enum": ["code"]
130 },
156 },
131 "metadata": {
157 "metadata": {
132 "description": "Cell-level metadata.",
158 "description": "Cell-level metadata.",
133 "type": "object",
159 "type": "object",
134 "additionalProperties": true,
160 "additionalProperties": true,
135 "properties": {
161 "properties": {
136 "collapsed": {
162 "collapsed": {
137 "description": "Whether the cell is collapsed/expanded.",
163 "description": "Whether the cell is collapsed/expanded.",
138 "type": "boolean"
164 "type": "boolean"
139 },
165 },
140 "autoscroll": {
166 "autoscroll": {
141 "description": "Whether the cell's output is scrolled, unscrolled, or autoscrolled.",
167 "description": "Whether the cell's output is scrolled, unscrolled, or autoscrolled.",
142 "enum": [true, false, "auto"]
168 "enum": [true, false, "auto"]
143 },
169 },
144 "name": {"$ref": "#/definitions/misc/metadata_name"},
170 "name": {"$ref": "#/definitions/misc/metadata_name"},
145 "tags": {"$ref": "#/definitions/misc/metadata_tags"}
171 "tags": {"$ref": "#/definitions/misc/metadata_tags"}
146 }
172 }
147 },
173 },
148 "source": {"$ref": "#/definitions/misc/source"},
174 "source": {"$ref": "#/definitions/misc/source"},
149 "outputs": {
175 "outputs": {
150 "description": "Execution, display, or stream outputs.",
176 "description": "Execution, display, or stream outputs.",
151 "type": "array",
177 "type": "array",
152 "items": {"$ref": "#/definitions/output"}
178 "items": {"$ref": "#/definitions/output"}
153 },
179 },
154 "execution_count": {
180 "execution_count": {
155 "description": "The code cell's prompt number. Will be null if the cell has not been run.",
181 "description": "The code cell's prompt number. Will be null if the cell has not been run.",
156 "type": ["integer", "null"],
182 "type": ["integer", "null"],
157 "minimum": 0
183 "minimum": 0
158 }
184 }
159 }
185 }
160 },
186 },
161
187
162 "unrecognized_cell": {
188 "unrecognized_cell": {
163 "description": "Unrecognized cell from a future minor-revision to the notebook format.",
189 "description": "Unrecognized cell from a future minor-revision to the notebook format.",
164 "type": "object",
190 "type": "object",
165 "additionalProperties": true,
191 "additionalProperties": true,
166 "required": ["cell_type", "metadata"],
192 "required": ["cell_type", "metadata"],
167 "properties": {
193 "properties": {
168 "cell_type": {
194 "cell_type": {
169 "description": "String identifying the type of cell.",
195 "description": "String identifying the type of cell.",
170 "not" : {
196 "not" : {
171 "enum": ["markdown", "code", "raw"]
197 "enum": ["markdown", "code", "raw"]
172 }
198 }
173 },
199 },
174 "metadata": {
200 "metadata": {
175 "description": "Cell-level metadata.",
201 "description": "Cell-level metadata.",
176 "type": "object",
202 "type": "object",
177 "properties": {
203 "properties": {
178 "name": {"$ref": "#/definitions/misc/metadata_name"},
204 "name": {"$ref": "#/definitions/misc/metadata_name"},
179 "tags": {"$ref": "#/definitions/misc/metadata_tags"}
205 "tags": {"$ref": "#/definitions/misc/metadata_tags"}
180 },
206 },
181 "additionalProperties": true
207 "additionalProperties": true
182 }
208 }
183 }
209 }
184 },
210 },
185
211
186 "output": {
212 "output": {
187 "type": "object",
213 "type": "object",
188 "oneOf": [
214 "oneOf": [
189 {"$ref": "#/definitions/execute_result"},
215 {"$ref": "#/definitions/execute_result"},
190 {"$ref": "#/definitions/display_data"},
216 {"$ref": "#/definitions/display_data"},
191 {"$ref": "#/definitions/stream"},
217 {"$ref": "#/definitions/stream"},
192 {"$ref": "#/definitions/error"}
218 {"$ref": "#/definitions/error"}
193 ]
219 ]
194 },
220 },
195
221
196 "execute_result": {
222 "execute_result": {
197 "description": "Result of executing a code cell.",
223 "description": "Result of executing a code cell.",
198 "type": "object",
224 "type": "object",
199 "additionalProperties": false,
225 "additionalProperties": false,
200 "required": ["output_type", "data", "metadata", "execution_count"],
226 "required": ["output_type", "data", "metadata", "execution_count"],
201 "properties": {
227 "properties": {
202 "output_type": {
228 "output_type": {
203 "description": "Type of cell output.",
229 "description": "Type of cell output.",
204 "enum": ["execute_result"]
230 "enum": ["execute_result"]
205 },
231 },
206 "execution_count": {
232 "execution_count": {
207 "description": "A result's prompt number.",
233 "description": "A result's prompt number.",
208 "type": ["integer", "null"],
234 "type": ["integer", "null"],
209 "minimum": 0
235 "minimum": 0
210 },
236 },
211 "data": {"$ref": "#/definitions/misc/mimebundle"},
237 "data": {"$ref": "#/definitions/misc/mimebundle"},
212 "metadata": {"$ref": "#/definitions/misc/output_metadata"}
238 "metadata": {"$ref": "#/definitions/misc/output_metadata"}
213 }
239 }
214 },
240 },
215
241
216 "display_data": {
242 "display_data": {
217 "description": "Data displayed as a result of code cell execution.",
243 "description": "Data displayed as a result of code cell execution.",
218 "type": "object",
244 "type": "object",
219 "additionalProperties": false,
245 "additionalProperties": false,
220 "required": ["output_type", "data", "metadata"],
246 "required": ["output_type", "data", "metadata"],
221 "properties": {
247 "properties": {
222 "output_type": {
248 "output_type": {
223 "description": "Type of cell output.",
249 "description": "Type of cell output.",
224 "enum": ["display_data"]
250 "enum": ["display_data"]
225 },
251 },
226 "data": {"$ref": "#/definitions/misc/mimebundle"},
252 "data": {"$ref": "#/definitions/misc/mimebundle"},
227 "metadata": {"$ref": "#/definitions/misc/output_metadata"}
253 "metadata": {"$ref": "#/definitions/misc/output_metadata"}
228 }
254 }
229 },
255 },
230
256
231 "stream": {
257 "stream": {
232 "description": "Stream output from a code cell.",
258 "description": "Stream output from a code cell.",
233 "type": "object",
259 "type": "object",
234 "additionalProperties": false,
260 "additionalProperties": false,
235 "required": ["output_type", "name", "text"],
261 "required": ["output_type", "name", "text"],
236 "properties": {
262 "properties": {
237 "output_type": {
263 "output_type": {
238 "description": "Type of cell output.",
264 "description": "Type of cell output.",
239 "enum": ["stream"]
265 "enum": ["stream"]
240 },
266 },
241 "name": {
267 "name": {
242 "description": "The name of the stream (stdout, stderr).",
268 "description": "The name of the stream (stdout, stderr).",
243 "type": "string"
269 "type": "string"
244 },
270 },
245 "text": {
271 "text": {
246 "description": "The stream's text output, represented as an array of strings.",
272 "description": "The stream's text output, represented as an array of strings.",
247 "$ref": "#/definitions/misc/multiline_string"
273 "$ref": "#/definitions/misc/multiline_string"
248 }
274 }
249 }
275 }
250 },
276 },
251
277
252 "error": {
278 "error": {
253 "description": "Output of an error that occurred during code cell execution.",
279 "description": "Output of an error that occurred during code cell execution.",
254 "type": "object",
280 "type": "object",
255 "additionalProperties": false,
281 "additionalProperties": false,
256 "required": ["output_type", "ename", "evalue", "traceback"],
282 "required": ["output_type", "ename", "evalue", "traceback"],
257 "properties": {
283 "properties": {
258 "output_type": {
284 "output_type": {
259 "description": "Type of cell output.",
285 "description": "Type of cell output.",
260 "enum": ["error"]
286 "enum": ["error"]
261 },
287 },
262 "ename": {
288 "ename": {
263 "description": "The name of the error.",
289 "description": "The name of the error.",
264 "type": "string"
290 "type": "string"
265 },
291 },
266 "evalue": {
292 "evalue": {
267 "description": "The value, or message, of the error.",
293 "description": "The value, or message, of the error.",
268 "type": "string"
294 "type": "string"
269 },
295 },
270 "traceback": {
296 "traceback": {
271 "description": "The error's traceback, represented as an array of strings.",
297 "description": "The error's traceback, represented as an array of strings.",
272 "type": "array",
298 "type": "array",
273 "items": {"type": "string"}
299 "items": {"type": "string"}
274 }
300 }
275 }
301 }
276 },
302 },
277
303
278 "unrecognized_output": {
304 "unrecognized_output": {
279 "description": "Unrecognized output from a future minor-revision to the notebook format.",
305 "description": "Unrecognized output from a future minor-revision to the notebook format.",
280 "type": "object",
306 "type": "object",
281 "additionalProperties": true,
307 "additionalProperties": true,
282 "required": ["output_type"],
308 "required": ["output_type"],
283 "properties": {
309 "properties": {
284 "output_type": {
310 "output_type": {
285 "description": "Type of cell output.",
311 "description": "Type of cell output.",
286 "not": {
312 "not": {
287 "enum": ["execute_result", "display_data", "stream", "error"]
313 "enum": ["execute_result", "display_data", "stream", "error"]
288 }
314 }
289 }
315 }
290 }
316 }
291 },
317 },
292
318
293 "misc": {
319 "misc": {
294 "metadata_name": {
320 "metadata_name": {
295 "description": "The cell's name. If present, must be a non-empty string.",
321 "description": "The cell's name. If present, must be a non-empty string.",
296 "type": "string",
322 "type": "string",
297 "pattern": "^.+$"
323 "pattern": "^.+$"
298 },
324 },
299 "metadata_tags": {
325 "metadata_tags": {
300 "description": "The cell's tags. Tags must be unique, and must not contain commas.",
326 "description": "The cell's tags. Tags must be unique, and must not contain commas.",
301 "type": "array",
327 "type": "array",
302 "uniqueItems": true,
328 "uniqueItems": true,
303 "items": {
329 "items": {
304 "type": "string",
330 "type": "string",
305 "pattern": "^[^,]+$"
331 "pattern": "^[^,]+$"
306 }
332 }
307 },
333 },
308 "source": {
334 "source": {
309 "description": "Contents of the cell, represented as an array of lines.",
335 "description": "Contents of the cell, represented as an array of lines.",
310 "$ref": "#/definitions/misc/multiline_string"
336 "$ref": "#/definitions/misc/multiline_string"
311 },
337 },
312 "execution_count": {
338 "execution_count": {
313 "description": "The code cell's prompt number. Will be null if the cell has not been run.",
339 "description": "The code cell's prompt number. Will be null if the cell has not been run.",
314 "type": ["integer", "null"],
340 "type": ["integer", "null"],
315 "minimum": 0
341 "minimum": 0
316 },
342 },
317 "mimebundle": {
343 "mimebundle": {
318 "description": "A mime-type keyed dictionary of data",
344 "description": "A mime-type keyed dictionary of data",
319 "type": "object",
345 "type": "object",
320 "additionalProperties": false,
346 "additionalProperties": false,
321 "properties": {
347 "properties": {
322 "application/json": {
348 "application/json": {
323 "type": "object"
349 "type": "object"
324 }
350 }
325 },
351 },
326 "patternProperties": {
352 "patternProperties": {
327 "^(?!application/json$)[a-zA-Z0-9]+/[a-zA-Z0-9\\-\\+\\.]+$": {
353 "^(?!application/json$)[a-zA-Z0-9]+/[a-zA-Z0-9\\-\\+\\.]+$": {
328 "description": "mimetype output (e.g. text/plain), represented as either an array of strings or a string.",
354 "description": "mimetype output (e.g. text/plain), represented as either an array of strings or a string.",
329 "$ref": "#/definitions/misc/multiline_string"
355 "$ref": "#/definitions/misc/multiline_string"
330 }
356 }
331 }
357 }
332 },
358 },
333 "output_metadata": {
359 "output_metadata": {
334 "description": "Cell output metadata.",
360 "description": "Cell output metadata.",
335 "type": "object",
361 "type": "object",
336 "additionalProperties": true
362 "additionalProperties": true
337 },
363 },
338 "multiline_string": {
364 "multiline_string": {
339 "oneOf" : [
365 "oneOf" : [
340 {"type": "string"},
366 {"type": "string"},
341 {
367 {
342 "type": "array",
368 "type": "array",
343 "items": {"type": "string"}
369 "items": {"type": "string"}
344 }
370 }
345 ]
371 ]
346 }
372 }
347 }
373 }
348 }
374 }
349 }
375 }
@@ -1,1192 +1,1201 b''
1 .. _messaging:
1 .. _messaging:
2
2
3 ======================
3 ======================
4 Messaging in IPython
4 Messaging in IPython
5 ======================
5 ======================
6
6
7
7
8 Versioning
8 Versioning
9 ==========
9 ==========
10
10
11 The IPython message specification is versioned independently of IPython.
11 The IPython message specification is versioned independently of IPython.
12 The current version of the specification is 5.0.
12 The current version of the specification is 5.0.
13
13
14
14
15 Introduction
15 Introduction
16 ============
16 ============
17
17
18 This document explains the basic communications design and messaging
18 This document explains the basic communications design and messaging
19 specification for how the various IPython objects interact over a network
19 specification for how the various IPython objects interact over a network
20 transport. The current implementation uses the ZeroMQ_ library for messaging
20 transport. The current implementation uses the ZeroMQ_ library for messaging
21 within and between hosts.
21 within and between hosts.
22
22
23 .. Note::
23 .. Note::
24
24
25 This document should be considered the authoritative description of the
25 This document should be considered the authoritative description of the
26 IPython messaging protocol, and all developers are strongly encouraged to
26 IPython messaging protocol, and all developers are strongly encouraged to
27 keep it updated as the implementation evolves, so that we have a single
27 keep it updated as the implementation evolves, so that we have a single
28 common reference for all protocol details.
28 common reference for all protocol details.
29
29
30 The basic design is explained in the following diagram:
30 The basic design is explained in the following diagram:
31
31
32 .. image:: figs/frontend-kernel.png
32 .. image:: figs/frontend-kernel.png
33 :width: 450px
33 :width: 450px
34 :alt: IPython kernel/frontend messaging architecture.
34 :alt: IPython kernel/frontend messaging architecture.
35 :align: center
35 :align: center
36 :target: ../_images/frontend-kernel.png
36 :target: ../_images/frontend-kernel.png
37
37
38 A single kernel can be simultaneously connected to one or more frontends. The
38 A single kernel can be simultaneously connected to one or more frontends. The
39 kernel has three sockets that serve the following functions:
39 kernel has three sockets that serve the following functions:
40
40
41 1. Shell: this single ROUTER socket allows multiple incoming connections from
41 1. Shell: this single ROUTER socket allows multiple incoming connections from
42 frontends, and this is the socket where requests for code execution, object
42 frontends, and this is the socket where requests for code execution, object
43 information, prompts, etc. are made to the kernel by any frontend. The
43 information, prompts, etc. are made to the kernel by any frontend. The
44 communication on this socket is a sequence of request/reply actions from
44 communication on this socket is a sequence of request/reply actions from
45 each frontend and the kernel.
45 each frontend and the kernel.
46
46
47 2. IOPub: this socket is the 'broadcast channel' where the kernel publishes all
47 2. IOPub: this socket is the 'broadcast channel' where the kernel publishes all
48 side effects (stdout, stderr, etc.) as well as the requests coming from any
48 side effects (stdout, stderr, etc.) as well as the requests coming from any
49 client over the shell socket and its own requests on the stdin socket. There
49 client over the shell socket and its own requests on the stdin socket. There
50 are a number of actions in Python which generate side effects: :func:`print`
50 are a number of actions in Python which generate side effects: :func:`print`
51 writes to ``sys.stdout``, errors generate tracebacks, etc. Additionally, in
51 writes to ``sys.stdout``, errors generate tracebacks, etc. Additionally, in
52 a multi-client scenario, we want all frontends to be able to know what each
52 a multi-client scenario, we want all frontends to be able to know what each
53 other has sent to the kernel (this can be useful in collaborative scenarios,
53 other has sent to the kernel (this can be useful in collaborative scenarios,
54 for example). This socket allows both side effects and the information
54 for example). This socket allows both side effects and the information
55 about communications taking place with one client over the shell channel
55 about communications taking place with one client over the shell channel
56 to be made available to all clients in a uniform manner.
56 to be made available to all clients in a uniform manner.
57
57
58 3. stdin: this ROUTER socket is connected to all frontends, and it allows
58 3. stdin: this ROUTER socket is connected to all frontends, and it allows
59 the kernel to request input from the active frontend when :func:`raw_input` is called.
59 the kernel to request input from the active frontend when :func:`raw_input` is called.
60 The frontend that executed the code has a DEALER socket that acts as a 'virtual keyboard'
60 The frontend that executed the code has a DEALER socket that acts as a 'virtual keyboard'
61 for the kernel while this communication is happening (illustrated in the
61 for the kernel while this communication is happening (illustrated in the
62 figure by the black outline around the central keyboard). In practice,
62 figure by the black outline around the central keyboard). In practice,
63 frontends may display such kernel requests using a special input widget or
63 frontends may display such kernel requests using a special input widget or
64 otherwise indicating that the user is to type input for the kernel instead
64 otherwise indicating that the user is to type input for the kernel instead
65 of normal commands in the frontend.
65 of normal commands in the frontend.
66
66
67 All messages are tagged with enough information (details below) for clients
67 All messages are tagged with enough information (details below) for clients
68 to know which messages come from their own interaction with the kernel and
68 to know which messages come from their own interaction with the kernel and
69 which ones are from other clients, so they can display each type
69 which ones are from other clients, so they can display each type
70 appropriately.
70 appropriately.
71
71
72 4. Control: This channel is identical to Shell, but operates on a separate socket,
72 4. Control: This channel is identical to Shell, but operates on a separate socket,
73 to allow important messages to avoid queueing behind execution requests (e.g. shutdown or abort).
73 to allow important messages to avoid queueing behind execution requests (e.g. shutdown or abort).
74
74
75 The actual format of the messages allowed on each of these channels is
75 The actual format of the messages allowed on each of these channels is
76 specified below. Messages are dicts of dicts with string keys and values that
76 specified below. Messages are dicts of dicts with string keys and values that
77 are reasonably representable in JSON. Our current implementation uses JSON
77 are reasonably representable in JSON. Our current implementation uses JSON
78 explicitly as its message format, but this shouldn't be considered a permanent
78 explicitly as its message format, but this shouldn't be considered a permanent
79 feature. As we've discovered that JSON has non-trivial performance issues due
79 feature. As we've discovered that JSON has non-trivial performance issues due
80 to excessive copying, we may in the future move to a pure pickle-based raw
80 to excessive copying, we may in the future move to a pure pickle-based raw
81 message format. However, it should be possible to easily convert from the raw
81 message format. However, it should be possible to easily convert from the raw
82 objects to JSON, since we may have non-python clients (e.g. a web frontend).
82 objects to JSON, since we may have non-python clients (e.g. a web frontend).
83 As long as it's easy to make a JSON version of the objects that is a faithful
83 As long as it's easy to make a JSON version of the objects that is a faithful
84 representation of all the data, we can communicate with such clients.
84 representation of all the data, we can communicate with such clients.
85
85
86 .. Note::
86 .. Note::
87
87
88 Not all of these have yet been fully fleshed out, but the key ones are, see
88 Not all of these have yet been fully fleshed out, but the key ones are, see
89 kernel and frontend files for actual implementation details.
89 kernel and frontend files for actual implementation details.
90
90
91 General Message Format
91 General Message Format
92 ======================
92 ======================
93
93
94 A message is defined by the following four-dictionary structure::
94 A message is defined by the following four-dictionary structure::
95
95
96 {
96 {
97 # The message header contains a pair of unique identifiers for the
97 # The message header contains a pair of unique identifiers for the
98 # originating session and the actual message id, in addition to the
98 # originating session and the actual message id, in addition to the
99 # username for the process that generated the message. This is useful in
99 # username for the process that generated the message. This is useful in
100 # collaborative settings where multiple users may be interacting with the
100 # collaborative settings where multiple users may be interacting with the
101 # same kernel simultaneously, so that frontends can label the various
101 # same kernel simultaneously, so that frontends can label the various
102 # messages in a meaningful way.
102 # messages in a meaningful way.
103 'header' : {
103 'header' : {
104 'msg_id' : uuid,
104 'msg_id' : uuid,
105 'username' : str,
105 'username' : str,
106 'session' : uuid,
106 'session' : uuid,
107 # All recognized message type strings are listed below.
107 # All recognized message type strings are listed below.
108 'msg_type' : str,
108 'msg_type' : str,
109 # the message protocol version
109 # the message protocol version
110 'version' : '5.0',
110 'version' : '5.0',
111 },
111 },
112
112
113 # In a chain of messages, the header from the parent is copied so that
113 # In a chain of messages, the header from the parent is copied so that
114 # clients can track where messages come from.
114 # clients can track where messages come from.
115 'parent_header' : dict,
115 'parent_header' : dict,
116
116
117 # Any metadata associated with the message.
117 # Any metadata associated with the message.
118 'metadata' : dict,
118 'metadata' : dict,
119
119
120 # The actual content of the message must be a dict, whose structure
120 # The actual content of the message must be a dict, whose structure
121 # depends on the message type.
121 # depends on the message type.
122 'content' : dict,
122 'content' : dict,
123 }
123 }
124
124
125 .. versionchanged:: 5.0
125 .. versionchanged:: 5.0
126
126
127 ``version`` key added to the header.
127 ``version`` key added to the header.
128
128
129 .. _wire_protocol:
129 .. _wire_protocol:
130
130
131 The Wire Protocol
131 The Wire Protocol
132 =================
132 =================
133
133
134
134
135 This message format exists at a high level,
135 This message format exists at a high level,
136 but does not describe the actual *implementation* at the wire level in zeromq.
136 but does not describe the actual *implementation* at the wire level in zeromq.
137 The canonical implementation of the message spec is our :class:`~IPython.kernel.zmq.session.Session` class.
137 The canonical implementation of the message spec is our :class:`~IPython.kernel.zmq.session.Session` class.
138
138
139 .. note::
139 .. note::
140
140
141 This section should only be relevant to non-Python consumers of the protocol.
141 This section should only be relevant to non-Python consumers of the protocol.
142 Python consumers should simply import and use IPython's own implementation of the wire protocol
142 Python consumers should simply import and use IPython's own implementation of the wire protocol
143 in the :class:`IPython.kernel.zmq.session.Session` object.
143 in the :class:`IPython.kernel.zmq.session.Session` object.
144
144
145 Every message is serialized to a sequence of at least six blobs of bytes:
145 Every message is serialized to a sequence of at least six blobs of bytes:
146
146
147 .. sourcecode:: python
147 .. sourcecode:: python
148
148
149 [
149 [
150 b'u-u-i-d', # zmq identity(ies)
150 b'u-u-i-d', # zmq identity(ies)
151 b'<IDS|MSG>', # delimiter
151 b'<IDS|MSG>', # delimiter
152 b'baddad42', # HMAC signature
152 b'baddad42', # HMAC signature
153 b'{header}', # serialized header dict
153 b'{header}', # serialized header dict
154 b'{parent_header}', # serialized parent header dict
154 b'{parent_header}', # serialized parent header dict
155 b'{metadata}', # serialized metadata dict
155 b'{metadata}', # serialized metadata dict
156 b'{content}, # serialized content dict
156 b'{content}, # serialized content dict
157 b'blob', # extra raw data buffer(s)
157 b'blob', # extra raw data buffer(s)
158 ...
158 ...
159 ]
159 ]
160
160
161 The front of the message is the ZeroMQ routing prefix,
161 The front of the message is the ZeroMQ routing prefix,
162 which can be zero or more socket identities.
162 which can be zero or more socket identities.
163 This is every piece of the message prior to the delimiter key ``<IDS|MSG>``.
163 This is every piece of the message prior to the delimiter key ``<IDS|MSG>``.
164 In the case of IOPub, there should be just one prefix component,
164 In the case of IOPub, there should be just one prefix component,
165 which is the topic for IOPub subscribers, e.g. ``execute_result``, ``display_data``.
165 which is the topic for IOPub subscribers, e.g. ``execute_result``, ``display_data``.
166
166
167 .. note::
167 .. note::
168
168
169 In most cases, the IOPub topics are irrelevant and completely ignored,
169 In most cases, the IOPub topics are irrelevant and completely ignored,
170 because frontends just subscribe to all topics.
170 because frontends just subscribe to all topics.
171 The convention used in the IPython kernel is to use the msg_type as the topic,
171 The convention used in the IPython kernel is to use the msg_type as the topic,
172 and possibly extra information about the message, e.g. ``execute_result`` or ``stream.stdout``
172 and possibly extra information about the message, e.g. ``execute_result`` or ``stream.stdout``
173
173
174 After the delimiter is the `HMAC`_ signature of the message, used for authentication.
174 After the delimiter is the `HMAC`_ signature of the message, used for authentication.
175 If authentication is disabled, this should be an empty string.
175 If authentication is disabled, this should be an empty string.
176 By default, the hashing function used for computing these signatures is sha256.
176 By default, the hashing function used for computing these signatures is sha256.
177
177
178 .. _HMAC: http://en.wikipedia.org/wiki/HMAC
178 .. _HMAC: http://en.wikipedia.org/wiki/HMAC
179
179
180 .. note::
180 .. note::
181
181
182 To disable authentication and signature checking,
182 To disable authentication and signature checking,
183 set the `key` field of a connection file to an empty string.
183 set the `key` field of a connection file to an empty string.
184
184
185 The signature is the HMAC hex digest of the concatenation of:
185 The signature is the HMAC hex digest of the concatenation of:
186
186
187 - A shared key (typically the ``key`` field of a connection file)
187 - A shared key (typically the ``key`` field of a connection file)
188 - The serialized header dict
188 - The serialized header dict
189 - The serialized parent header dict
189 - The serialized parent header dict
190 - The serialized metadata dict
190 - The serialized metadata dict
191 - The serialized content dict
191 - The serialized content dict
192
192
193 In Python, this is implemented via:
193 In Python, this is implemented via:
194
194
195 .. sourcecode:: python
195 .. sourcecode:: python
196
196
197 # once:
197 # once:
198 digester = HMAC(key, digestmod=hashlib.sha256)
198 digester = HMAC(key, digestmod=hashlib.sha256)
199
199
200 # for each message
200 # for each message
201 d = digester.copy()
201 d = digester.copy()
202 for serialized_dict in (header, parent, metadata, content):
202 for serialized_dict in (header, parent, metadata, content):
203 d.update(serialized_dict)
203 d.update(serialized_dict)
204 signature = d.hexdigest()
204 signature = d.hexdigest()
205
205
206 After the signature is the actual message, always in four frames of bytes.
206 After the signature is the actual message, always in four frames of bytes.
207 The four dictionaries that compose a message are serialized separately,
207 The four dictionaries that compose a message are serialized separately,
208 in the order of header, parent header, metadata, and content.
208 in the order of header, parent header, metadata, and content.
209 These can be serialized by any function that turns a dict into bytes.
209 These can be serialized by any function that turns a dict into bytes.
210 The default and most common serialization is JSON, but msgpack and pickle
210 The default and most common serialization is JSON, but msgpack and pickle
211 are common alternatives.
211 are common alternatives.
212
212
213 After the serialized dicts are zero to many raw data buffers,
213 After the serialized dicts are zero to many raw data buffers,
214 which can be used by message types that support binary data (mainly apply and data_pub).
214 which can be used by message types that support binary data (mainly apply and data_pub).
215
215
216
216
217 Python functional API
217 Python functional API
218 =====================
218 =====================
219
219
220 As messages are dicts, they map naturally to a ``func(**kw)`` call form. We
220 As messages are dicts, they map naturally to a ``func(**kw)`` call form. We
221 should develop, at a few key points, functional forms of all the requests that
221 should develop, at a few key points, functional forms of all the requests that
222 take arguments in this manner and automatically construct the necessary dict
222 take arguments in this manner and automatically construct the necessary dict
223 for sending.
223 for sending.
224
224
225 In addition, the Python implementation of the message specification extends
225 In addition, the Python implementation of the message specification extends
226 messages upon deserialization to the following form for convenience::
226 messages upon deserialization to the following form for convenience::
227
227
228 {
228 {
229 'header' : dict,
229 'header' : dict,
230 # The msg's unique identifier and type are always stored in the header,
230 # The msg's unique identifier and type are always stored in the header,
231 # but the Python implementation copies them to the top level.
231 # but the Python implementation copies them to the top level.
232 'msg_id' : uuid,
232 'msg_id' : uuid,
233 'msg_type' : str,
233 'msg_type' : str,
234 'parent_header' : dict,
234 'parent_header' : dict,
235 'content' : dict,
235 'content' : dict,
236 'metadata' : dict,
236 'metadata' : dict,
237 }
237 }
238
238
239 All messages sent to or received by any IPython process should have this
239 All messages sent to or received by any IPython process should have this
240 extended structure.
240 extended structure.
241
241
242
242
243 Messages on the shell ROUTER/DEALER sockets
243 Messages on the shell ROUTER/DEALER sockets
244 ===========================================
244 ===========================================
245
245
246 .. _execute:
246 .. _execute:
247
247
248 Execute
248 Execute
249 -------
249 -------
250
250
251 This message type is used by frontends to ask the kernel to execute code on
251 This message type is used by frontends to ask the kernel to execute code on
252 behalf of the user, in a namespace reserved to the user's variables (and thus
252 behalf of the user, in a namespace reserved to the user's variables (and thus
253 separate from the kernel's own internal code and variables).
253 separate from the kernel's own internal code and variables).
254
254
255 Message type: ``execute_request``::
255 Message type: ``execute_request``::
256
256
257 content = {
257 content = {
258 # Source code to be executed by the kernel, one or more lines.
258 # Source code to be executed by the kernel, one or more lines.
259 'code' : str,
259 'code' : str,
260
260
261 # A boolean flag which, if True, signals the kernel to execute
261 # A boolean flag which, if True, signals the kernel to execute
262 # this code as quietly as possible.
262 # this code as quietly as possible.
263 # silent=True forces store_history to be False,
263 # silent=True forces store_history to be False,
264 # and will *not*:
264 # and will *not*:
265 # - broadcast output on the IOPUB channel
265 # - broadcast output on the IOPUB channel
266 # - have an execute_result
266 # - have an execute_result
267 # The default is False.
267 # The default is False.
268 'silent' : bool,
268 'silent' : bool,
269
269
270 # A boolean flag which, if True, signals the kernel to populate history
270 # A boolean flag which, if True, signals the kernel to populate history
271 # The default is True if silent is False. If silent is True, store_history
271 # The default is True if silent is False. If silent is True, store_history
272 # is forced to be False.
272 # is forced to be False.
273 'store_history' : bool,
273 'store_history' : bool,
274
274
275 # A dict mapping names to expressions to be evaluated in the
275 # A dict mapping names to expressions to be evaluated in the
276 # user's dict. The rich display-data representation of each will be evaluated after execution.
276 # user's dict. The rich display-data representation of each will be evaluated after execution.
277 # See the display_data content for the structure of the representation data.
277 # See the display_data content for the structure of the representation data.
278 'user_expressions' : dict,
278 'user_expressions' : dict,
279
279
280 # Some frontends do not support stdin requests.
280 # Some frontends do not support stdin requests.
281 # If raw_input is called from code executed from such a frontend,
281 # If raw_input is called from code executed from such a frontend,
282 # a StdinNotImplementedError will be raised.
282 # a StdinNotImplementedError will be raised.
283 'allow_stdin' : True,
283 'allow_stdin' : True,
284 }
284 }
285
285
286 .. versionchanged:: 5.0
286 .. versionchanged:: 5.0
287
287
288 ``user_variables`` removed, because it is redundant with user_expressions.
288 ``user_variables`` removed, because it is redundant with user_expressions.
289
289
290 The ``code`` field contains a single string (possibly multiline) to be executed.
290 The ``code`` field contains a single string (possibly multiline) to be executed.
291
291
292 The ``user_expressions`` field deserves a detailed explanation. In the past, IPython had
292 The ``user_expressions`` field deserves a detailed explanation. In the past, IPython had
293 the notion of a prompt string that allowed arbitrary code to be evaluated, and
293 the notion of a prompt string that allowed arbitrary code to be evaluated, and
294 this was put to good use by many in creating prompts that displayed system
294 this was put to good use by many in creating prompts that displayed system
295 status, path information, and even more esoteric uses like remote instrument
295 status, path information, and even more esoteric uses like remote instrument
296 status acquired over the network. But now that IPython has a clean separation
296 status acquired over the network. But now that IPython has a clean separation
297 between the kernel and the clients, the kernel has no prompt knowledge; prompts
297 between the kernel and the clients, the kernel has no prompt knowledge; prompts
298 are a frontend feature, and it should be even possible for different
298 are a frontend feature, and it should be even possible for different
299 frontends to display different prompts while interacting with the same kernel.
299 frontends to display different prompts while interacting with the same kernel.
300 ``user_expressions`` can be used to retrieve this information.
300 ``user_expressions`` can be used to retrieve this information.
301
301
302 Any error in evaluating any expression in ``user_expressions`` will result in
302 Any error in evaluating any expression in ``user_expressions`` will result in
303 only that key containing a standard error message, of the form::
303 only that key containing a standard error message, of the form::
304
304
305 {
305 {
306 'status' : 'error',
306 'status' : 'error',
307 'ename' : 'NameError',
307 'ename' : 'NameError',
308 'evalue' : 'foo',
308 'evalue' : 'foo',
309 'traceback' : ...
309 'traceback' : ...
310 }
310 }
311
311
312 .. Note::
312 .. Note::
313
313
314 In order to obtain the current execution counter for the purposes of
314 In order to obtain the current execution counter for the purposes of
315 displaying input prompts, frontends may make an execution request with an
315 displaying input prompts, frontends may make an execution request with an
316 empty code string and ``silent=True``.
316 empty code string and ``silent=True``.
317
317
318 Upon completion of the execution request, the kernel *always* sends a reply,
318 Upon completion of the execution request, the kernel *always* sends a reply,
319 with a status code indicating what happened and additional data depending on
319 with a status code indicating what happened and additional data depending on
320 the outcome. See :ref:`below <execution_results>` for the possible return
320 the outcome. See :ref:`below <execution_results>` for the possible return
321 codes and associated data.
321 codes and associated data.
322
322
323 .. seealso::
323 .. seealso::
324
324
325 :ref:`execution_semantics`
325 :ref:`execution_semantics`
326
326
327 .. _execution_counter:
327 .. _execution_counter:
328
328
329 Execution counter (prompt number)
329 Execution counter (prompt number)
330 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
330 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
331
331
332 The kernel should have a single, monotonically increasing counter of all execution
332 The kernel should have a single, monotonically increasing counter of all execution
333 requests that are made with ``store_history=True``. This counter is used to populate
333 requests that are made with ``store_history=True``. This counter is used to populate
334 the ``In[n]`` and ``Out[n]`` prompts. The value of this counter will be returned as the
334 the ``In[n]`` and ``Out[n]`` prompts. The value of this counter will be returned as the
335 ``execution_count`` field of all ``execute_reply`` and ``execute_input`` messages.
335 ``execution_count`` field of all ``execute_reply`` and ``execute_input`` messages.
336
336
337 .. _execution_results:
337 .. _execution_results:
338
338
339 Execution results
339 Execution results
340 ~~~~~~~~~~~~~~~~~
340 ~~~~~~~~~~~~~~~~~
341
341
342 Message type: ``execute_reply``::
342 Message type: ``execute_reply``::
343
343
344 content = {
344 content = {
345 # One of: 'ok' OR 'error' OR 'abort'
345 # One of: 'ok' OR 'error' OR 'abort'
346 'status' : str,
346 'status' : str,
347
347
348 # The global kernel counter that increases by one with each request that
348 # The global kernel counter that increases by one with each request that
349 # stores history. This will typically be used by clients to display
349 # stores history. This will typically be used by clients to display
350 # prompt numbers to the user. If the request did not store history, this will
350 # prompt numbers to the user. If the request did not store history, this will
351 # be the current value of the counter in the kernel.
351 # be the current value of the counter in the kernel.
352 'execution_count' : int,
352 'execution_count' : int,
353 }
353 }
354
354
355 When status is 'ok', the following extra fields are present::
355 When status is 'ok', the following extra fields are present::
356
356
357 {
357 {
358 # 'payload' will be a list of payload dicts, and is optional.
358 # 'payload' will be a list of payload dicts, and is optional.
359 # payloads are considered deprecated.
359 # payloads are considered deprecated.
360 # The only requirement of each payload dict is that it have a 'source' key,
360 # The only requirement of each payload dict is that it have a 'source' key,
361 # which is a string classifying the payload (e.g. 'page').
361 # which is a string classifying the payload (e.g. 'page').
362
362
363 'payload' : list(dict),
363 'payload' : list(dict),
364
364
365 # Results for the user_expressions.
365 # Results for the user_expressions.
366 'user_expressions' : dict,
366 'user_expressions' : dict,
367 }
367 }
368
368
369 .. versionchanged:: 5.0
369 .. versionchanged:: 5.0
370
370
371 ``user_variables`` is removed, use user_expressions instead.
371 ``user_variables`` is removed, use user_expressions instead.
372
372
373 When status is 'error', the following extra fields are present::
373 When status is 'error', the following extra fields are present::
374
374
375 {
375 {
376 'ename' : str, # Exception name, as a string
376 'ename' : str, # Exception name, as a string
377 'evalue' : str, # Exception value, as a string
377 'evalue' : str, # Exception value, as a string
378
378
379 # The traceback will contain a list of frames, represented each as a
379 # The traceback will contain a list of frames, represented each as a
380 # string. For now we'll stick to the existing design of ultraTB, which
380 # string. For now we'll stick to the existing design of ultraTB, which
381 # controls exception level of detail statefully. But eventually we'll
381 # controls exception level of detail statefully. But eventually we'll
382 # want to grow into a model where more information is collected and
382 # want to grow into a model where more information is collected and
383 # packed into the traceback object, with clients deciding how little or
383 # packed into the traceback object, with clients deciding how little or
384 # how much of it to unpack. But for now, let's start with a simple list
384 # how much of it to unpack. But for now, let's start with a simple list
385 # of strings, since that requires only minimal changes to ultratb as
385 # of strings, since that requires only minimal changes to ultratb as
386 # written.
386 # written.
387 'traceback' : list,
387 'traceback' : list,
388 }
388 }
389
389
390
390
391 When status is 'abort', there are for now no additional data fields. This
391 When status is 'abort', there are for now no additional data fields. This
392 happens when the kernel was interrupted by a signal.
392 happens when the kernel was interrupted by a signal.
393
393
394 Payloads
394 Payloads
395 ********
395 ********
396
396
397 .. admonition:: Execution payloads
397 .. admonition:: Execution payloads
398
398
399 Payloads are considered deprecated, though their replacement is not yet implemented.
399 Payloads are considered deprecated, though their replacement is not yet implemented.
400
400
401 Payloads are a way to trigger frontend actions from the kernel. Current payloads:
401 Payloads are a way to trigger frontend actions from the kernel. Current payloads:
402
402
403 **page**: display data in a pager.
403 **page**: display data in a pager.
404
404
405 Pager output is used for introspection, or other displayed information that's not considered output.
405 Pager output is used for introspection, or other displayed information that's not considered output.
406 Pager payloads are generally displayed in a separate pane, that can be viewed alongside code,
406 Pager payloads are generally displayed in a separate pane, that can be viewed alongside code,
407 and are not included in notebook documents.
407 and are not included in notebook documents.
408
408
409 .. sourcecode:: python
409 .. sourcecode:: python
410
410
411 {
411 {
412 "source": "page",
412 "source": "page",
413 # mime-bundle of data to display in the pager.
413 # mime-bundle of data to display in the pager.
414 # Must include text/plain.
414 # Must include text/plain.
415 "data": mimebundle,
415 "data": mimebundle,
416 # line offset to start from
416 # line offset to start from
417 "start": int,
417 "start": int,
418 }
418 }
419
419
420 **set_next_input**: create a new output
420 **set_next_input**: create a new output
421
421
422 used to create new cells in the notebook,
422 used to create new cells in the notebook,
423 or set the next input in a console interface.
423 or set the next input in a console interface.
424 The main example being ``%load``.
424 The main example being ``%load``.
425
425
426 .. sourcecode:: python
426 .. sourcecode:: python
427
427
428 {
428 {
429 "source": "set_next_input",
429 "source": "set_next_input",
430 # the text contents of the cell to create
430 # the text contents of the cell to create
431 "text": "some cell content",
431 "text": "some cell content",
432 }
432 }
433
433
434 **edit**: open a file for editing.
434 **edit**: open a file for editing.
435
435
436 Triggered by `%edit`. Only the QtConsole currently supports edit payloads.
436 Triggered by `%edit`. Only the QtConsole currently supports edit payloads.
437
437
438 .. sourcecode:: python
438 .. sourcecode:: python
439
439
440 {
440 {
441 "source": "edit",
441 "source": "edit",
442 "filename": "/path/to/file.py", # the file to edit
442 "filename": "/path/to/file.py", # the file to edit
443 "line_number": int, # the line number to start with
443 "line_number": int, # the line number to start with
444 }
444 }
445
445
446 **ask_exit**: instruct the frontend to prompt the user for exit
446 **ask_exit**: instruct the frontend to prompt the user for exit
447
447
448 Allows the kernel to request exit, e.g. via ``%exit`` in IPython.
448 Allows the kernel to request exit, e.g. via ``%exit`` in IPython.
449 Only for console frontends.
449 Only for console frontends.
450
450
451 .. sourcecode:: python
451 .. sourcecode:: python
452
452
453 {
453 {
454 "source": "ask_exit",
454 "source": "ask_exit",
455 # whether the kernel should be left running, only closing the client
455 # whether the kernel should be left running, only closing the client
456 "keepkernel": bool,
456 "keepkernel": bool,
457 }
457 }
458
458
459
459
460 .. _msging_inspection:
460 .. _msging_inspection:
461
461
462 Introspection
462 Introspection
463 -------------
463 -------------
464
464
465 Code can be inspected to show useful information to the user.
465 Code can be inspected to show useful information to the user.
466 It is up to the Kernel to decide what information should be displayed, and its formatting.
466 It is up to the Kernel to decide what information should be displayed, and its formatting.
467
467
468 Message type: ``inspect_request``::
468 Message type: ``inspect_request``::
469
469
470 content = {
470 content = {
471 # The code context in which introspection is requested
471 # The code context in which introspection is requested
472 # this may be up to an entire multiline cell.
472 # this may be up to an entire multiline cell.
473 'code' : str,
473 'code' : str,
474
474
475 # The cursor position within 'code' (in unicode characters) where inspection is requested
475 # The cursor position within 'code' (in unicode characters) where inspection is requested
476 'cursor_pos' : int,
476 'cursor_pos' : int,
477
477
478 # The level of detail desired. In IPython, the default (0) is equivalent to typing
478 # The level of detail desired. In IPython, the default (0) is equivalent to typing
479 # 'x?' at the prompt, 1 is equivalent to 'x??'.
479 # 'x?' at the prompt, 1 is equivalent to 'x??'.
480 # The difference is up to kernels, but in IPython level 1 includes the source code
480 # The difference is up to kernels, but in IPython level 1 includes the source code
481 # if available.
481 # if available.
482 'detail_level' : 0 or 1,
482 'detail_level' : 0 or 1,
483 }
483 }
484
484
485 .. versionchanged:: 5.0
485 .. versionchanged:: 5.0
486
486
487 ``object_info_request`` renamed to ``inspect_request``.
487 ``object_info_request`` renamed to ``inspect_request``.
488
488
489 .. versionchanged:: 5.0
489 .. versionchanged:: 5.0
490
490
491 ``name`` key replaced with ``code`` and ``cursor_pos``,
491 ``name`` key replaced with ``code`` and ``cursor_pos``,
492 moving the lexing responsibility to the kernel.
492 moving the lexing responsibility to the kernel.
493
493
494 The reply is a mime-bundle, like a `display_data`_ message,
494 The reply is a mime-bundle, like a `display_data`_ message,
495 which should be a formatted representation of information about the context.
495 which should be a formatted representation of information about the context.
496 In the notebook, this is used to show tooltips over function calls, etc.
496 In the notebook, this is used to show tooltips over function calls, etc.
497
497
498 Message type: ``inspect_reply``::
498 Message type: ``inspect_reply``::
499
499
500 content = {
500 content = {
501 # 'ok' if the request succeeded or 'error', with error information as in all other replies.
501 # 'ok' if the request succeeded or 'error', with error information as in all other replies.
502 'status' : 'ok',
502 'status' : 'ok',
503
503
504 # data can be empty if nothing is found
504 # data can be empty if nothing is found
505 'data' : dict,
505 'data' : dict,
506 'metadata' : dict,
506 'metadata' : dict,
507 }
507 }
508
508
509 .. versionchanged:: 5.0
509 .. versionchanged:: 5.0
510
510
511 ``object_info_reply`` renamed to ``inspect_reply``.
511 ``object_info_reply`` renamed to ``inspect_reply``.
512
512
513 .. versionchanged:: 5.0
513 .. versionchanged:: 5.0
514
514
515 Reply is changed from structured data to a mime bundle, allowing formatting decisions to be made by the kernel.
515 Reply is changed from structured data to a mime bundle, allowing formatting decisions to be made by the kernel.
516
516
517 .. _msging_completion:
517 .. _msging_completion:
518
518
519 Completion
519 Completion
520 ----------
520 ----------
521
521
522 Message type: ``complete_request``::
522 Message type: ``complete_request``::
523
523
524 content = {
524 content = {
525 # The code context in which completion is requested
525 # The code context in which completion is requested
526 # this may be up to an entire multiline cell, such as
526 # this may be up to an entire multiline cell, such as
527 # 'foo = a.isal'
527 # 'foo = a.isal'
528 'code' : str,
528 'code' : str,
529
529
530 # The cursor position within 'code' (in unicode characters) where completion is requested
530 # The cursor position within 'code' (in unicode characters) where completion is requested
531 'cursor_pos' : int,
531 'cursor_pos' : int,
532 }
532 }
533
533
534 .. versionchanged:: 5.0
534 .. versionchanged:: 5.0
535
535
536 ``line``, ``block``, and ``text`` keys are removed in favor of a single ``code`` for context.
536 ``line``, ``block``, and ``text`` keys are removed in favor of a single ``code`` for context.
537 Lexing is up to the kernel.
537 Lexing is up to the kernel.
538
538
539
539
540 Message type: ``complete_reply``::
540 Message type: ``complete_reply``::
541
541
542 content = {
542 content = {
543 # The list of all matches to the completion request, such as
543 # The list of all matches to the completion request, such as
544 # ['a.isalnum', 'a.isalpha'] for the above example.
544 # ['a.isalnum', 'a.isalpha'] for the above example.
545 'matches' : list,
545 'matches' : list,
546
546
547 # The range of text that should be replaced by the above matches when a completion is accepted.
547 # The range of text that should be replaced by the above matches when a completion is accepted.
548 # typically cursor_end is the same as cursor_pos in the request.
548 # typically cursor_end is the same as cursor_pos in the request.
549 'cursor_start' : int,
549 'cursor_start' : int,
550 'cursor_end' : int,
550 'cursor_end' : int,
551
551
552 # Information that frontend plugins might use for extra display information about completions.
552 # Information that frontend plugins might use for extra display information about completions.
553 'metadata' : dict,
553 'metadata' : dict,
554
554
555 # status should be 'ok' unless an exception was raised during the request,
555 # status should be 'ok' unless an exception was raised during the request,
556 # in which case it should be 'error', along with the usual error message content
556 # in which case it should be 'error', along with the usual error message content
557 # in other messages.
557 # in other messages.
558 'status' : 'ok'
558 'status' : 'ok'
559 }
559 }
560
560
561 .. versionchanged:: 5.0
561 .. versionchanged:: 5.0
562
562
563 - ``matched_text`` is removed in favor of ``cursor_start`` and ``cursor_end``.
563 - ``matched_text`` is removed in favor of ``cursor_start`` and ``cursor_end``.
564 - ``metadata`` is added for extended information.
564 - ``metadata`` is added for extended information.
565
565
566 .. _msging_history:
566 .. _msging_history:
567
567
568 History
568 History
569 -------
569 -------
570
570
571 For clients to explicitly request history from a kernel. The kernel has all
571 For clients to explicitly request history from a kernel. The kernel has all
572 the actual execution history stored in a single location, so clients can
572 the actual execution history stored in a single location, so clients can
573 request it from the kernel when needed.
573 request it from the kernel when needed.
574
574
575 Message type: ``history_request``::
575 Message type: ``history_request``::
576
576
577 content = {
577 content = {
578
578
579 # If True, also return output history in the resulting dict.
579 # If True, also return output history in the resulting dict.
580 'output' : bool,
580 'output' : bool,
581
581
582 # If True, return the raw input history, else the transformed input.
582 # If True, return the raw input history, else the transformed input.
583 'raw' : bool,
583 'raw' : bool,
584
584
585 # So far, this can be 'range', 'tail' or 'search'.
585 # So far, this can be 'range', 'tail' or 'search'.
586 'hist_access_type' : str,
586 'hist_access_type' : str,
587
587
588 # If hist_access_type is 'range', get a range of input cells. session can
588 # If hist_access_type is 'range', get a range of input cells. session can
589 # be a positive session number, or a negative number to count back from
589 # be a positive session number, or a negative number to count back from
590 # the current session.
590 # the current session.
591 'session' : int,
591 'session' : int,
592 # start and stop are line numbers within that session.
592 # start and stop are line numbers within that session.
593 'start' : int,
593 'start' : int,
594 'stop' : int,
594 'stop' : int,
595
595
596 # If hist_access_type is 'tail' or 'search', get the last n cells.
596 # If hist_access_type is 'tail' or 'search', get the last n cells.
597 'n' : int,
597 'n' : int,
598
598
599 # If hist_access_type is 'search', get cells matching the specified glob
599 # If hist_access_type is 'search', get cells matching the specified glob
600 # pattern (with * and ? as wildcards).
600 # pattern (with * and ? as wildcards).
601 'pattern' : str,
601 'pattern' : str,
602
602
603 # If hist_access_type is 'search' and unique is true, do not
603 # If hist_access_type is 'search' and unique is true, do not
604 # include duplicated history. Default is false.
604 # include duplicated history. Default is false.
605 'unique' : bool,
605 'unique' : bool,
606
606
607 }
607 }
608
608
609 .. versionadded:: 4.0
609 .. versionadded:: 4.0
610 The key ``unique`` for ``history_request``.
610 The key ``unique`` for ``history_request``.
611
611
612 Message type: ``history_reply``::
612 Message type: ``history_reply``::
613
613
614 content = {
614 content = {
615 # A list of 3 tuples, either:
615 # A list of 3 tuples, either:
616 # (session, line_number, input) or
616 # (session, line_number, input) or
617 # (session, line_number, (input, output)),
617 # (session, line_number, (input, output)),
618 # depending on whether output was False or True, respectively.
618 # depending on whether output was False or True, respectively.
619 'history' : list,
619 'history' : list,
620 }
620 }
621
621
622 .. _msging_is_complete:
622 .. _msging_is_complete:
623
623
624 Code completeness
624 Code completeness
625 -----------------
625 -----------------
626
626
627 .. versionadded:: 5.0
627 .. versionadded:: 5.0
628
628
629 When the user enters a line in a console style interface, the console must
629 When the user enters a line in a console style interface, the console must
630 decide whether to immediately execute the current code, or whether to show a
630 decide whether to immediately execute the current code, or whether to show a
631 continuation prompt for further input. For instance, in Python ``a = 5`` would
631 continuation prompt for further input. For instance, in Python ``a = 5`` would
632 be executed immediately, while ``for i in range(5):`` would expect further input.
632 be executed immediately, while ``for i in range(5):`` would expect further input.
633
633
634 There are four possible replies:
634 There are four possible replies:
635
635
636 - *complete* code is ready to be executed
636 - *complete* code is ready to be executed
637 - *incomplete* code should prompt for another line
637 - *incomplete* code should prompt for another line
638 - *invalid* code will typically be sent for execution, so that the user sees the
638 - *invalid* code will typically be sent for execution, so that the user sees the
639 error soonest.
639 error soonest.
640 - *unknown* - if the kernel is not able to determine this. The frontend should
640 - *unknown* - if the kernel is not able to determine this. The frontend should
641 also handle the kernel not replying promptly. It may default to sending the
641 also handle the kernel not replying promptly. It may default to sending the
642 code for execution, or it may implement simple fallback heuristics for whether
642 code for execution, or it may implement simple fallback heuristics for whether
643 to execute the code (e.g. execute after a blank line).
643 to execute the code (e.g. execute after a blank line).
644
644
645 Frontends may have ways to override this, forcing the code to be sent for
645 Frontends may have ways to override this, forcing the code to be sent for
646 execution or forcing a continuation prompt.
646 execution or forcing a continuation prompt.
647
647
648 Message type: ``is_complete_request``::
648 Message type: ``is_complete_request``::
649
649
650 content = {
650 content = {
651 # The code entered so far as a multiline string
651 # The code entered so far as a multiline string
652 'code' : str,
652 'code' : str,
653 }
653 }
654
654
655 Message type: ``is_complete_reply``::
655 Message type: ``is_complete_reply``::
656
656
657 content = {
657 content = {
658 # One of 'complete', 'incomplete', 'invalid', 'unknown'
658 # One of 'complete', 'incomplete', 'invalid', 'unknown'
659 'status' : str,
659 'status' : str,
660
660
661 # If status is 'incomplete', indent should contain the characters to use
661 # If status is 'incomplete', indent should contain the characters to use
662 # to indent the next line. This is only a hint: frontends may ignore it
662 # to indent the next line. This is only a hint: frontends may ignore it
663 # and use their own autoindentation rules. For other statuses, this
663 # and use their own autoindentation rules. For other statuses, this
664 # field does not exist.
664 # field does not exist.
665 'indent': str,
665 'indent': str,
666 }
666 }
667
667
668 Connect
668 Connect
669 -------
669 -------
670
670
671 When a client connects to the request/reply socket of the kernel, it can issue
671 When a client connects to the request/reply socket of the kernel, it can issue
672 a connect request to get basic information about the kernel, such as the ports
672 a connect request to get basic information about the kernel, such as the ports
673 the other ZeroMQ sockets are listening on. This allows clients to only have
673 the other ZeroMQ sockets are listening on. This allows clients to only have
674 to know about a single port (the shell channel) to connect to a kernel.
674 to know about a single port (the shell channel) to connect to a kernel.
675
675
676 Message type: ``connect_request``::
676 Message type: ``connect_request``::
677
677
678 content = {
678 content = {
679 }
679 }
680
680
681 Message type: ``connect_reply``::
681 Message type: ``connect_reply``::
682
682
683 content = {
683 content = {
684 'shell_port' : int, # The port the shell ROUTER socket is listening on.
684 'shell_port' : int, # The port the shell ROUTER socket is listening on.
685 'iopub_port' : int, # The port the PUB socket is listening on.
685 'iopub_port' : int, # The port the PUB socket is listening on.
686 'stdin_port' : int, # The port the stdin ROUTER socket is listening on.
686 'stdin_port' : int, # The port the stdin ROUTER socket is listening on.
687 'hb_port' : int, # The port the heartbeat socket is listening on.
687 'hb_port' : int, # The port the heartbeat socket is listening on.
688 }
688 }
689
689
690 .. _msging_kernel_info:
690 .. _msging_kernel_info:
691
691
692 Kernel info
692 Kernel info
693 -----------
693 -----------
694
694
695 If a client needs to know information about the kernel, it can
695 If a client needs to know information about the kernel, it can
696 make a request of the kernel's information.
696 make a request of the kernel's information.
697 This message can be used to fetch core information of the
697 This message can be used to fetch core information of the
698 kernel, including language (e.g., Python), language version number and
698 kernel, including language (e.g., Python), language version number and
699 IPython version number, and the IPython message spec version number.
699 IPython version number, and the IPython message spec version number.
700
700
701 Message type: ``kernel_info_request``::
701 Message type: ``kernel_info_request``::
702
702
703 content = {
703 content = {
704 }
704 }
705
705
706 Message type: ``kernel_info_reply``::
706 Message type: ``kernel_info_reply``::
707
707
708 content = {
708 content = {
709 # Version of messaging protocol.
709 # Version of messaging protocol.
710 # The first integer indicates major version. It is incremented when
710 # The first integer indicates major version. It is incremented when
711 # there is any backward incompatible change.
711 # there is any backward incompatible change.
712 # The second integer indicates minor version. It is incremented when
712 # The second integer indicates minor version. It is incremented when
713 # there is any backward compatible change.
713 # there is any backward compatible change.
714 'protocol_version': 'X.Y.Z',
714 'protocol_version': 'X.Y.Z',
715
715
716 # The kernel implementation name
716 # The kernel implementation name
717 # (e.g. 'ipython' for the IPython kernel)
717 # (e.g. 'ipython' for the IPython kernel)
718 'implementation': str,
718 'implementation': str,
719
719
720 # Implementation version number.
720 # Implementation version number.
721 # The version number of the kernel's implementation
721 # The version number of the kernel's implementation
722 # (e.g. IPython.__version__ for the IPython kernel)
722 # (e.g. IPython.__version__ for the IPython kernel)
723 'implementation_version': 'X.Y.Z',
723 'implementation_version': 'X.Y.Z',
724
725 # Programming language in which kernel is implemented.
726 # Kernel included in IPython returns 'python'.
727 'language': str,
728
729 # Language version number.
730 # It is Python version number (e.g., '2.7.3') for the kernel
731 # included in IPython.
732 'language_version': 'X.Y.Z',
733
724
734 # Information about the language of code for the kernel
725 # Information about the language of code for the kernel
735 'language_info': {
726 'language_info': {
727 # Name of the programming language in which kernel is implemented.
728 # Kernel included in IPython returns 'python'.
729 'name': str,
730
731 # Language version number.
732 # It is Python version number (e.g., '2.7.3') for the kernel
733 # included in IPython.
734 'version': 'X.Y.Z',
735
736 # mimetype for script files in this language
736 'mimetype': str,
737 'mimetype': str,
737
738
738 # Extension without the dot, e.g. 'py'
739 # Extension without the dot, e.g. 'py'
739 'file_extension': str,
740 'file_extension': str,
740
741
741 # Pygments lexer, for highlighting
742 # Pygments lexer, for highlighting
742 # Only needed if it differs from the top level 'language' field.
743 # Only needed if it differs from the top level 'language' field.
743 'pygments_lexer': str,
744 'pygments_lexer': str,
744
745
745 # Codemirror mode, for for highlighting in the notebook.
746 # Codemirror mode, for for highlighting in the notebook.
746 # Only needed if it differs from the top level 'language' field.
747 # Only needed if it differs from the top level 'language' field.
747 'codemirror_mode': str or dict,
748 'codemirror_mode': str or dict,
748
749
749 # Nbconvert exporter, if notebooks written with this kernel should
750 # Nbconvert exporter, if notebooks written with this kernel should
750 # be exported with something other than the general 'script'
751 # be exported with something other than the general 'script'
751 # exporter.
752 # exporter.
752 'nbconvert_exporter': str,
753 'nbconvert_exporter': str,
753 },
754 },
754
755
755 # A banner of information about the kernel,
756 # A banner of information about the kernel,
756 # which may be desplayed in console environments.
757 # which may be desplayed in console environments.
757 'banner' : str,
758 'banner' : str,
758
759
759 # Optional: A list of dictionaries, each with keys 'text' and 'url'.
760 # Optional: A list of dictionaries, each with keys 'text' and 'url'.
760 # These will be displayed in the help menu in the notebook UI.
761 # These will be displayed in the help menu in the notebook UI.
761 'help_links': [
762 'help_links': [
762 {'text': str, 'url': str}
763 {'text': str, 'url': str}
763 ],
764 ],
764 }
765 }
765
766
766 Refer to the lists of available `Pygments lexers <http://pygments.org/docs/lexers/>`_
767 Refer to the lists of available `Pygments lexers <http://pygments.org/docs/lexers/>`_
767 and `codemirror modes <http://codemirror.net/mode/index.html>`_ for those fields.
768 and `codemirror modes <http://codemirror.net/mode/index.html>`_ for those fields.
768
769
769 .. versionchanged:: 5.0
770 .. versionchanged:: 5.0
770
771
771 Versions changed from lists of integers to strings.
772 Versions changed from lists of integers to strings.
772
773
773 .. versionchanged:: 5.0
774 .. versionchanged:: 5.0
774
775
775 ``ipython_version`` is removed.
776 ``ipython_version`` is removed.
776
777
777 .. versionchanged:: 5.0
778 .. versionchanged:: 5.0
778
779
779 ``language_info``, ``implementation``, ``implementation_version``, ``banner``
780 ``language_info``, ``implementation``, ``implementation_version``, ``banner``
780 and ``help_links`` keys are added.
781 and ``help_links`` keys are added.
781
782
783 .. versionchanged:: 5.0
784
785 ``language_version`` moved to ``language_info.version``
786
787 .. versionchanged:: 5.0
788
789 ``language`` moved to ``language_info.name``
790
782 .. _msging_shutdown:
791 .. _msging_shutdown:
783
792
784 Kernel shutdown
793 Kernel shutdown
785 ---------------
794 ---------------
786
795
787 The clients can request the kernel to shut itself down; this is used in
796 The clients can request the kernel to shut itself down; this is used in
788 multiple cases:
797 multiple cases:
789
798
790 - when the user chooses to close the client application via a menu or window
799 - when the user chooses to close the client application via a menu or window
791 control.
800 control.
792 - when the user types 'exit' or 'quit' (or their uppercase magic equivalents).
801 - when the user types 'exit' or 'quit' (or their uppercase magic equivalents).
793 - when the user chooses a GUI method (like the 'Ctrl-C' shortcut in the
802 - when the user chooses a GUI method (like the 'Ctrl-C' shortcut in the
794 IPythonQt client) to force a kernel restart to get a clean kernel without
803 IPythonQt client) to force a kernel restart to get a clean kernel without
795 losing client-side state like history or inlined figures.
804 losing client-side state like history or inlined figures.
796
805
797 The client sends a shutdown request to the kernel, and once it receives the
806 The client sends a shutdown request to the kernel, and once it receives the
798 reply message (which is otherwise empty), it can assume that the kernel has
807 reply message (which is otherwise empty), it can assume that the kernel has
799 completed shutdown safely.
808 completed shutdown safely.
800
809
801 Upon their own shutdown, client applications will typically execute a last
810 Upon their own shutdown, client applications will typically execute a last
802 minute sanity check and forcefully terminate any kernel that is still alive, to
811 minute sanity check and forcefully terminate any kernel that is still alive, to
803 avoid leaving stray processes in the user's machine.
812 avoid leaving stray processes in the user's machine.
804
813
805 Message type: ``shutdown_request``::
814 Message type: ``shutdown_request``::
806
815
807 content = {
816 content = {
808 'restart' : bool # whether the shutdown is final, or precedes a restart
817 'restart' : bool # whether the shutdown is final, or precedes a restart
809 }
818 }
810
819
811 Message type: ``shutdown_reply``::
820 Message type: ``shutdown_reply``::
812
821
813 content = {
822 content = {
814 'restart' : bool # whether the shutdown is final, or precedes a restart
823 'restart' : bool # whether the shutdown is final, or precedes a restart
815 }
824 }
816
825
817 .. Note::
826 .. Note::
818
827
819 When the clients detect a dead kernel thanks to inactivity on the heartbeat
828 When the clients detect a dead kernel thanks to inactivity on the heartbeat
820 socket, they simply send a forceful process termination signal, since a dead
829 socket, they simply send a forceful process termination signal, since a dead
821 process is unlikely to respond in any useful way to messages.
830 process is unlikely to respond in any useful way to messages.
822
831
823
832
824 Messages on the PUB/SUB socket
833 Messages on the PUB/SUB socket
825 ==============================
834 ==============================
826
835
827 Streams (stdout, stderr, etc)
836 Streams (stdout, stderr, etc)
828 ------------------------------
837 ------------------------------
829
838
830 Message type: ``stream``::
839 Message type: ``stream``::
831
840
832 content = {
841 content = {
833 # The name of the stream is one of 'stdout', 'stderr'
842 # The name of the stream is one of 'stdout', 'stderr'
834 'name' : str,
843 'name' : str,
835
844
836 # The text is an arbitrary string to be written to that stream
845 # The text is an arbitrary string to be written to that stream
837 'text' : str,
846 'text' : str,
838 }
847 }
839
848
840 .. versionchanged:: 5.0
849 .. versionchanged:: 5.0
841
850
842 'data' key renamed to 'text' for conistency with the notebook format.
851 'data' key renamed to 'text' for conistency with the notebook format.
843
852
844 Display Data
853 Display Data
845 ------------
854 ------------
846
855
847 This type of message is used to bring back data that should be displayed (text,
856 This type of message is used to bring back data that should be displayed (text,
848 html, svg, etc.) in the frontends. This data is published to all frontends.
857 html, svg, etc.) in the frontends. This data is published to all frontends.
849 Each message can have multiple representations of the data; it is up to the
858 Each message can have multiple representations of the data; it is up to the
850 frontend to decide which to use and how. A single message should contain all
859 frontend to decide which to use and how. A single message should contain all
851 possible representations of the same information. Each representation should
860 possible representations of the same information. Each representation should
852 be a JSON'able data structure, and should be a valid MIME type.
861 be a JSON'able data structure, and should be a valid MIME type.
853
862
854 Some questions remain about this design:
863 Some questions remain about this design:
855
864
856 * Do we use this message type for execute_result/displayhook? Probably not, because
865 * Do we use this message type for execute_result/displayhook? Probably not, because
857 the displayhook also has to handle the Out prompt display. On the other hand
866 the displayhook also has to handle the Out prompt display. On the other hand
858 we could put that information into the metadata section.
867 we could put that information into the metadata section.
859
868
860 .. _display_data:
869 .. _display_data:
861
870
862 Message type: ``display_data``::
871 Message type: ``display_data``::
863
872
864 content = {
873 content = {
865
874
866 # Who create the data
875 # Who create the data
867 'source' : str,
876 'source' : str,
868
877
869 # The data dict contains key/value pairs, where the keys are MIME
878 # The data dict contains key/value pairs, where the keys are MIME
870 # types and the values are the raw data of the representation in that
879 # types and the values are the raw data of the representation in that
871 # format.
880 # format.
872 'data' : dict,
881 'data' : dict,
873
882
874 # Any metadata that describes the data
883 # Any metadata that describes the data
875 'metadata' : dict
884 'metadata' : dict
876 }
885 }
877
886
878
887
879 The ``metadata`` contains any metadata that describes the output.
888 The ``metadata`` contains any metadata that describes the output.
880 Global keys are assumed to apply to the output as a whole.
889 Global keys are assumed to apply to the output as a whole.
881 The ``metadata`` dict can also contain mime-type keys, which will be sub-dictionaries,
890 The ``metadata`` dict can also contain mime-type keys, which will be sub-dictionaries,
882 which are interpreted as applying only to output of that type.
891 which are interpreted as applying only to output of that type.
883 Third parties should put any data they write into a single dict
892 Third parties should put any data they write into a single dict
884 with a reasonably unique name to avoid conflicts.
893 with a reasonably unique name to avoid conflicts.
885
894
886 The only metadata keys currently defined in IPython are the width and height
895 The only metadata keys currently defined in IPython are the width and height
887 of images::
896 of images::
888
897
889 metadata = {
898 metadata = {
890 'image/png' : {
899 'image/png' : {
891 'width': 640,
900 'width': 640,
892 'height': 480
901 'height': 480
893 }
902 }
894 }
903 }
895
904
896
905
897 .. versionchanged:: 5.0
906 .. versionchanged:: 5.0
898
907
899 `application/json` data should be unpacked JSON data,
908 `application/json` data should be unpacked JSON data,
900 not double-serialized as a JSON string.
909 not double-serialized as a JSON string.
901
910
902
911
903 Raw Data Publication
912 Raw Data Publication
904 --------------------
913 --------------------
905
914
906 ``display_data`` lets you publish *representations* of data, such as images and html.
915 ``display_data`` lets you publish *representations* of data, such as images and html.
907 This ``data_pub`` message lets you publish *actual raw data*, sent via message buffers.
916 This ``data_pub`` message lets you publish *actual raw data*, sent via message buffers.
908
917
909 data_pub messages are constructed via the :func:`IPython.lib.datapub.publish_data` function:
918 data_pub messages are constructed via the :func:`IPython.lib.datapub.publish_data` function:
910
919
911 .. sourcecode:: python
920 .. sourcecode:: python
912
921
913 from IPython.kernel.zmq.datapub import publish_data
922 from IPython.kernel.zmq.datapub import publish_data
914 ns = dict(x=my_array)
923 ns = dict(x=my_array)
915 publish_data(ns)
924 publish_data(ns)
916
925
917
926
918 Message type: ``data_pub``::
927 Message type: ``data_pub``::
919
928
920 content = {
929 content = {
921 # the keys of the data dict, after it has been unserialized
930 # the keys of the data dict, after it has been unserialized
922 'keys' : ['a', 'b']
931 'keys' : ['a', 'b']
923 }
932 }
924 # the namespace dict will be serialized in the message buffers,
933 # the namespace dict will be serialized in the message buffers,
925 # which will have a length of at least one
934 # which will have a length of at least one
926 buffers = [b'pdict', ...]
935 buffers = [b'pdict', ...]
927
936
928
937
929 The interpretation of a sequence of data_pub messages for a given parent request should be
938 The interpretation of a sequence of data_pub messages for a given parent request should be
930 to update a single namespace with subsequent results.
939 to update a single namespace with subsequent results.
931
940
932 .. note::
941 .. note::
933
942
934 No frontends directly handle data_pub messages at this time.
943 No frontends directly handle data_pub messages at this time.
935 It is currently only used by the client/engines in :mod:`IPython.parallel`,
944 It is currently only used by the client/engines in :mod:`IPython.parallel`,
936 where engines may publish *data* to the Client,
945 where engines may publish *data* to the Client,
937 of which the Client can then publish *representations* via ``display_data``
946 of which the Client can then publish *representations* via ``display_data``
938 to various frontends.
947 to various frontends.
939
948
940 Code inputs
949 Code inputs
941 -----------
950 -----------
942
951
943 To let all frontends know what code is being executed at any given time, these
952 To let all frontends know what code is being executed at any given time, these
944 messages contain a re-broadcast of the ``code`` portion of an
953 messages contain a re-broadcast of the ``code`` portion of an
945 :ref:`execute_request <execute>`, along with the :ref:`execution_count
954 :ref:`execute_request <execute>`, along with the :ref:`execution_count
946 <execution_counter>`.
955 <execution_counter>`.
947
956
948 Message type: ``execute_input``::
957 Message type: ``execute_input``::
949
958
950 content = {
959 content = {
951 'code' : str, # Source code to be executed, one or more lines
960 'code' : str, # Source code to be executed, one or more lines
952
961
953 # The counter for this execution is also provided so that clients can
962 # The counter for this execution is also provided so that clients can
954 # display it, since IPython automatically creates variables called _iN
963 # display it, since IPython automatically creates variables called _iN
955 # (for input prompt In[N]).
964 # (for input prompt In[N]).
956 'execution_count' : int
965 'execution_count' : int
957 }
966 }
958
967
959 .. versionchanged:: 5.0
968 .. versionchanged:: 5.0
960
969
961 ``pyin`` is renamed to ``execute_input``.
970 ``pyin`` is renamed to ``execute_input``.
962
971
963
972
964 Execution results
973 Execution results
965 -----------------
974 -----------------
966
975
967 Results of an execution are published as an ``execute_result``.
976 Results of an execution are published as an ``execute_result``.
968 These are identical to `display_data`_ messages, with the addition of an ``execution_count`` key.
977 These are identical to `display_data`_ messages, with the addition of an ``execution_count`` key.
969
978
970 Results can have multiple simultaneous formats depending on its
979 Results can have multiple simultaneous formats depending on its
971 configuration. A plain text representation should always be provided
980 configuration. A plain text representation should always be provided
972 in the ``text/plain`` mime-type. Frontends are free to display any or all of these
981 in the ``text/plain`` mime-type. Frontends are free to display any or all of these
973 according to its capabilities.
982 according to its capabilities.
974 Frontends should ignore mime-types they do not understand. The data itself is
983 Frontends should ignore mime-types they do not understand. The data itself is
975 any JSON object and depends on the format. It is often, but not always a string.
984 any JSON object and depends on the format. It is often, but not always a string.
976
985
977 Message type: ``execute_result``::
986 Message type: ``execute_result``::
978
987
979 content = {
988 content = {
980
989
981 # The counter for this execution is also provided so that clients can
990 # The counter for this execution is also provided so that clients can
982 # display it, since IPython automatically creates variables called _N
991 # display it, since IPython automatically creates variables called _N
983 # (for prompt N).
992 # (for prompt N).
984 'execution_count' : int,
993 'execution_count' : int,
985
994
986 # data and metadata are identical to a display_data message.
995 # data and metadata are identical to a display_data message.
987 # the object being displayed is that passed to the display hook,
996 # the object being displayed is that passed to the display hook,
988 # i.e. the *result* of the execution.
997 # i.e. the *result* of the execution.
989 'data' : dict,
998 'data' : dict,
990 'metadata' : dict,
999 'metadata' : dict,
991 }
1000 }
992
1001
993 Execution errors
1002 Execution errors
994 ----------------
1003 ----------------
995
1004
996 When an error occurs during code execution
1005 When an error occurs during code execution
997
1006
998 Message type: ``error``::
1007 Message type: ``error``::
999
1008
1000 content = {
1009 content = {
1001 # Similar content to the execute_reply messages for the 'error' case,
1010 # Similar content to the execute_reply messages for the 'error' case,
1002 # except the 'status' field is omitted.
1011 # except the 'status' field is omitted.
1003 }
1012 }
1004
1013
1005 .. versionchanged:: 5.0
1014 .. versionchanged:: 5.0
1006
1015
1007 ``pyerr`` renamed to ``error``
1016 ``pyerr`` renamed to ``error``
1008
1017
1009 Kernel status
1018 Kernel status
1010 -------------
1019 -------------
1011
1020
1012 This message type is used by frontends to monitor the status of the kernel.
1021 This message type is used by frontends to monitor the status of the kernel.
1013
1022
1014 Message type: ``status``::
1023 Message type: ``status``::
1015
1024
1016 content = {
1025 content = {
1017 # When the kernel starts to handle a message, it will enter the 'busy'
1026 # When the kernel starts to handle a message, it will enter the 'busy'
1018 # state and when it finishes, it will enter the 'idle' state.
1027 # state and when it finishes, it will enter the 'idle' state.
1019 # The kernel will publish state 'starting' exactly once at process startup.
1028 # The kernel will publish state 'starting' exactly once at process startup.
1020 execution_state : ('busy', 'idle', 'starting')
1029 execution_state : ('busy', 'idle', 'starting')
1021 }
1030 }
1022
1031
1023 .. versionchanged:: 5.0
1032 .. versionchanged:: 5.0
1024
1033
1025 Busy and idle messages should be sent before/after handling every message,
1034 Busy and idle messages should be sent before/after handling every message,
1026 not just execution.
1035 not just execution.
1027
1036
1028 Clear output
1037 Clear output
1029 ------------
1038 ------------
1030
1039
1031 This message type is used to clear the output that is visible on the frontend.
1040 This message type is used to clear the output that is visible on the frontend.
1032
1041
1033 Message type: ``clear_output``::
1042 Message type: ``clear_output``::
1034
1043
1035 content = {
1044 content = {
1036
1045
1037 # Wait to clear the output until new output is available. Clears the
1046 # Wait to clear the output until new output is available. Clears the
1038 # existing output immediately before the new output is displayed.
1047 # existing output immediately before the new output is displayed.
1039 # Useful for creating simple animations with minimal flickering.
1048 # Useful for creating simple animations with minimal flickering.
1040 'wait' : bool,
1049 'wait' : bool,
1041 }
1050 }
1042
1051
1043 .. versionchanged:: 4.1
1052 .. versionchanged:: 4.1
1044
1053
1045 ``stdout``, ``stderr``, and ``display`` boolean keys for selective clearing are removed,
1054 ``stdout``, ``stderr``, and ``display`` boolean keys for selective clearing are removed,
1046 and ``wait`` is added.
1055 and ``wait`` is added.
1047 The selective clearing keys are ignored in v4 and the default behavior remains the same,
1056 The selective clearing keys are ignored in v4 and the default behavior remains the same,
1048 so v4 clear_output messages will be safely handled by a v4.1 frontend.
1057 so v4 clear_output messages will be safely handled by a v4.1 frontend.
1049
1058
1050
1059
1051 Messages on the stdin ROUTER/DEALER sockets
1060 Messages on the stdin ROUTER/DEALER sockets
1052 ===========================================
1061 ===========================================
1053
1062
1054 This is a socket where the request/reply pattern goes in the opposite direction:
1063 This is a socket where the request/reply pattern goes in the opposite direction:
1055 from the kernel to a *single* frontend, and its purpose is to allow
1064 from the kernel to a *single* frontend, and its purpose is to allow
1056 ``raw_input`` and similar operations that read from ``sys.stdin`` on the kernel
1065 ``raw_input`` and similar operations that read from ``sys.stdin`` on the kernel
1057 to be fulfilled by the client. The request should be made to the frontend that
1066 to be fulfilled by the client. The request should be made to the frontend that
1058 made the execution request that prompted ``raw_input`` to be called. For now we
1067 made the execution request that prompted ``raw_input`` to be called. For now we
1059 will keep these messages as simple as possible, since they only mean to convey
1068 will keep these messages as simple as possible, since they only mean to convey
1060 the ``raw_input(prompt)`` call.
1069 the ``raw_input(prompt)`` call.
1061
1070
1062 Message type: ``input_request``::
1071 Message type: ``input_request``::
1063
1072
1064 content = {
1073 content = {
1065 # the text to show at the prompt
1074 # the text to show at the prompt
1066 'prompt' : str,
1075 'prompt' : str,
1067 # Is the request for a password?
1076 # Is the request for a password?
1068 # If so, the frontend shouldn't echo input.
1077 # If so, the frontend shouldn't echo input.
1069 'password' : bool
1078 'password' : bool
1070 }
1079 }
1071
1080
1072 Message type: ``input_reply``::
1081 Message type: ``input_reply``::
1073
1082
1074 content = { 'value' : str }
1083 content = { 'value' : str }
1075
1084
1076
1085
1077 When ``password`` is True, the frontend should not echo the input as it is entered.
1086 When ``password`` is True, the frontend should not echo the input as it is entered.
1078
1087
1079 .. versionchanged:: 5.0
1088 .. versionchanged:: 5.0
1080
1089
1081 ``password`` key added.
1090 ``password`` key added.
1082
1091
1083 .. note::
1092 .. note::
1084
1093
1085 The stdin socket of the client is required to have the same zmq IDENTITY
1094 The stdin socket of the client is required to have the same zmq IDENTITY
1086 as the client's shell socket.
1095 as the client's shell socket.
1087 Because of this, the ``input_request`` must be sent with the same IDENTITY
1096 Because of this, the ``input_request`` must be sent with the same IDENTITY
1088 routing prefix as the ``execute_reply`` in order for the frontend to receive
1097 routing prefix as the ``execute_reply`` in order for the frontend to receive
1089 the message.
1098 the message.
1090
1099
1091 .. note::
1100 .. note::
1092
1101
1093 We do not explicitly try to forward the raw ``sys.stdin`` object, because in
1102 We do not explicitly try to forward the raw ``sys.stdin`` object, because in
1094 practice the kernel should behave like an interactive program. When a
1103 practice the kernel should behave like an interactive program. When a
1095 program is opened on the console, the keyboard effectively takes over the
1104 program is opened on the console, the keyboard effectively takes over the
1096 ``stdin`` file descriptor, and it can't be used for raw reading anymore.
1105 ``stdin`` file descriptor, and it can't be used for raw reading anymore.
1097 Since the IPython kernel effectively behaves like a console program (albeit
1106 Since the IPython kernel effectively behaves like a console program (albeit
1098 one whose "keyboard" is actually living in a separate process and
1107 one whose "keyboard" is actually living in a separate process and
1099 transported over the zmq connection), raw ``stdin`` isn't expected to be
1108 transported over the zmq connection), raw ``stdin`` isn't expected to be
1100 available.
1109 available.
1101
1110
1102 .. _kernel_heartbeat:
1111 .. _kernel_heartbeat:
1103
1112
1104 Heartbeat for kernels
1113 Heartbeat for kernels
1105 =====================
1114 =====================
1106
1115
1107 Clients send ping messages on a REQ socket, which are echoed right back
1116 Clients send ping messages on a REQ socket, which are echoed right back
1108 from the Kernel's REP socket. These are simple bytestrings, not full JSON messages described above.
1117 from the Kernel's REP socket. These are simple bytestrings, not full JSON messages described above.
1109
1118
1110
1119
1111 Custom Messages
1120 Custom Messages
1112 ===============
1121 ===============
1113
1122
1114 .. versionadded:: 4.1
1123 .. versionadded:: 4.1
1115
1124
1116 IPython 2.0 (msgspec v4.1) adds a messaging system for developers to add their own objects with Frontend
1125 IPython 2.0 (msgspec v4.1) adds a messaging system for developers to add their own objects with Frontend
1117 and Kernel-side components, and allow them to communicate with each other.
1126 and Kernel-side components, and allow them to communicate with each other.
1118 To do this, IPython adds a notion of a ``Comm``, which exists on both sides,
1127 To do this, IPython adds a notion of a ``Comm``, which exists on both sides,
1119 and can communicate in either direction.
1128 and can communicate in either direction.
1120
1129
1121 These messages are fully symmetrical - both the Kernel and the Frontend can send each message,
1130 These messages are fully symmetrical - both the Kernel and the Frontend can send each message,
1122 and no messages expect a reply.
1131 and no messages expect a reply.
1123 The Kernel listens for these messages on the Shell channel,
1132 The Kernel listens for these messages on the Shell channel,
1124 and the Frontend listens for them on the IOPub channel.
1133 and the Frontend listens for them on the IOPub channel.
1125
1134
1126 Opening a Comm
1135 Opening a Comm
1127 --------------
1136 --------------
1128
1137
1129 Opening a Comm produces a ``comm_open`` message, to be sent to the other side::
1138 Opening a Comm produces a ``comm_open`` message, to be sent to the other side::
1130
1139
1131 {
1140 {
1132 'comm_id' : 'u-u-i-d',
1141 'comm_id' : 'u-u-i-d',
1133 'target_name' : 'my_comm',
1142 'target_name' : 'my_comm',
1134 'data' : {}
1143 'data' : {}
1135 }
1144 }
1136
1145
1137 Every Comm has an ID and a target name.
1146 Every Comm has an ID and a target name.
1138 The code handling the message on the receiving side is responsible for maintaining a mapping
1147 The code handling the message on the receiving side is responsible for maintaining a mapping
1139 of target_name keys to constructors.
1148 of target_name keys to constructors.
1140 After a ``comm_open`` message has been sent,
1149 After a ``comm_open`` message has been sent,
1141 there should be a corresponding Comm instance on both sides.
1150 there should be a corresponding Comm instance on both sides.
1142 The ``data`` key is always a dict and can be any extra JSON information used in initialization of the comm.
1151 The ``data`` key is always a dict and can be any extra JSON information used in initialization of the comm.
1143
1152
1144 If the ``target_name`` key is not found on the receiving side,
1153 If the ``target_name`` key is not found on the receiving side,
1145 then it should immediately reply with a ``comm_close`` message to avoid an inconsistent state.
1154 then it should immediately reply with a ``comm_close`` message to avoid an inconsistent state.
1146
1155
1147 Comm Messages
1156 Comm Messages
1148 -------------
1157 -------------
1149
1158
1150 Comm messages are one-way communications to update comm state,
1159 Comm messages are one-way communications to update comm state,
1151 used for synchronizing widget state, or simply requesting actions of a comm's counterpart.
1160 used for synchronizing widget state, or simply requesting actions of a comm's counterpart.
1152
1161
1153 Essentially, each comm pair defines their own message specification implemented inside the ``data`` dict.
1162 Essentially, each comm pair defines their own message specification implemented inside the ``data`` dict.
1154
1163
1155 There are no expected replies (of course, one side can send another ``comm_msg`` in reply).
1164 There are no expected replies (of course, one side can send another ``comm_msg`` in reply).
1156
1165
1157 Message type: ``comm_msg``::
1166 Message type: ``comm_msg``::
1158
1167
1159 {
1168 {
1160 'comm_id' : 'u-u-i-d',
1169 'comm_id' : 'u-u-i-d',
1161 'data' : {}
1170 'data' : {}
1162 }
1171 }
1163
1172
1164 Tearing Down Comms
1173 Tearing Down Comms
1165 ------------------
1174 ------------------
1166
1175
1167 Since comms live on both sides, when a comm is destroyed the other side must be notified.
1176 Since comms live on both sides, when a comm is destroyed the other side must be notified.
1168 This is done with a ``comm_close`` message.
1177 This is done with a ``comm_close`` message.
1169
1178
1170 Message type: ``comm_close``::
1179 Message type: ``comm_close``::
1171
1180
1172 {
1181 {
1173 'comm_id' : 'u-u-i-d',
1182 'comm_id' : 'u-u-i-d',
1174 'data' : {}
1183 'data' : {}
1175 }
1184 }
1176
1185
1177 Output Side Effects
1186 Output Side Effects
1178 -------------------
1187 -------------------
1179
1188
1180 Since comm messages can execute arbitrary user code,
1189 Since comm messages can execute arbitrary user code,
1181 handlers should set the parent header and publish status busy / idle,
1190 handlers should set the parent header and publish status busy / idle,
1182 just like an execute request.
1191 just like an execute request.
1183
1192
1184
1193
1185 To Do
1194 To Do
1186 =====
1195 =====
1187
1196
1188 Missing things include:
1197 Missing things include:
1189
1198
1190 * Important: finish thinking through the payload concept and API.
1199 * Important: finish thinking through the payload concept and API.
1191
1200
1192 .. include:: ../links.txt
1201 .. include:: ../links.txt
@@ -1,338 +1,342 b''
1 .. _nbformat:
1 .. _nbformat:
2
2
3 ===========================
3 ===========================
4 The Jupyter Notebook Format
4 The Jupyter Notebook Format
5 ===========================
5 ===========================
6
6
7 Introduction
7 Introduction
8 ============
8 ============
9
9
10 Jupyter (nΓ© IPython) notebook files are simple JSON documents,
10 Jupyter (nΓ© IPython) notebook files are simple JSON documents,
11 containing text, source code, rich media output, and metadata.
11 containing text, source code, rich media output, and metadata.
12 each segment of the document is stored in a cell.
12 each segment of the document is stored in a cell.
13
13
14 Some general points about the notebook format:
14 Some general points about the notebook format:
15
15
16 .. note::
16 .. note::
17
17
18 *All* metadata fields are optional.
18 *All* metadata fields are optional.
19 While the type and values of some metadata are defined,
19 While the type and values of some metadata are defined,
20 no metadata values are required to be defined.
20 no metadata values are required to be defined.
21
21
22
22
23 Top-level structure
23 Top-level structure
24 ===================
24 ===================
25
25
26 At the highest level, a Jupyter notebook is a dictionary with a few keys:
26 At the highest level, a Jupyter notebook is a dictionary with a few keys:
27
27
28 - metadata (dict)
28 - metadata (dict)
29 - nbformat (int)
29 - nbformat (int)
30 - nbformat_minor (int)
30 - nbformat_minor (int)
31 - cells (list)
31 - cells (list)
32
32
33 .. sourcecode:: python
33 .. sourcecode:: python
34
34
35 {
35 {
36 "metadata" : {
36 "metadata" : {
37 "signature": "hex-digest", # used for authenticating unsafe outputs on load
37 "signature": "hex-digest", # used for authenticating unsafe outputs on load
38 "kernel_info": {
38 "kernel_info": {
39 # if kernel_info is defined, its name and language fields are required.
39 # if kernel_info is defined, its name field is required.
40 "name" : "the name of the kernel",
40 "name" : "the name of the kernel"
41 "language" : "the programming language of the kernel",
42 "codemirror_mode": "The name of the codemirror mode to use [optional]"
43 },
41 },
42 "language_info": {
43 # if language_info is defined, its name field is required.
44 "name" : "the programming language of the kernel",
45 "version": "the version of the language",
46 "codemirror_mode": "The name of the codemirror mode to use [optional]"
47 }
44 },
48 },
45 "nbformat": 4,
49 "nbformat": 4,
46 "nbformat_minor": 0,
50 "nbformat_minor": 0,
47 "cells" : [
51 "cells" : [
48 # list of cell dictionaries, see below
52 # list of cell dictionaries, see below
49 ],
53 ],
50 }
54 }
51
55
52 Some fields, such as code input and text output, are characteristically multi-line strings.
56 Some fields, such as code input and text output, are characteristically multi-line strings.
53 When these fields are written to disk, they **may** be written as a list of strings,
57 When these fields are written to disk, they **may** be written as a list of strings,
54 which should be joined with ``''`` when reading back into memory.
58 which should be joined with ``''`` when reading back into memory.
55 In programmatic APIs for working with notebooks (Python, Javascript),
59 In programmatic APIs for working with notebooks (Python, Javascript),
56 these are always re-joined into the original multi-line string.
60 these are always re-joined into the original multi-line string.
57 If you intend to work with notebook files directly,
61 If you intend to work with notebook files directly,
58 you must allow multi-line string fields to be either a string or list of strings.
62 you must allow multi-line string fields to be either a string or list of strings.
59
63
60
64
61 Cell Types
65 Cell Types
62 ==========
66 ==========
63
67
64 There are a few basic cell types for encapsulating code and text.
68 There are a few basic cell types for encapsulating code and text.
65 All cells have the following basic structure:
69 All cells have the following basic structure:
66
70
67 .. sourcecode:: python
71 .. sourcecode:: python
68
72
69 {
73 {
70 "cell_type" : "name",
74 "cell_type" : "name",
71 "metadata" : {},
75 "metadata" : {},
72 "source" : "single string or [list, of, strings]",
76 "source" : "single string or [list, of, strings]",
73 }
77 }
74
78
75
79
76 Markdown cells
80 Markdown cells
77 --------------
81 --------------
78
82
79 Markdown cells are used for body-text, and contain markdown,
83 Markdown cells are used for body-text, and contain markdown,
80 as defined in `GitHub-flavored markdown`_, and implemented in marked_.
84 as defined in `GitHub-flavored markdown`_, and implemented in marked_.
81
85
82 .. _GitHub-flavored markdown: https://help.github.com/articles/github-flavored-markdown
86 .. _GitHub-flavored markdown: https://help.github.com/articles/github-flavored-markdown
83 .. _marked: https://github.com/chjj/marked
87 .. _marked: https://github.com/chjj/marked
84
88
85 .. sourcecode:: python
89 .. sourcecode:: python
86
90
87 {
91 {
88 "cell_type" : "markdown",
92 "cell_type" : "markdown",
89 "metadata" : {},
93 "metadata" : {},
90 "source" : ["some *markdown*"],
94 "source" : ["some *markdown*"],
91 }
95 }
92
96
93 .. versionchanged:: nbformat 4.0
97 .. versionchanged:: nbformat 4.0
94
98
95 Heading cells have been removed, in favor of simple headings in markdown.
99 Heading cells have been removed, in favor of simple headings in markdown.
96
100
97
101
98 Code cells
102 Code cells
99 ----------
103 ----------
100
104
101 Code cells are the primary content of Jupyter notebooks.
105 Code cells are the primary content of Jupyter notebooks.
102 They contain source code in the language of the document's associated kernel,
106 They contain source code in the language of the document's associated kernel,
103 and a list of outputs associated with executing that code.
107 and a list of outputs associated with executing that code.
104 They also have an execution_count, which must be an integer or ``null``.
108 They also have an execution_count, which must be an integer or ``null``.
105
109
106 .. sourcecode:: python
110 .. sourcecode:: python
107
111
108 {
112 {
109 "cell_type" : "code",
113 "cell_type" : "code",
110 "execution_count": 1, # integer or null
114 "execution_count": 1, # integer or null
111 "metadata" : {
115 "metadata" : {
112 "collapsed" : True, # whether the output of the cell is collapsed
116 "collapsed" : True, # whether the output of the cell is collapsed
113 "autoscroll": False, # any of true, false or "auto"
117 "autoscroll": False, # any of true, false or "auto"
114 },
118 },
115 "source" : ["some code"],
119 "source" : ["some code"],
116 "outputs": [{
120 "outputs": [{
117 # list of output dicts (described below)
121 # list of output dicts (described below)
118 "output_type": "stream",
122 "output_type": "stream",
119 ...
123 ...
120 }],
124 }],
121 }
125 }
122
126
123 .. versionchanged:: nbformat 4.0
127 .. versionchanged:: nbformat 4.0
124
128
125 ``input`` was renamed to ``source``, for consistency among cell types.
129 ``input`` was renamed to ``source``, for consistency among cell types.
126
130
127 .. versionchanged:: nbformat 4.0
131 .. versionchanged:: nbformat 4.0
128
132
129 ``prompt_number`` renamed to ``execution_count``
133 ``prompt_number`` renamed to ``execution_count``
130
134
131 Code cell outputs
135 Code cell outputs
132 -----------------
136 -----------------
133
137
134 A code cell can have a variety of outputs (stream data or rich mime-type output).
138 A code cell can have a variety of outputs (stream data or rich mime-type output).
135 These correspond to :ref:`messages <messaging>` produced as a result of executing the cell.
139 These correspond to :ref:`messages <messaging>` produced as a result of executing the cell.
136
140
137 All outputs have an ``output_type`` field,
141 All outputs have an ``output_type`` field,
138 which is a string defining what type of output it is.
142 which is a string defining what type of output it is.
139
143
140
144
141 stream output
145 stream output
142 *************
146 *************
143
147
144 .. sourcecode:: python
148 .. sourcecode:: python
145
149
146 {
150 {
147 "output_type" : "stream",
151 "output_type" : "stream",
148 "name" : "stdout", # or stderr
152 "name" : "stdout", # or stderr
149 "data" : ["multiline stream text"],
153 "data" : ["multiline stream text"],
150 }
154 }
151
155
152 .. versionchanged:: nbformat 4.0
156 .. versionchanged:: nbformat 4.0
153
157
154 The keys ``stream`` and ``text`` were changed to ``name`` and ``data`` to match
158 The keys ``stream`` and ``text`` were changed to ``name`` and ``data`` to match
155 the stream message specification.
159 the stream message specification.
156
160
157
161
158 display_data
162 display_data
159 ************
163 ************
160
164
161 Rich display outputs, as created by ``display_data`` messages,
165 Rich display outputs, as created by ``display_data`` messages,
162 contain data keyed by mime-type. This is often called a mime-bundle,
166 contain data keyed by mime-type. This is often called a mime-bundle,
163 and shows up in various locations in the notebook format and message spec.
167 and shows up in various locations in the notebook format and message spec.
164 The metadata of these messages may be keyed by mime-type as well.
168 The metadata of these messages may be keyed by mime-type as well.
165
169
166
170
167
171
168 .. sourcecode:: python
172 .. sourcecode:: python
169
173
170 {
174 {
171 "output_type" : "display_data",
175 "output_type" : "display_data",
172 "data" : {
176 "data" : {
173 "text/plain" : ["multiline text data"],
177 "text/plain" : ["multiline text data"],
174 "image/png": ["base64-encoded-png-data"],
178 "image/png": ["base64-encoded-png-data"],
175 "application/json": {
179 "application/json": {
176 # JSON data is included as-is
180 # JSON data is included as-is
177 "json": "data",
181 "json": "data",
178 },
182 },
179 },
183 },
180 "metadata" : {
184 "metadata" : {
181 "image/png": {
185 "image/png": {
182 "width": 640,
186 "width": 640,
183 "height": 480,
187 "height": 480,
184 },
188 },
185 },
189 },
186 }
190 }
187
191
188
192
189 .. versionchanged:: nbformat 4.0
193 .. versionchanged:: nbformat 4.0
190
194
191 ``application/json`` output is no longer double-serialized into a string.
195 ``application/json`` output is no longer double-serialized into a string.
192
196
193 .. versionchanged:: nbformat 4.0
197 .. versionchanged:: nbformat 4.0
194
198
195 mime-types are used for keys, instead of a combination of short names (``text``)
199 mime-types are used for keys, instead of a combination of short names (``text``)
196 and mime-types, and are stored in a ``data`` key, rather than the top-level.
200 and mime-types, and are stored in a ``data`` key, rather than the top-level.
197 i.e. ``output.data['image/png']`` instead of ``output.png``.
201 i.e. ``output.data['image/png']`` instead of ``output.png``.
198
202
199
203
200 execute_result
204 execute_result
201 **************
205 **************
202
206
203 Results of executing a cell (as created by ``displayhook`` in Python)
207 Results of executing a cell (as created by ``displayhook`` in Python)
204 are stored in ``execute_result`` outputs.
208 are stored in ``execute_result`` outputs.
205 `execute_result` outputs are identical to ``display_data``,
209 `execute_result` outputs are identical to ``display_data``,
206 adding only a ``execution_count`` field, which must be an integer.
210 adding only a ``execution_count`` field, which must be an integer.
207
211
208 .. sourcecode:: python
212 .. sourcecode:: python
209
213
210 {
214 {
211 "output_type" : "execute_result",
215 "output_type" : "execute_result",
212 "execution_count": 42,
216 "execution_count": 42,
213 "data" : {
217 "data" : {
214 "text/plain" : ["multiline text data"],
218 "text/plain" : ["multiline text data"],
215 "image/png": ["base64-encoded-png-data"],
219 "image/png": ["base64-encoded-png-data"],
216 "application/json": {
220 "application/json": {
217 # JSON data is included as-is
221 # JSON data is included as-is
218 "json": "data",
222 "json": "data",
219 },
223 },
220 },
224 },
221 "metadata" : {
225 "metadata" : {
222 "image/png": {
226 "image/png": {
223 "width": 640,
227 "width": 640,
224 "height": 480,
228 "height": 480,
225 },
229 },
226 },
230 },
227 }
231 }
228
232
229 .. versionchanged:: nbformat 4.0
233 .. versionchanged:: nbformat 4.0
230
234
231 ``pyout`` renamed to ``execute_result``
235 ``pyout`` renamed to ``execute_result``
232
236
233 .. versionchanged:: nbformat 4.0
237 .. versionchanged:: nbformat 4.0
234
238
235 ``prompt_number`` renamed to ``execution_count``
239 ``prompt_number`` renamed to ``execution_count``
236
240
237
241
238 error
242 error
239 *****
243 *****
240
244
241 Failed execution may show a traceback
245 Failed execution may show a traceback
242
246
243 .. sourcecode:: python
247 .. sourcecode:: python
244
248
245 {
249 {
246 'ename' : str, # Exception name, as a string
250 'ename' : str, # Exception name, as a string
247 'evalue' : str, # Exception value, as a string
251 'evalue' : str, # Exception value, as a string
248
252
249 # The traceback will contain a list of frames,
253 # The traceback will contain a list of frames,
250 # represented each as a string.
254 # represented each as a string.
251 'traceback' : list,
255 'traceback' : list,
252 }
256 }
253
257
254 .. versionchanged:: nbformat 4.0
258 .. versionchanged:: nbformat 4.0
255
259
256 ``pyerr`` renamed to ``error``
260 ``pyerr`` renamed to ``error``
257
261
258
262
259 .. _raw nbconvert cells:
263 .. _raw nbconvert cells:
260
264
261 Raw NBConvert cells
265 Raw NBConvert cells
262 -------------------
266 -------------------
263
267
264 A raw cell is defined as content that should be included *unmodified* in :ref:`nbconvert <nbconvert>` output.
268 A raw cell is defined as content that should be included *unmodified* in :ref:`nbconvert <nbconvert>` output.
265 For example, this cell could include raw LaTeX for nbconvert to pdf via latex,
269 For example, this cell could include raw LaTeX for nbconvert to pdf via latex,
266 or restructured text for use in Sphinx documentation.
270 or restructured text for use in Sphinx documentation.
267
271
268 The notebook authoring environment does not render raw cells.
272 The notebook authoring environment does not render raw cells.
269
273
270 The only logic in a raw cell is the `format` metadata field.
274 The only logic in a raw cell is the `format` metadata field.
271 If defined, it specifies which nbconvert output format is the intended target
275 If defined, it specifies which nbconvert output format is the intended target
272 for the raw cell. When outputting to any other format,
276 for the raw cell. When outputting to any other format,
273 the raw cell's contents will be excluded.
277 the raw cell's contents will be excluded.
274 In the default case when this value is undefined,
278 In the default case when this value is undefined,
275 a raw cell's contents will be included in any nbconvert output,
279 a raw cell's contents will be included in any nbconvert output,
276 regardless of format.
280 regardless of format.
277
281
278 .. sourcecode:: python
282 .. sourcecode:: python
279
283
280 {
284 {
281 "cell_type" : "raw",
285 "cell_type" : "raw",
282 "metadata" : {
286 "metadata" : {
283 # the mime-type of the target nbconvert format.
287 # the mime-type of the target nbconvert format.
284 # nbconvert to formats other than this will exclude this cell.
288 # nbconvert to formats other than this will exclude this cell.
285 "format" : "mime/type"
289 "format" : "mime/type"
286 },
290 },
287 "source" : ["some nbformat mime-type data"]
291 "source" : ["some nbformat mime-type data"]
288 }
292 }
289
293
290 Metadata
294 Metadata
291 ========
295 ========
292
296
293 Metadata is a place that you can put arbitrary JSONable information about
297 Metadata is a place that you can put arbitrary JSONable information about
294 your notebook, cell, or output. Because it is a shared namespace,
298 your notebook, cell, or output. Because it is a shared namespace,
295 any custom metadata should use a sufficiently unique namespace,
299 any custom metadata should use a sufficiently unique namespace,
296 such as `metadata.kaylees_md.foo = "bar"`.
300 such as `metadata.kaylees_md.foo = "bar"`.
297
301
298 Metadata fields officially defined for Jupyter notebooks are listed here:
302 Metadata fields officially defined for Jupyter notebooks are listed here:
299
303
300 Notebook metadata
304 Notebook metadata
301 -----------------
305 -----------------
302
306
303 The following metadata keys are defined at the notebook level:
307 The following metadata keys are defined at the notebook level:
304
308
305 =========== =============== ==============
309 =========== =============== ==============
306 Key Value Interpretation
310 Key Value Interpretation
307 =========== =============== ==============
311 =========== =============== ==============
308 kernelspec dict A :ref:`kernel specification <kernelspecs>`
312 kernelspec dict A :ref:`kernel specification <kernelspecs>`
309 signature str A hashed :ref:`signature <notebook_security>` of the notebook
313 signature str A hashed :ref:`signature <notebook_security>` of the notebook
310 =========== =============== ==============
314 =========== =============== ==============
311
315
312
316
313 Cell metadata
317 Cell metadata
314 -------------
318 -------------
315
319
316 The following metadata keys are defined at the cell level:
320 The following metadata keys are defined at the cell level:
317
321
318 =========== =============== ==============
322 =========== =============== ==============
319 Key Value Interpretation
323 Key Value Interpretation
320 =========== =============== ==============
324 =========== =============== ==============
321 collapsed bool Whether the cell's output container should be collapsed
325 collapsed bool Whether the cell's output container should be collapsed
322 autoscroll bool or 'auto' Whether the cell's output is scrolled, unscrolled, or autoscrolled
326 autoscroll bool or 'auto' Whether the cell's output is scrolled, unscrolled, or autoscrolled
323 deletable bool If False, prevent deletion of the cell
327 deletable bool If False, prevent deletion of the cell
324 format 'mime/type' The mime-type of a :ref:`Raw NBConvert Cell <raw nbconvert cells>`
328 format 'mime/type' The mime-type of a :ref:`Raw NBConvert Cell <raw nbconvert cells>`
325 name str A name for the cell. Should be unique
329 name str A name for the cell. Should be unique
326 tags list of str A list of string tags on the cell. Commas are not allowed in a tag
330 tags list of str A list of string tags on the cell. Commas are not allowed in a tag
327 =========== =============== ==============
331 =========== =============== ==============
328
332
329 Output metadata
333 Output metadata
330 ---------------
334 ---------------
331
335
332 The following metadata keys are defined for code cell outputs:
336 The following metadata keys are defined for code cell outputs:
333
337
334 =========== =============== ==============
338 =========== =============== ==============
335 Key Value Interpretation
339 Key Value Interpretation
336 =========== =============== ==============
340 =========== =============== ==============
337 isolated bool Whether the output should be isolated into an IFrame
341 isolated bool Whether the output should be isolated into an IFrame
338 =========== =============== ==============
342 =========== =============== ==============
General Comments 0
You need to be logged in to leave comments. Login now