##// END OF EJS Templates
Merge pull request #6793 from takluyver/langinfo...
Thomas Kluyver -
r18545:dbb3d415 merge
parent child Browse files
Show More
@@ -1,2700 +1,2707 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/textcell',
9 'notebook/js/textcell',
10 'notebook/js/codecell',
10 'notebook/js/codecell',
11 'services/sessions/session',
11 'services/sessions/session',
12 'notebook/js/celltoolbar',
12 'notebook/js/celltoolbar',
13 'components/marked/lib/marked',
13 'components/marked/lib/marked',
14 'highlight',
14 'highlight',
15 'notebook/js/mathjaxutils',
15 'notebook/js/mathjaxutils',
16 'base/js/keyboard',
16 'base/js/keyboard',
17 'notebook/js/tooltip',
17 'notebook/js/tooltip',
18 'notebook/js/celltoolbarpresets/default',
18 'notebook/js/celltoolbarpresets/default',
19 'notebook/js/celltoolbarpresets/rawcell',
19 'notebook/js/celltoolbarpresets/rawcell',
20 'notebook/js/celltoolbarpresets/slideshow',
20 'notebook/js/celltoolbarpresets/slideshow',
21 'notebook/js/scrollmanager'
21 'notebook/js/scrollmanager'
22 ], function (
22 ], function (
23 IPython,
23 IPython,
24 $,
24 $,
25 utils,
25 utils,
26 dialog,
26 dialog,
27 textcell,
27 textcell,
28 codecell,
28 codecell,
29 session,
29 session,
30 celltoolbar,
30 celltoolbar,
31 marked,
31 marked,
32 hljs,
32 hljs,
33 mathjaxutils,
33 mathjaxutils,
34 keyboard,
34 keyboard,
35 tooltip,
35 tooltip,
36 default_celltoolbar,
36 default_celltoolbar,
37 rawcell_celltoolbar,
37 rawcell_celltoolbar,
38 slideshow_celltoolbar,
38 slideshow_celltoolbar,
39 scrollmanager
39 scrollmanager
40 ) {
40 ) {
41
41
42 var Notebook = function (selector, options) {
42 var Notebook = function (selector, options) {
43 // Constructor
43 // Constructor
44 //
44 //
45 // A notebook contains and manages cells.
45 // A notebook contains and manages cells.
46 //
46 //
47 // Parameters:
47 // Parameters:
48 // selector: string
48 // selector: string
49 // options: dictionary
49 // options: dictionary
50 // Dictionary of keyword arguments.
50 // Dictionary of keyword arguments.
51 // events: $(Events) instance
51 // events: $(Events) instance
52 // keyboard_manager: KeyboardManager instance
52 // keyboard_manager: KeyboardManager instance
53 // save_widget: SaveWidget instance
53 // save_widget: SaveWidget instance
54 // config: dictionary
54 // config: dictionary
55 // base_url : string
55 // base_url : string
56 // notebook_path : string
56 // notebook_path : string
57 // notebook_name : string
57 // notebook_name : string
58 this.config = utils.mergeopt(Notebook, options.config);
58 this.config = utils.mergeopt(Notebook, options.config);
59 this.base_url = options.base_url;
59 this.base_url = options.base_url;
60 this.notebook_path = options.notebook_path;
60 this.notebook_path = options.notebook_path;
61 this.notebook_name = options.notebook_name;
61 this.notebook_name = options.notebook_name;
62 this.events = options.events;
62 this.events = options.events;
63 this.keyboard_manager = options.keyboard_manager;
63 this.keyboard_manager = options.keyboard_manager;
64 this.save_widget = options.save_widget;
64 this.save_widget = options.save_widget;
65 this.tooltip = new tooltip.Tooltip(this.events);
65 this.tooltip = new tooltip.Tooltip(this.events);
66 this.ws_url = options.ws_url;
66 this.ws_url = options.ws_url;
67 this._session_starting = false;
67 this._session_starting = false;
68 this.default_cell_type = this.config.default_cell_type || 'code';
68 this.default_cell_type = this.config.default_cell_type || 'code';
69
69
70 // Create default scroll manager.
70 // Create default scroll manager.
71 this.scroll_manager = new scrollmanager.ScrollManager(this);
71 this.scroll_manager = new scrollmanager.ScrollManager(this);
72
72
73 // TODO: This code smells (and the other `= this` line a couple lines down)
73 // TODO: This code smells (and the other `= this` line a couple lines down)
74 // We need a better way to deal with circular instance references.
74 // We need a better way to deal with circular instance references.
75 this.keyboard_manager.notebook = this;
75 this.keyboard_manager.notebook = this;
76 this.save_widget.notebook = this;
76 this.save_widget.notebook = this;
77
77
78 mathjaxutils.init();
78 mathjaxutils.init();
79
79
80 if (marked) {
80 if (marked) {
81 marked.setOptions({
81 marked.setOptions({
82 gfm : true,
82 gfm : true,
83 tables: true,
83 tables: true,
84 langPrefix: "language-",
84 langPrefix: "language-",
85 highlight: function(code, lang) {
85 highlight: function(code, lang) {
86 if (!lang) {
86 if (!lang) {
87 // no language, no highlight
87 // no language, no highlight
88 return code;
88 return code;
89 }
89 }
90 var highlighted;
90 var highlighted;
91 try {
91 try {
92 highlighted = hljs.highlight(lang, code, false);
92 highlighted = hljs.highlight(lang, code, false);
93 } catch(err) {
93 } catch(err) {
94 highlighted = hljs.highlightAuto(code);
94 highlighted = hljs.highlightAuto(code);
95 }
95 }
96 return highlighted.value;
96 return highlighted.value;
97 }
97 }
98 });
98 });
99 }
99 }
100
100
101 this.element = $(selector);
101 this.element = $(selector);
102 this.element.scroll();
102 this.element.scroll();
103 this.element.data("notebook", this);
103 this.element.data("notebook", this);
104 this.next_prompt_number = 1;
104 this.next_prompt_number = 1;
105 this.session = null;
105 this.session = null;
106 this.kernel = null;
106 this.kernel = null;
107 this.clipboard = null;
107 this.clipboard = null;
108 this.undelete_backup = null;
108 this.undelete_backup = null;
109 this.undelete_index = null;
109 this.undelete_index = null;
110 this.undelete_below = false;
110 this.undelete_below = false;
111 this.paste_enabled = false;
111 this.paste_enabled = false;
112 // It is important to start out in command mode to match the intial mode
112 // It is important to start out in command mode to match the intial mode
113 // of the KeyboardManager.
113 // of the KeyboardManager.
114 this.mode = 'command';
114 this.mode = 'command';
115 this.set_dirty(false);
115 this.set_dirty(false);
116 this.metadata = {};
116 this.metadata = {};
117 this._checkpoint_after_save = false;
117 this._checkpoint_after_save = false;
118 this.last_checkpoint = null;
118 this.last_checkpoint = null;
119 this.checkpoints = [];
119 this.checkpoints = [];
120 this.autosave_interval = 0;
120 this.autosave_interval = 0;
121 this.autosave_timer = null;
121 this.autosave_timer = null;
122 // autosave *at most* every two minutes
122 // autosave *at most* every two minutes
123 this.minimum_autosave_interval = 120000;
123 this.minimum_autosave_interval = 120000;
124 // single worksheet for now
124 // single worksheet for now
125 this.worksheet_metadata = {};
125 this.worksheet_metadata = {};
126 this.notebook_name_blacklist_re = /[\/\\:]/;
126 this.notebook_name_blacklist_re = /[\/\\:]/;
127 this.nbformat = 3; // Increment this when changing the nbformat
127 this.nbformat = 3; // Increment this when changing the nbformat
128 this.nbformat_minor = 0; // Increment this when changing the nbformat
128 this.nbformat_minor = 0; // Increment this when changing the nbformat
129 this.codemirror_mode = 'ipython';
129 this.codemirror_mode = 'ipython';
130 this.create_elements();
130 this.create_elements();
131 this.bind_events();
131 this.bind_events();
132 this.save_notebook = function() { // don't allow save until notebook_loaded
132 this.save_notebook = function() { // don't allow save until notebook_loaded
133 this.save_notebook_error(null, null, "Load failed, save is disabled");
133 this.save_notebook_error(null, null, "Load failed, save is disabled");
134 };
134 };
135
135
136 // Trigger cell toolbar registration.
136 // Trigger cell toolbar registration.
137 default_celltoolbar.register(this);
137 default_celltoolbar.register(this);
138 rawcell_celltoolbar.register(this);
138 rawcell_celltoolbar.register(this);
139 slideshow_celltoolbar.register(this);
139 slideshow_celltoolbar.register(this);
140 };
140 };
141
141
142 Notebook.options_default = {
142 Notebook.options_default = {
143 // can be any cell type, or the special values of
143 // can be any cell type, or the special values of
144 // 'above', 'below', or 'selected' to get the value from another cell.
144 // 'above', 'below', or 'selected' to get the value from another cell.
145 Notebook: {
145 Notebook: {
146 default_cell_type: 'code',
146 default_cell_type: 'code',
147 }
147 }
148 };
148 };
149
149
150
150
151 /**
151 /**
152 * Create an HTML and CSS representation of the notebook.
152 * Create an HTML and CSS representation of the notebook.
153 *
153 *
154 * @method create_elements
154 * @method create_elements
155 */
155 */
156 Notebook.prototype.create_elements = function () {
156 Notebook.prototype.create_elements = function () {
157 var that = this;
157 var that = this;
158 this.element.attr('tabindex','-1');
158 this.element.attr('tabindex','-1');
159 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
159 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
160 // We add this end_space div to the end of the notebook div to:
160 // We add this end_space div to the end of the notebook div to:
161 // i) provide a margin between the last cell and the end of the notebook
161 // i) provide a margin between the last cell and the end of the notebook
162 // ii) to prevent the div from scrolling up when the last cell is being
162 // ii) to prevent the div from scrolling up when the last cell is being
163 // edited, but is too low on the page, which browsers will do automatically.
163 // edited, but is too low on the page, which browsers will do automatically.
164 var end_space = $('<div/>').addClass('end_space');
164 var end_space = $('<div/>').addClass('end_space');
165 end_space.dblclick(function (e) {
165 end_space.dblclick(function (e) {
166 var ncells = that.ncells();
166 var ncells = that.ncells();
167 that.insert_cell_below('code',ncells-1);
167 that.insert_cell_below('code',ncells-1);
168 });
168 });
169 this.element.append(this.container);
169 this.element.append(this.container);
170 this.container.append(end_space);
170 this.container.append(end_space);
171 };
171 };
172
172
173 /**
173 /**
174 * Bind JavaScript events: key presses and custom IPython events.
174 * Bind JavaScript events: key presses and custom IPython events.
175 *
175 *
176 * @method bind_events
176 * @method bind_events
177 */
177 */
178 Notebook.prototype.bind_events = function () {
178 Notebook.prototype.bind_events = function () {
179 var that = this;
179 var that = this;
180
180
181 this.events.on('set_next_input.Notebook', function (event, data) {
181 this.events.on('set_next_input.Notebook', function (event, data) {
182 var index = that.find_cell_index(data.cell);
182 var index = that.find_cell_index(data.cell);
183 var new_cell = that.insert_cell_below('code',index);
183 var new_cell = that.insert_cell_below('code',index);
184 new_cell.set_text(data.text);
184 new_cell.set_text(data.text);
185 that.dirty = true;
185 that.dirty = true;
186 });
186 });
187
187
188 this.events.on('set_dirty.Notebook', function (event, data) {
188 this.events.on('set_dirty.Notebook', function (event, data) {
189 that.dirty = data.value;
189 that.dirty = data.value;
190 });
190 });
191
191
192 this.events.on('trust_changed.Notebook', function (event, trusted) {
192 this.events.on('trust_changed.Notebook', function (event, trusted) {
193 that.trusted = trusted;
193 that.trusted = trusted;
194 });
194 });
195
195
196 this.events.on('select.Cell', function (event, data) {
196 this.events.on('select.Cell', function (event, data) {
197 var index = that.find_cell_index(data.cell);
197 var index = that.find_cell_index(data.cell);
198 that.select(index);
198 that.select(index);
199 });
199 });
200
200
201 this.events.on('edit_mode.Cell', function (event, data) {
201 this.events.on('edit_mode.Cell', function (event, data) {
202 that.handle_edit_mode(data.cell);
202 that.handle_edit_mode(data.cell);
203 });
203 });
204
204
205 this.events.on('command_mode.Cell', function (event, data) {
205 this.events.on('command_mode.Cell', function (event, data) {
206 that.handle_command_mode(data.cell);
206 that.handle_command_mode(data.cell);
207 });
207 });
208
208
209 this.events.on('spec_changed.Kernel', function(event, data) {
209 this.events.on('spec_changed.Kernel', function(event, data) {
210 that.set_kernelspec_metadata(data);
210 that.metadata.kernelspec =
211 {name: data.name, display_name: data.display_name};
212 });
213
214 this.events.on('kernel_ready.Kernel', function(event, data) {
215 var kinfo = data.kernel.info_reply
216 var langinfo = kinfo.language_info || {};
217 if (!langinfo.name) langinfo.name = kinfo.language;
218
219 that.metadata.language_info = langinfo;
211 // Mode 'null' should be plain, unhighlighted text.
220 // Mode 'null' should be plain, unhighlighted text.
212 cm_mode = data.codemirror_mode || data.language || 'null'
221 var cm_mode = langinfo.codemirror_mode || langinfo.language || 'null'
213 that.set_codemirror_mode(cm_mode);
222 that.set_codemirror_mode(cm_mode);
214 });
223 });
215
224
216 var collapse_time = function (time) {
225 var collapse_time = function (time) {
217 var app_height = $('#ipython-main-app').height(); // content height
226 var app_height = $('#ipython-main-app').height(); // content height
218 var splitter_height = $('div#pager_splitter').outerHeight(true);
227 var splitter_height = $('div#pager_splitter').outerHeight(true);
219 var new_height = app_height - splitter_height;
228 var new_height = app_height - splitter_height;
220 that.element.animate({height : new_height + 'px'}, time);
229 that.element.animate({height : new_height + 'px'}, time);
221 };
230 };
222
231
223 this.element.bind('collapse_pager', function (event, extrap) {
232 this.element.bind('collapse_pager', function (event, extrap) {
224 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
233 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
225 collapse_time(time);
234 collapse_time(time);
226 });
235 });
227
236
228 var expand_time = function (time) {
237 var expand_time = function (time) {
229 var app_height = $('#ipython-main-app').height(); // content height
238 var app_height = $('#ipython-main-app').height(); // content height
230 var splitter_height = $('div#pager_splitter').outerHeight(true);
239 var splitter_height = $('div#pager_splitter').outerHeight(true);
231 var pager_height = $('div#pager').outerHeight(true);
240 var pager_height = $('div#pager').outerHeight(true);
232 var new_height = app_height - pager_height - splitter_height;
241 var new_height = app_height - pager_height - splitter_height;
233 that.element.animate({height : new_height + 'px'}, time);
242 that.element.animate({height : new_height + 'px'}, time);
234 };
243 };
235
244
236 this.element.bind('expand_pager', function (event, extrap) {
245 this.element.bind('expand_pager', function (event, extrap) {
237 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
246 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
238 expand_time(time);
247 expand_time(time);
239 });
248 });
240
249
241 // Firefox 22 broke $(window).on("beforeunload")
250 // Firefox 22 broke $(window).on("beforeunload")
242 // I'm not sure why or how.
251 // I'm not sure why or how.
243 window.onbeforeunload = function (e) {
252 window.onbeforeunload = function (e) {
244 // TODO: Make killing the kernel configurable.
253 // TODO: Make killing the kernel configurable.
245 var kill_kernel = false;
254 var kill_kernel = false;
246 if (kill_kernel) {
255 if (kill_kernel) {
247 that.session.delete();
256 that.session.delete();
248 }
257 }
249 // if we are autosaving, trigger an autosave on nav-away.
258 // if we are autosaving, trigger an autosave on nav-away.
250 // still warn, because if we don't the autosave may fail.
259 // still warn, because if we don't the autosave may fail.
251 if (that.dirty) {
260 if (that.dirty) {
252 if ( that.autosave_interval ) {
261 if ( that.autosave_interval ) {
253 // schedule autosave in a timeout
262 // schedule autosave in a timeout
254 // this gives you a chance to forcefully discard changes
263 // this gives you a chance to forcefully discard changes
255 // by reloading the page if you *really* want to.
264 // by reloading the page if you *really* want to.
256 // the timer doesn't start until you *dismiss* the dialog.
265 // the timer doesn't start until you *dismiss* the dialog.
257 setTimeout(function () {
266 setTimeout(function () {
258 if (that.dirty) {
267 if (that.dirty) {
259 that.save_notebook();
268 that.save_notebook();
260 }
269 }
261 }, 1000);
270 }, 1000);
262 return "Autosave in progress, latest changes may be lost.";
271 return "Autosave in progress, latest changes may be lost.";
263 } else {
272 } else {
264 return "Unsaved changes will be lost.";
273 return "Unsaved changes will be lost.";
265 }
274 }
266 }
275 }
267 // Null is the *only* return value that will make the browser not
276 // Null is the *only* return value that will make the browser not
268 // pop up the "don't leave" dialog.
277 // pop up the "don't leave" dialog.
269 return null;
278 return null;
270 };
279 };
271 };
280 };
272
281
273 /**
282 /**
274 * Set the dirty flag, and trigger the set_dirty.Notebook event
283 * Set the dirty flag, and trigger the set_dirty.Notebook event
275 *
284 *
276 * @method set_dirty
285 * @method set_dirty
277 */
286 */
278 Notebook.prototype.set_dirty = function (value) {
287 Notebook.prototype.set_dirty = function (value) {
279 if (value === undefined) {
288 if (value === undefined) {
280 value = true;
289 value = true;
281 }
290 }
282 if (this.dirty == value) {
291 if (this.dirty == value) {
283 return;
292 return;
284 }
293 }
285 this.events.trigger('set_dirty.Notebook', {value: value});
294 this.events.trigger('set_dirty.Notebook', {value: value});
286 };
295 };
287
296
288 /**
297 /**
289 * Scroll the top of the page to a given cell.
298 * Scroll the top of the page to a given cell.
290 *
299 *
291 * @method scroll_to_cell
300 * @method scroll_to_cell
292 * @param {Number} cell_number An index of the cell to view
301 * @param {Number} cell_number An index of the cell to view
293 * @param {Number} time Animation time in milliseconds
302 * @param {Number} time Animation time in milliseconds
294 * @return {Number} Pixel offset from the top of the container
303 * @return {Number} Pixel offset from the top of the container
295 */
304 */
296 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
305 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
297 var cells = this.get_cells();
306 var cells = this.get_cells();
298 time = time || 0;
307 time = time || 0;
299 cell_number = Math.min(cells.length-1,cell_number);
308 cell_number = Math.min(cells.length-1,cell_number);
300 cell_number = Math.max(0 ,cell_number);
309 cell_number = Math.max(0 ,cell_number);
301 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
310 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
302 this.element.animate({scrollTop:scroll_value}, time);
311 this.element.animate({scrollTop:scroll_value}, time);
303 return scroll_value;
312 return scroll_value;
304 };
313 };
305
314
306 /**
315 /**
307 * Scroll to the bottom of the page.
316 * Scroll to the bottom of the page.
308 *
317 *
309 * @method scroll_to_bottom
318 * @method scroll_to_bottom
310 */
319 */
311 Notebook.prototype.scroll_to_bottom = function () {
320 Notebook.prototype.scroll_to_bottom = function () {
312 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
321 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
313 };
322 };
314
323
315 /**
324 /**
316 * Scroll to the top of the page.
325 * Scroll to the top of the page.
317 *
326 *
318 * @method scroll_to_top
327 * @method scroll_to_top
319 */
328 */
320 Notebook.prototype.scroll_to_top = function () {
329 Notebook.prototype.scroll_to_top = function () {
321 this.element.animate({scrollTop:0}, 0);
330 this.element.animate({scrollTop:0}, 0);
322 };
331 };
323
332
324 // Edit Notebook metadata
333 // Edit Notebook metadata
325
334
326 Notebook.prototype.edit_metadata = function () {
335 Notebook.prototype.edit_metadata = function () {
327 var that = this;
336 var that = this;
328 dialog.edit_metadata({
337 dialog.edit_metadata({
329 md: this.metadata,
338 md: this.metadata,
330 callback: function (md) {
339 callback: function (md) {
331 that.metadata = md;
340 that.metadata = md;
332 },
341 },
333 name: 'Notebook',
342 name: 'Notebook',
334 notebook: this,
343 notebook: this,
335 keyboard_manager: this.keyboard_manager});
344 keyboard_manager: this.keyboard_manager});
336 };
345 };
337
346
338 Notebook.prototype.set_kernelspec_metadata = function(ks) {
339 var tostore = {};
340 $.map(ks, function(value, field) {
341 if (field !== 'argv' && field !== 'env') {
342 tostore[field] = value;
343 }
344 });
345 this.metadata.kernelspec = tostore;
346 }
347
348 // Cell indexing, retrieval, etc.
347 // Cell indexing, retrieval, etc.
349
348
350 /**
349 /**
351 * Get all cell elements in the notebook.
350 * Get all cell elements in the notebook.
352 *
351 *
353 * @method get_cell_elements
352 * @method get_cell_elements
354 * @return {jQuery} A selector of all cell elements
353 * @return {jQuery} A selector of all cell elements
355 */
354 */
356 Notebook.prototype.get_cell_elements = function () {
355 Notebook.prototype.get_cell_elements = function () {
357 return this.container.children("div.cell");
356 return this.container.children("div.cell");
358 };
357 };
359
358
360 /**
359 /**
361 * Get a particular cell element.
360 * Get a particular cell element.
362 *
361 *
363 * @method get_cell_element
362 * @method get_cell_element
364 * @param {Number} index An index of a cell to select
363 * @param {Number} index An index of a cell to select
365 * @return {jQuery} A selector of the given cell.
364 * @return {jQuery} A selector of the given cell.
366 */
365 */
367 Notebook.prototype.get_cell_element = function (index) {
366 Notebook.prototype.get_cell_element = function (index) {
368 var result = null;
367 var result = null;
369 var e = this.get_cell_elements().eq(index);
368 var e = this.get_cell_elements().eq(index);
370 if (e.length !== 0) {
369 if (e.length !== 0) {
371 result = e;
370 result = e;
372 }
371 }
373 return result;
372 return result;
374 };
373 };
375
374
376 /**
375 /**
377 * Try to get a particular cell by msg_id.
376 * Try to get a particular cell by msg_id.
378 *
377 *
379 * @method get_msg_cell
378 * @method get_msg_cell
380 * @param {String} msg_id A message UUID
379 * @param {String} msg_id A message UUID
381 * @return {Cell} Cell or null if no cell was found.
380 * @return {Cell} Cell or null if no cell was found.
382 */
381 */
383 Notebook.prototype.get_msg_cell = function (msg_id) {
382 Notebook.prototype.get_msg_cell = function (msg_id) {
384 return codecell.CodeCell.msg_cells[msg_id] || null;
383 return codecell.CodeCell.msg_cells[msg_id] || null;
385 };
384 };
386
385
387 /**
386 /**
388 * Count the cells in this notebook.
387 * Count the cells in this notebook.
389 *
388 *
390 * @method ncells
389 * @method ncells
391 * @return {Number} The number of cells in this notebook
390 * @return {Number} The number of cells in this notebook
392 */
391 */
393 Notebook.prototype.ncells = function () {
392 Notebook.prototype.ncells = function () {
394 return this.get_cell_elements().length;
393 return this.get_cell_elements().length;
395 };
394 };
396
395
397 /**
396 /**
398 * Get all Cell objects in this notebook.
397 * Get all Cell objects in this notebook.
399 *
398 *
400 * @method get_cells
399 * @method get_cells
401 * @return {Array} This notebook's Cell objects
400 * @return {Array} This notebook's Cell objects
402 */
401 */
403 // TODO: we are often calling cells as cells()[i], which we should optimize
402 // TODO: we are often calling cells as cells()[i], which we should optimize
404 // to cells(i) or a new method.
403 // to cells(i) or a new method.
405 Notebook.prototype.get_cells = function () {
404 Notebook.prototype.get_cells = function () {
406 return this.get_cell_elements().toArray().map(function (e) {
405 return this.get_cell_elements().toArray().map(function (e) {
407 return $(e).data("cell");
406 return $(e).data("cell");
408 });
407 });
409 };
408 };
410
409
411 /**
410 /**
412 * Get a Cell object from this notebook.
411 * Get a Cell object from this notebook.
413 *
412 *
414 * @method get_cell
413 * @method get_cell
415 * @param {Number} index An index of a cell to retrieve
414 * @param {Number} index An index of a cell to retrieve
416 * @return {Cell} Cell or null if no cell was found.
415 * @return {Cell} Cell or null if no cell was found.
417 */
416 */
418 Notebook.prototype.get_cell = function (index) {
417 Notebook.prototype.get_cell = function (index) {
419 var result = null;
418 var result = null;
420 var ce = this.get_cell_element(index);
419 var ce = this.get_cell_element(index);
421 if (ce !== null) {
420 if (ce !== null) {
422 result = ce.data('cell');
421 result = ce.data('cell');
423 }
422 }
424 return result;
423 return result;
425 };
424 };
426
425
427 /**
426 /**
428 * Get the cell below a given cell.
427 * Get the cell below a given cell.
429 *
428 *
430 * @method get_next_cell
429 * @method get_next_cell
431 * @param {Cell} cell The provided cell
430 * @param {Cell} cell The provided cell
432 * @return {Cell} the next cell or null if no cell was found.
431 * @return {Cell} the next cell or null if no cell was found.
433 */
432 */
434 Notebook.prototype.get_next_cell = function (cell) {
433 Notebook.prototype.get_next_cell = function (cell) {
435 var result = null;
434 var result = null;
436 var index = this.find_cell_index(cell);
435 var index = this.find_cell_index(cell);
437 if (this.is_valid_cell_index(index+1)) {
436 if (this.is_valid_cell_index(index+1)) {
438 result = this.get_cell(index+1);
437 result = this.get_cell(index+1);
439 }
438 }
440 return result;
439 return result;
441 };
440 };
442
441
443 /**
442 /**
444 * Get the cell above a given cell.
443 * Get the cell above a given cell.
445 *
444 *
446 * @method get_prev_cell
445 * @method get_prev_cell
447 * @param {Cell} cell The provided cell
446 * @param {Cell} cell The provided cell
448 * @return {Cell} The previous cell or null if no cell was found.
447 * @return {Cell} The previous cell or null if no cell was found.
449 */
448 */
450 Notebook.prototype.get_prev_cell = function (cell) {
449 Notebook.prototype.get_prev_cell = function (cell) {
451 var result = null;
450 var result = null;
452 var index = this.find_cell_index(cell);
451 var index = this.find_cell_index(cell);
453 if (index !== null && index > 0) {
452 if (index !== null && index > 0) {
454 result = this.get_cell(index-1);
453 result = this.get_cell(index-1);
455 }
454 }
456 return result;
455 return result;
457 };
456 };
458
457
459 /**
458 /**
460 * Get the numeric index of a given cell.
459 * Get the numeric index of a given cell.
461 *
460 *
462 * @method find_cell_index
461 * @method find_cell_index
463 * @param {Cell} cell The provided cell
462 * @param {Cell} cell The provided cell
464 * @return {Number} The cell's numeric index or null if no cell was found.
463 * @return {Number} The cell's numeric index or null if no cell was found.
465 */
464 */
466 Notebook.prototype.find_cell_index = function (cell) {
465 Notebook.prototype.find_cell_index = function (cell) {
467 var result = null;
466 var result = null;
468 this.get_cell_elements().filter(function (index) {
467 this.get_cell_elements().filter(function (index) {
469 if ($(this).data("cell") === cell) {
468 if ($(this).data("cell") === cell) {
470 result = index;
469 result = index;
471 }
470 }
472 });
471 });
473 return result;
472 return result;
474 };
473 };
475
474
476 /**
475 /**
477 * Get a given index , or the selected index if none is provided.
476 * Get a given index , or the selected index if none is provided.
478 *
477 *
479 * @method index_or_selected
478 * @method index_or_selected
480 * @param {Number} index A cell's index
479 * @param {Number} index A cell's index
481 * @return {Number} The given index, or selected index if none is provided.
480 * @return {Number} The given index, or selected index if none is provided.
482 */
481 */
483 Notebook.prototype.index_or_selected = function (index) {
482 Notebook.prototype.index_or_selected = function (index) {
484 var i;
483 var i;
485 if (index === undefined || index === null) {
484 if (index === undefined || index === null) {
486 i = this.get_selected_index();
485 i = this.get_selected_index();
487 if (i === null) {
486 if (i === null) {
488 i = 0;
487 i = 0;
489 }
488 }
490 } else {
489 } else {
491 i = index;
490 i = index;
492 }
491 }
493 return i;
492 return i;
494 };
493 };
495
494
496 /**
495 /**
497 * Get the currently selected cell.
496 * Get the currently selected cell.
498 * @method get_selected_cell
497 * @method get_selected_cell
499 * @return {Cell} The selected cell
498 * @return {Cell} The selected cell
500 */
499 */
501 Notebook.prototype.get_selected_cell = function () {
500 Notebook.prototype.get_selected_cell = function () {
502 var index = this.get_selected_index();
501 var index = this.get_selected_index();
503 return this.get_cell(index);
502 return this.get_cell(index);
504 };
503 };
505
504
506 /**
505 /**
507 * Check whether a cell index is valid.
506 * Check whether a cell index is valid.
508 *
507 *
509 * @method is_valid_cell_index
508 * @method is_valid_cell_index
510 * @param {Number} index A cell index
509 * @param {Number} index A cell index
511 * @return True if the index is valid, false otherwise
510 * @return True if the index is valid, false otherwise
512 */
511 */
513 Notebook.prototype.is_valid_cell_index = function (index) {
512 Notebook.prototype.is_valid_cell_index = function (index) {
514 if (index !== null && index >= 0 && index < this.ncells()) {
513 if (index !== null && index >= 0 && index < this.ncells()) {
515 return true;
514 return true;
516 } else {
515 } else {
517 return false;
516 return false;
518 }
517 }
519 };
518 };
520
519
521 /**
520 /**
522 * Get the index of the currently selected cell.
521 * Get the index of the currently selected cell.
523
522
524 * @method get_selected_index
523 * @method get_selected_index
525 * @return {Number} The selected cell's numeric index
524 * @return {Number} The selected cell's numeric index
526 */
525 */
527 Notebook.prototype.get_selected_index = function () {
526 Notebook.prototype.get_selected_index = function () {
528 var result = null;
527 var result = null;
529 this.get_cell_elements().filter(function (index) {
528 this.get_cell_elements().filter(function (index) {
530 if ($(this).data("cell").selected === true) {
529 if ($(this).data("cell").selected === true) {
531 result = index;
530 result = index;
532 }
531 }
533 });
532 });
534 return result;
533 return result;
535 };
534 };
536
535
537
536
538 // Cell selection.
537 // Cell selection.
539
538
540 /**
539 /**
541 * Programmatically select a cell.
540 * Programmatically select a cell.
542 *
541 *
543 * @method select
542 * @method select
544 * @param {Number} index A cell's index
543 * @param {Number} index A cell's index
545 * @return {Notebook} This notebook
544 * @return {Notebook} This notebook
546 */
545 */
547 Notebook.prototype.select = function (index) {
546 Notebook.prototype.select = function (index) {
548 if (this.is_valid_cell_index(index)) {
547 if (this.is_valid_cell_index(index)) {
549 var sindex = this.get_selected_index();
548 var sindex = this.get_selected_index();
550 if (sindex !== null && index !== sindex) {
549 if (sindex !== null && index !== sindex) {
551 // If we are about to select a different cell, make sure we are
550 // If we are about to select a different cell, make sure we are
552 // first in command mode.
551 // first in command mode.
553 if (this.mode !== 'command') {
552 if (this.mode !== 'command') {
554 this.command_mode();
553 this.command_mode();
555 }
554 }
556 this.get_cell(sindex).unselect();
555 this.get_cell(sindex).unselect();
557 }
556 }
558 var cell = this.get_cell(index);
557 var cell = this.get_cell(index);
559 cell.select();
558 cell.select();
560 if (cell.cell_type === 'heading') {
559 if (cell.cell_type === 'heading') {
561 this.events.trigger('selected_cell_type_changed.Notebook',
560 this.events.trigger('selected_cell_type_changed.Notebook',
562 {'cell_type':cell.cell_type,level:cell.level}
561 {'cell_type':cell.cell_type,level:cell.level}
563 );
562 );
564 } else {
563 } else {
565 this.events.trigger('selected_cell_type_changed.Notebook',
564 this.events.trigger('selected_cell_type_changed.Notebook',
566 {'cell_type':cell.cell_type}
565 {'cell_type':cell.cell_type}
567 );
566 );
568 }
567 }
569 }
568 }
570 return this;
569 return this;
571 };
570 };
572
571
573 /**
572 /**
574 * Programmatically select the next cell.
573 * Programmatically select the next cell.
575 *
574 *
576 * @method select_next
575 * @method select_next
577 * @return {Notebook} This notebook
576 * @return {Notebook} This notebook
578 */
577 */
579 Notebook.prototype.select_next = function () {
578 Notebook.prototype.select_next = function () {
580 var index = this.get_selected_index();
579 var index = this.get_selected_index();
581 this.select(index+1);
580 this.select(index+1);
582 return this;
581 return this;
583 };
582 };
584
583
585 /**
584 /**
586 * Programmatically select the previous cell.
585 * Programmatically select the previous cell.
587 *
586 *
588 * @method select_prev
587 * @method select_prev
589 * @return {Notebook} This notebook
588 * @return {Notebook} This notebook
590 */
589 */
591 Notebook.prototype.select_prev = function () {
590 Notebook.prototype.select_prev = function () {
592 var index = this.get_selected_index();
591 var index = this.get_selected_index();
593 this.select(index-1);
592 this.select(index-1);
594 return this;
593 return this;
595 };
594 };
596
595
597
596
598 // Edit/Command mode
597 // Edit/Command mode
599
598
600 /**
599 /**
601 * Gets the index of the cell that is in edit mode.
600 * Gets the index of the cell that is in edit mode.
602 *
601 *
603 * @method get_edit_index
602 * @method get_edit_index
604 *
603 *
605 * @return index {int}
604 * @return index {int}
606 **/
605 **/
607 Notebook.prototype.get_edit_index = function () {
606 Notebook.prototype.get_edit_index = function () {
608 var result = null;
607 var result = null;
609 this.get_cell_elements().filter(function (index) {
608 this.get_cell_elements().filter(function (index) {
610 if ($(this).data("cell").mode === 'edit') {
609 if ($(this).data("cell").mode === 'edit') {
611 result = index;
610 result = index;
612 }
611 }
613 });
612 });
614 return result;
613 return result;
615 };
614 };
616
615
617 /**
616 /**
618 * Handle when a a cell blurs and the notebook should enter command mode.
617 * Handle when a a cell blurs and the notebook should enter command mode.
619 *
618 *
620 * @method handle_command_mode
619 * @method handle_command_mode
621 * @param [cell] {Cell} Cell to enter command mode on.
620 * @param [cell] {Cell} Cell to enter command mode on.
622 **/
621 **/
623 Notebook.prototype.handle_command_mode = function (cell) {
622 Notebook.prototype.handle_command_mode = function (cell) {
624 if (this.mode !== 'command') {
623 if (this.mode !== 'command') {
625 cell.command_mode();
624 cell.command_mode();
626 this.mode = 'command';
625 this.mode = 'command';
627 this.events.trigger('command_mode.Notebook');
626 this.events.trigger('command_mode.Notebook');
628 this.keyboard_manager.command_mode();
627 this.keyboard_manager.command_mode();
629 }
628 }
630 };
629 };
631
630
632 /**
631 /**
633 * Make the notebook enter command mode.
632 * Make the notebook enter command mode.
634 *
633 *
635 * @method command_mode
634 * @method command_mode
636 **/
635 **/
637 Notebook.prototype.command_mode = function () {
636 Notebook.prototype.command_mode = function () {
638 var cell = this.get_cell(this.get_edit_index());
637 var cell = this.get_cell(this.get_edit_index());
639 if (cell && this.mode !== 'command') {
638 if (cell && this.mode !== 'command') {
640 // We don't call cell.command_mode, but rather call cell.focus_cell()
639 // We don't call cell.command_mode, but rather call cell.focus_cell()
641 // which will blur and CM editor and trigger the call to
640 // which will blur and CM editor and trigger the call to
642 // handle_command_mode.
641 // handle_command_mode.
643 cell.focus_cell();
642 cell.focus_cell();
644 }
643 }
645 };
644 };
646
645
647 /**
646 /**
648 * Handle when a cell fires it's edit_mode event.
647 * Handle when a cell fires it's edit_mode event.
649 *
648 *
650 * @method handle_edit_mode
649 * @method handle_edit_mode
651 * @param [cell] {Cell} Cell to enter edit mode on.
650 * @param [cell] {Cell} Cell to enter edit mode on.
652 **/
651 **/
653 Notebook.prototype.handle_edit_mode = function (cell) {
652 Notebook.prototype.handle_edit_mode = function (cell) {
654 if (cell && this.mode !== 'edit') {
653 if (cell && this.mode !== 'edit') {
655 cell.edit_mode();
654 cell.edit_mode();
656 this.mode = 'edit';
655 this.mode = 'edit';
657 this.events.trigger('edit_mode.Notebook');
656 this.events.trigger('edit_mode.Notebook');
658 this.keyboard_manager.edit_mode();
657 this.keyboard_manager.edit_mode();
659 }
658 }
660 };
659 };
661
660
662 /**
661 /**
663 * Make a cell enter edit mode.
662 * Make a cell enter edit mode.
664 *
663 *
665 * @method edit_mode
664 * @method edit_mode
666 **/
665 **/
667 Notebook.prototype.edit_mode = function () {
666 Notebook.prototype.edit_mode = function () {
668 var cell = this.get_selected_cell();
667 var cell = this.get_selected_cell();
669 if (cell && this.mode !== 'edit') {
668 if (cell && this.mode !== 'edit') {
670 cell.unrender();
669 cell.unrender();
671 cell.focus_editor();
670 cell.focus_editor();
672 }
671 }
673 };
672 };
674
673
675 /**
674 /**
676 * Focus the currently selected cell.
675 * Focus the currently selected cell.
677 *
676 *
678 * @method focus_cell
677 * @method focus_cell
679 **/
678 **/
680 Notebook.prototype.focus_cell = function () {
679 Notebook.prototype.focus_cell = function () {
681 var cell = this.get_selected_cell();
680 var cell = this.get_selected_cell();
682 if (cell === null) {return;} // No cell is selected
681 if (cell === null) {return;} // No cell is selected
683 cell.focus_cell();
682 cell.focus_cell();
684 };
683 };
685
684
686 // Cell movement
685 // Cell movement
687
686
688 /**
687 /**
689 * Move given (or selected) cell up and select it.
688 * Move given (or selected) cell up and select it.
690 *
689 *
691 * @method move_cell_up
690 * @method move_cell_up
692 * @param [index] {integer} cell index
691 * @param [index] {integer} cell index
693 * @return {Notebook} This notebook
692 * @return {Notebook} This notebook
694 **/
693 **/
695 Notebook.prototype.move_cell_up = function (index) {
694 Notebook.prototype.move_cell_up = function (index) {
696 var i = this.index_or_selected(index);
695 var i = this.index_or_selected(index);
697 if (this.is_valid_cell_index(i) && i > 0) {
696 if (this.is_valid_cell_index(i) && i > 0) {
698 var pivot = this.get_cell_element(i-1);
697 var pivot = this.get_cell_element(i-1);
699 var tomove = this.get_cell_element(i);
698 var tomove = this.get_cell_element(i);
700 if (pivot !== null && tomove !== null) {
699 if (pivot !== null && tomove !== null) {
701 tomove.detach();
700 tomove.detach();
702 pivot.before(tomove);
701 pivot.before(tomove);
703 this.select(i-1);
702 this.select(i-1);
704 var cell = this.get_selected_cell();
703 var cell = this.get_selected_cell();
705 cell.focus_cell();
704 cell.focus_cell();
706 }
705 }
707 this.set_dirty(true);
706 this.set_dirty(true);
708 }
707 }
709 return this;
708 return this;
710 };
709 };
711
710
712
711
713 /**
712 /**
714 * Move given (or selected) cell down and select it
713 * Move given (or selected) cell down and select it
715 *
714 *
716 * @method move_cell_down
715 * @method move_cell_down
717 * @param [index] {integer} cell index
716 * @param [index] {integer} cell index
718 * @return {Notebook} This notebook
717 * @return {Notebook} This notebook
719 **/
718 **/
720 Notebook.prototype.move_cell_down = function (index) {
719 Notebook.prototype.move_cell_down = function (index) {
721 var i = this.index_or_selected(index);
720 var i = this.index_or_selected(index);
722 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
721 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
723 var pivot = this.get_cell_element(i+1);
722 var pivot = this.get_cell_element(i+1);
724 var tomove = this.get_cell_element(i);
723 var tomove = this.get_cell_element(i);
725 if (pivot !== null && tomove !== null) {
724 if (pivot !== null && tomove !== null) {
726 tomove.detach();
725 tomove.detach();
727 pivot.after(tomove);
726 pivot.after(tomove);
728 this.select(i+1);
727 this.select(i+1);
729 var cell = this.get_selected_cell();
728 var cell = this.get_selected_cell();
730 cell.focus_cell();
729 cell.focus_cell();
731 }
730 }
732 }
731 }
733 this.set_dirty();
732 this.set_dirty();
734 return this;
733 return this;
735 };
734 };
736
735
737
736
738 // Insertion, deletion.
737 // Insertion, deletion.
739
738
740 /**
739 /**
741 * Delete a cell from the notebook.
740 * Delete a cell from the notebook.
742 *
741 *
743 * @method delete_cell
742 * @method delete_cell
744 * @param [index] A cell's numeric index
743 * @param [index] A cell's numeric index
745 * @return {Notebook} This notebook
744 * @return {Notebook} This notebook
746 */
745 */
747 Notebook.prototype.delete_cell = function (index) {
746 Notebook.prototype.delete_cell = function (index) {
748 var i = this.index_or_selected(index);
747 var i = this.index_or_selected(index);
749 var cell = this.get_cell(i);
748 var cell = this.get_cell(i);
750 if (!cell.is_deletable()) {
749 if (!cell.is_deletable()) {
751 return this;
750 return this;
752 }
751 }
753
752
754 this.undelete_backup = cell.toJSON();
753 this.undelete_backup = cell.toJSON();
755 $('#undelete_cell').removeClass('disabled');
754 $('#undelete_cell').removeClass('disabled');
756 if (this.is_valid_cell_index(i)) {
755 if (this.is_valid_cell_index(i)) {
757 var old_ncells = this.ncells();
756 var old_ncells = this.ncells();
758 var ce = this.get_cell_element(i);
757 var ce = this.get_cell_element(i);
759 ce.remove();
758 ce.remove();
760 if (i === 0) {
759 if (i === 0) {
761 // Always make sure we have at least one cell.
760 // Always make sure we have at least one cell.
762 if (old_ncells === 1) {
761 if (old_ncells === 1) {
763 this.insert_cell_below('code');
762 this.insert_cell_below('code');
764 }
763 }
765 this.select(0);
764 this.select(0);
766 this.undelete_index = 0;
765 this.undelete_index = 0;
767 this.undelete_below = false;
766 this.undelete_below = false;
768 } else if (i === old_ncells-1 && i !== 0) {
767 } else if (i === old_ncells-1 && i !== 0) {
769 this.select(i-1);
768 this.select(i-1);
770 this.undelete_index = i - 1;
769 this.undelete_index = i - 1;
771 this.undelete_below = true;
770 this.undelete_below = true;
772 } else {
771 } else {
773 this.select(i);
772 this.select(i);
774 this.undelete_index = i;
773 this.undelete_index = i;
775 this.undelete_below = false;
774 this.undelete_below = false;
776 }
775 }
777 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
776 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
778 this.set_dirty(true);
777 this.set_dirty(true);
779 }
778 }
780 return this;
779 return this;
781 };
780 };
782
781
783 /**
782 /**
784 * Restore the most recently deleted cell.
783 * Restore the most recently deleted cell.
785 *
784 *
786 * @method undelete
785 * @method undelete
787 */
786 */
788 Notebook.prototype.undelete_cell = function() {
787 Notebook.prototype.undelete_cell = function() {
789 if (this.undelete_backup !== null && this.undelete_index !== null) {
788 if (this.undelete_backup !== null && this.undelete_index !== null) {
790 var current_index = this.get_selected_index();
789 var current_index = this.get_selected_index();
791 if (this.undelete_index < current_index) {
790 if (this.undelete_index < current_index) {
792 current_index = current_index + 1;
791 current_index = current_index + 1;
793 }
792 }
794 if (this.undelete_index >= this.ncells()) {
793 if (this.undelete_index >= this.ncells()) {
795 this.select(this.ncells() - 1);
794 this.select(this.ncells() - 1);
796 }
795 }
797 else {
796 else {
798 this.select(this.undelete_index);
797 this.select(this.undelete_index);
799 }
798 }
800 var cell_data = this.undelete_backup;
799 var cell_data = this.undelete_backup;
801 var new_cell = null;
800 var new_cell = null;
802 if (this.undelete_below) {
801 if (this.undelete_below) {
803 new_cell = this.insert_cell_below(cell_data.cell_type);
802 new_cell = this.insert_cell_below(cell_data.cell_type);
804 } else {
803 } else {
805 new_cell = this.insert_cell_above(cell_data.cell_type);
804 new_cell = this.insert_cell_above(cell_data.cell_type);
806 }
805 }
807 new_cell.fromJSON(cell_data);
806 new_cell.fromJSON(cell_data);
808 if (this.undelete_below) {
807 if (this.undelete_below) {
809 this.select(current_index+1);
808 this.select(current_index+1);
810 } else {
809 } else {
811 this.select(current_index);
810 this.select(current_index);
812 }
811 }
813 this.undelete_backup = null;
812 this.undelete_backup = null;
814 this.undelete_index = null;
813 this.undelete_index = null;
815 }
814 }
816 $('#undelete_cell').addClass('disabled');
815 $('#undelete_cell').addClass('disabled');
817 };
816 };
818
817
819 /**
818 /**
820 * Insert a cell so that after insertion the cell is at given index.
819 * Insert a cell so that after insertion the cell is at given index.
821 *
820 *
822 * If cell type is not provided, it will default to the type of the
821 * If cell type is not provided, it will default to the type of the
823 * currently active cell.
822 * currently active cell.
824 *
823 *
825 * Similar to insert_above, but index parameter is mandatory
824 * Similar to insert_above, but index parameter is mandatory
826 *
825 *
827 * Index will be brought back into the accessible range [0,n]
826 * Index will be brought back into the accessible range [0,n]
828 *
827 *
829 * @method insert_cell_at_index
828 * @method insert_cell_at_index
830 * @param [type] {string} in ['code','markdown','heading'], defaults to 'code'
829 * @param [type] {string} in ['code','markdown','heading'], defaults to 'code'
831 * @param [index] {int} a valid index where to insert cell
830 * @param [index] {int} a valid index where to insert cell
832 *
831 *
833 * @return cell {cell|null} created cell or null
832 * @return cell {cell|null} created cell or null
834 **/
833 **/
835 Notebook.prototype.insert_cell_at_index = function(type, index){
834 Notebook.prototype.insert_cell_at_index = function(type, index){
836
835
837 var ncells = this.ncells();
836 var ncells = this.ncells();
838 index = Math.min(index, ncells);
837 index = Math.min(index, ncells);
839 index = Math.max(index, 0);
838 index = Math.max(index, 0);
840 var cell = null;
839 var cell = null;
841 type = type || this.default_cell_type;
840 type = type || this.default_cell_type;
842 if (type === 'above') {
841 if (type === 'above') {
843 if (index > 0) {
842 if (index > 0) {
844 type = this.get_cell(index-1).cell_type;
843 type = this.get_cell(index-1).cell_type;
845 } else {
844 } else {
846 type = 'code';
845 type = 'code';
847 }
846 }
848 } else if (type === 'below') {
847 } else if (type === 'below') {
849 if (index < ncells) {
848 if (index < ncells) {
850 type = this.get_cell(index).cell_type;
849 type = this.get_cell(index).cell_type;
851 } else {
850 } else {
852 type = 'code';
851 type = 'code';
853 }
852 }
854 } else if (type === 'selected') {
853 } else if (type === 'selected') {
855 type = this.get_selected_cell().cell_type;
854 type = this.get_selected_cell().cell_type;
856 }
855 }
857
856
858 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
857 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
859 var cell_options = {
858 var cell_options = {
860 events: this.events,
859 events: this.events,
861 config: this.config,
860 config: this.config,
862 keyboard_manager: this.keyboard_manager,
861 keyboard_manager: this.keyboard_manager,
863 notebook: this,
862 notebook: this,
864 tooltip: this.tooltip,
863 tooltip: this.tooltip,
865 };
864 };
866 if (type === 'code') {
865 if (type === 'code') {
867 cell = new codecell.CodeCell(this.kernel, cell_options);
866 cell = new codecell.CodeCell(this.kernel, cell_options);
868 cell.set_input_prompt();
867 cell.set_input_prompt();
869 } else if (type === 'markdown') {
868 } else if (type === 'markdown') {
870 cell = new textcell.MarkdownCell(cell_options);
869 cell = new textcell.MarkdownCell(cell_options);
871 } else if (type === 'raw') {
870 } else if (type === 'raw') {
872 cell = new textcell.RawCell(cell_options);
871 cell = new textcell.RawCell(cell_options);
873 } else if (type === 'heading') {
872 } else if (type === 'heading') {
874 cell = new textcell.HeadingCell(cell_options);
873 cell = new textcell.HeadingCell(cell_options);
875 }
874 }
876
875
877 if(this._insert_element_at_index(cell.element,index)) {
876 if(this._insert_element_at_index(cell.element,index)) {
878 cell.render();
877 cell.render();
879 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
878 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
880 cell.refresh();
879 cell.refresh();
881 // We used to select the cell after we refresh it, but there
880 // We used to select the cell after we refresh it, but there
882 // are now cases were this method is called where select is
881 // are now cases were this method is called where select is
883 // not appropriate. The selection logic should be handled by the
882 // not appropriate. The selection logic should be handled by the
884 // caller of the the top level insert_cell methods.
883 // caller of the the top level insert_cell methods.
885 this.set_dirty(true);
884 this.set_dirty(true);
886 }
885 }
887 }
886 }
888 return cell;
887 return cell;
889
888
890 };
889 };
891
890
892 /**
891 /**
893 * Insert an element at given cell index.
892 * Insert an element at given cell index.
894 *
893 *
895 * @method _insert_element_at_index
894 * @method _insert_element_at_index
896 * @param element {dom_element} a cell element
895 * @param element {dom_element} a cell element
897 * @param [index] {int} a valid index where to inser cell
896 * @param [index] {int} a valid index where to inser cell
898 * @private
897 * @private
899 *
898 *
900 * return true if everything whent fine.
899 * return true if everything whent fine.
901 **/
900 **/
902 Notebook.prototype._insert_element_at_index = function(element, index){
901 Notebook.prototype._insert_element_at_index = function(element, index){
903 if (element === undefined){
902 if (element === undefined){
904 return false;
903 return false;
905 }
904 }
906
905
907 var ncells = this.ncells();
906 var ncells = this.ncells();
908
907
909 if (ncells === 0) {
908 if (ncells === 0) {
910 // special case append if empty
909 // special case append if empty
911 this.element.find('div.end_space').before(element);
910 this.element.find('div.end_space').before(element);
912 } else if ( ncells === index ) {
911 } else if ( ncells === index ) {
913 // special case append it the end, but not empty
912 // special case append it the end, but not empty
914 this.get_cell_element(index-1).after(element);
913 this.get_cell_element(index-1).after(element);
915 } else if (this.is_valid_cell_index(index)) {
914 } else if (this.is_valid_cell_index(index)) {
916 // otherwise always somewhere to append to
915 // otherwise always somewhere to append to
917 this.get_cell_element(index).before(element);
916 this.get_cell_element(index).before(element);
918 } else {
917 } else {
919 return false;
918 return false;
920 }
919 }
921
920
922 if (this.undelete_index !== null && index <= this.undelete_index) {
921 if (this.undelete_index !== null && index <= this.undelete_index) {
923 this.undelete_index = this.undelete_index + 1;
922 this.undelete_index = this.undelete_index + 1;
924 this.set_dirty(true);
923 this.set_dirty(true);
925 }
924 }
926 return true;
925 return true;
927 };
926 };
928
927
929 /**
928 /**
930 * Insert a cell of given type above given index, or at top
929 * Insert a cell of given type above given index, or at top
931 * of notebook if index smaller than 0.
930 * of notebook if index smaller than 0.
932 *
931 *
933 * default index value is the one of currently selected cell
932 * default index value is the one of currently selected cell
934 *
933 *
935 * @method insert_cell_above
934 * @method insert_cell_above
936 * @param [type] {string} cell type
935 * @param [type] {string} cell type
937 * @param [index] {integer}
936 * @param [index] {integer}
938 *
937 *
939 * @return handle to created cell or null
938 * @return handle to created cell or null
940 **/
939 **/
941 Notebook.prototype.insert_cell_above = function (type, index) {
940 Notebook.prototype.insert_cell_above = function (type, index) {
942 index = this.index_or_selected(index);
941 index = this.index_or_selected(index);
943 return this.insert_cell_at_index(type, index);
942 return this.insert_cell_at_index(type, index);
944 };
943 };
945
944
946 /**
945 /**
947 * Insert a cell of given type below given index, or at bottom
946 * Insert a cell of given type below given index, or at bottom
948 * of notebook if index greater than number of cells
947 * of notebook if index greater than number of cells
949 *
948 *
950 * default index value is the one of currently selected cell
949 * default index value is the one of currently selected cell
951 *
950 *
952 * @method insert_cell_below
951 * @method insert_cell_below
953 * @param [type] {string} cell type
952 * @param [type] {string} cell type
954 * @param [index] {integer}
953 * @param [index] {integer}
955 *
954 *
956 * @return handle to created cell or null
955 * @return handle to created cell or null
957 *
956 *
958 **/
957 **/
959 Notebook.prototype.insert_cell_below = function (type, index) {
958 Notebook.prototype.insert_cell_below = function (type, index) {
960 index = this.index_or_selected(index);
959 index = this.index_or_selected(index);
961 return this.insert_cell_at_index(type, index+1);
960 return this.insert_cell_at_index(type, index+1);
962 };
961 };
963
962
964
963
965 /**
964 /**
966 * Insert cell at end of notebook
965 * Insert cell at end of notebook
967 *
966 *
968 * @method insert_cell_at_bottom
967 * @method insert_cell_at_bottom
969 * @param {String} type cell type
968 * @param {String} type cell type
970 *
969 *
971 * @return the added cell; or null
970 * @return the added cell; or null
972 **/
971 **/
973 Notebook.prototype.insert_cell_at_bottom = function (type){
972 Notebook.prototype.insert_cell_at_bottom = function (type){
974 var len = this.ncells();
973 var len = this.ncells();
975 return this.insert_cell_below(type,len-1);
974 return this.insert_cell_below(type,len-1);
976 };
975 };
977
976
978 /**
977 /**
979 * Turn a cell into a code cell.
978 * Turn a cell into a code cell.
980 *
979 *
981 * @method to_code
980 * @method to_code
982 * @param {Number} [index] A cell's index
981 * @param {Number} [index] A cell's index
983 */
982 */
984 Notebook.prototype.to_code = function (index) {
983 Notebook.prototype.to_code = function (index) {
985 var i = this.index_or_selected(index);
984 var i = this.index_or_selected(index);
986 if (this.is_valid_cell_index(i)) {
985 if (this.is_valid_cell_index(i)) {
987 var source_cell = this.get_cell(i);
986 var source_cell = this.get_cell(i);
988 if (!(source_cell instanceof codecell.CodeCell)) {
987 if (!(source_cell instanceof codecell.CodeCell)) {
989 var target_cell = this.insert_cell_below('code',i);
988 var target_cell = this.insert_cell_below('code',i);
990 var text = source_cell.get_text();
989 var text = source_cell.get_text();
991 if (text === source_cell.placeholder) {
990 if (text === source_cell.placeholder) {
992 text = '';
991 text = '';
993 }
992 }
994 //metadata
993 //metadata
995 target_cell.metadata = source_cell.metadata;
994 target_cell.metadata = source_cell.metadata;
996
995
997 target_cell.set_text(text);
996 target_cell.set_text(text);
998 // make this value the starting point, so that we can only undo
997 // make this value the starting point, so that we can only undo
999 // to this state, instead of a blank cell
998 // to this state, instead of a blank cell
1000 target_cell.code_mirror.clearHistory();
999 target_cell.code_mirror.clearHistory();
1001 source_cell.element.remove();
1000 source_cell.element.remove();
1002 this.select(i);
1001 this.select(i);
1003 var cursor = source_cell.code_mirror.getCursor();
1002 var cursor = source_cell.code_mirror.getCursor();
1004 target_cell.code_mirror.setCursor(cursor);
1003 target_cell.code_mirror.setCursor(cursor);
1005 this.set_dirty(true);
1004 this.set_dirty(true);
1006 }
1005 }
1007 }
1006 }
1008 };
1007 };
1009
1008
1010 /**
1009 /**
1011 * Turn a cell into a Markdown cell.
1010 * Turn a cell into a Markdown cell.
1012 *
1011 *
1013 * @method to_markdown
1012 * @method to_markdown
1014 * @param {Number} [index] A cell's index
1013 * @param {Number} [index] A cell's index
1015 */
1014 */
1016 Notebook.prototype.to_markdown = function (index) {
1015 Notebook.prototype.to_markdown = function (index) {
1017 var i = this.index_or_selected(index);
1016 var i = this.index_or_selected(index);
1018 if (this.is_valid_cell_index(i)) {
1017 if (this.is_valid_cell_index(i)) {
1019 var source_cell = this.get_cell(i);
1018 var source_cell = this.get_cell(i);
1020
1019
1021 if (!(source_cell instanceof textcell.MarkdownCell)) {
1020 if (!(source_cell instanceof textcell.MarkdownCell)) {
1022 var target_cell = this.insert_cell_below('markdown',i);
1021 var target_cell = this.insert_cell_below('markdown',i);
1023 var text = source_cell.get_text();
1022 var text = source_cell.get_text();
1024
1023
1025 if (text === source_cell.placeholder) {
1024 if (text === source_cell.placeholder) {
1026 text = '';
1025 text = '';
1027 }
1026 }
1028 // metadata
1027 // metadata
1029 target_cell.metadata = source_cell.metadata
1028 target_cell.metadata = source_cell.metadata
1030 // We must show the editor before setting its contents
1029 // We must show the editor before setting its contents
1031 target_cell.unrender();
1030 target_cell.unrender();
1032 target_cell.set_text(text);
1031 target_cell.set_text(text);
1033 // make this value the starting point, so that we can only undo
1032 // make this value the starting point, so that we can only undo
1034 // to this state, instead of a blank cell
1033 // to this state, instead of a blank cell
1035 target_cell.code_mirror.clearHistory();
1034 target_cell.code_mirror.clearHistory();
1036 source_cell.element.remove();
1035 source_cell.element.remove();
1037 this.select(i);
1036 this.select(i);
1038 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1037 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1039 target_cell.render();
1038 target_cell.render();
1040 }
1039 }
1041 var cursor = source_cell.code_mirror.getCursor();
1040 var cursor = source_cell.code_mirror.getCursor();
1042 target_cell.code_mirror.setCursor(cursor);
1041 target_cell.code_mirror.setCursor(cursor);
1043 this.set_dirty(true);
1042 this.set_dirty(true);
1044 }
1043 }
1045 }
1044 }
1046 };
1045 };
1047
1046
1048 /**
1047 /**
1049 * Turn a cell into a raw text cell.
1048 * Turn a cell into a raw text cell.
1050 *
1049 *
1051 * @method to_raw
1050 * @method to_raw
1052 * @param {Number} [index] A cell's index
1051 * @param {Number} [index] A cell's index
1053 */
1052 */
1054 Notebook.prototype.to_raw = function (index) {
1053 Notebook.prototype.to_raw = function (index) {
1055 var i = this.index_or_selected(index);
1054 var i = this.index_or_selected(index);
1056 if (this.is_valid_cell_index(i)) {
1055 if (this.is_valid_cell_index(i)) {
1057 var target_cell = null;
1056 var target_cell = null;
1058 var source_cell = this.get_cell(i);
1057 var source_cell = this.get_cell(i);
1059
1058
1060 if (!(source_cell instanceof textcell.RawCell)) {
1059 if (!(source_cell instanceof textcell.RawCell)) {
1061 target_cell = this.insert_cell_below('raw',i);
1060 target_cell = this.insert_cell_below('raw',i);
1062 var text = source_cell.get_text();
1061 var text = source_cell.get_text();
1063 if (text === source_cell.placeholder) {
1062 if (text === source_cell.placeholder) {
1064 text = '';
1063 text = '';
1065 }
1064 }
1066 //metadata
1065 //metadata
1067 target_cell.metadata = source_cell.metadata;
1066 target_cell.metadata = source_cell.metadata;
1068 // We must show the editor before setting its contents
1067 // We must show the editor before setting its contents
1069 target_cell.unrender();
1068 target_cell.unrender();
1070 target_cell.set_text(text);
1069 target_cell.set_text(text);
1071 // make this value the starting point, so that we can only undo
1070 // make this value the starting point, so that we can only undo
1072 // to this state, instead of a blank cell
1071 // to this state, instead of a blank cell
1073 target_cell.code_mirror.clearHistory();
1072 target_cell.code_mirror.clearHistory();
1074 source_cell.element.remove();
1073 source_cell.element.remove();
1075 this.select(i);
1074 this.select(i);
1076 var cursor = source_cell.code_mirror.getCursor();
1075 var cursor = source_cell.code_mirror.getCursor();
1077 target_cell.code_mirror.setCursor(cursor);
1076 target_cell.code_mirror.setCursor(cursor);
1078 this.set_dirty(true);
1077 this.set_dirty(true);
1079 }
1078 }
1080 }
1079 }
1081 };
1080 };
1082
1081
1083 /**
1082 /**
1084 * Turn a cell into a heading cell.
1083 * Turn a cell into a heading cell.
1085 *
1084 *
1086 * @method to_heading
1085 * @method to_heading
1087 * @param {Number} [index] A cell's index
1086 * @param {Number} [index] A cell's index
1088 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
1087 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
1089 */
1088 */
1090 Notebook.prototype.to_heading = function (index, level) {
1089 Notebook.prototype.to_heading = function (index, level) {
1091 level = level || 1;
1090 level = level || 1;
1092 var i = this.index_or_selected(index);
1091 var i = this.index_or_selected(index);
1093 if (this.is_valid_cell_index(i)) {
1092 if (this.is_valid_cell_index(i)) {
1094 var source_cell = this.get_cell(i);
1093 var source_cell = this.get_cell(i);
1095 var target_cell = null;
1094 var target_cell = null;
1096 if (source_cell instanceof textcell.HeadingCell) {
1095 if (source_cell instanceof textcell.HeadingCell) {
1097 source_cell.set_level(level);
1096 source_cell.set_level(level);
1098 } else {
1097 } else {
1099 target_cell = this.insert_cell_below('heading',i);
1098 target_cell = this.insert_cell_below('heading',i);
1100 var text = source_cell.get_text();
1099 var text = source_cell.get_text();
1101 if (text === source_cell.placeholder) {
1100 if (text === source_cell.placeholder) {
1102 text = '';
1101 text = '';
1103 }
1102 }
1104 //metadata
1103 //metadata
1105 target_cell.metadata = source_cell.metadata;
1104 target_cell.metadata = source_cell.metadata;
1106 // We must show the editor before setting its contents
1105 // We must show the editor before setting its contents
1107 target_cell.set_level(level);
1106 target_cell.set_level(level);
1108 target_cell.unrender();
1107 target_cell.unrender();
1109 target_cell.set_text(text);
1108 target_cell.set_text(text);
1110 // make this value the starting point, so that we can only undo
1109 // make this value the starting point, so that we can only undo
1111 // to this state, instead of a blank cell
1110 // to this state, instead of a blank cell
1112 target_cell.code_mirror.clearHistory();
1111 target_cell.code_mirror.clearHistory();
1113 source_cell.element.remove();
1112 source_cell.element.remove();
1114 this.select(i);
1113 this.select(i);
1115 var cursor = source_cell.code_mirror.getCursor();
1114 var cursor = source_cell.code_mirror.getCursor();
1116 target_cell.code_mirror.setCursor(cursor);
1115 target_cell.code_mirror.setCursor(cursor);
1117 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1116 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1118 target_cell.render();
1117 target_cell.render();
1119 }
1118 }
1120 }
1119 }
1121 this.set_dirty(true);
1120 this.set_dirty(true);
1122 this.events.trigger('selected_cell_type_changed.Notebook',
1121 this.events.trigger('selected_cell_type_changed.Notebook',
1123 {'cell_type':'heading',level:level}
1122 {'cell_type':'heading',level:level}
1124 );
1123 );
1125 }
1124 }
1126 };
1125 };
1127
1126
1128
1127
1129 // Cut/Copy/Paste
1128 // Cut/Copy/Paste
1130
1129
1131 /**
1130 /**
1132 * Enable UI elements for pasting cells.
1131 * Enable UI elements for pasting cells.
1133 *
1132 *
1134 * @method enable_paste
1133 * @method enable_paste
1135 */
1134 */
1136 Notebook.prototype.enable_paste = function () {
1135 Notebook.prototype.enable_paste = function () {
1137 var that = this;
1136 var that = this;
1138 if (!this.paste_enabled) {
1137 if (!this.paste_enabled) {
1139 $('#paste_cell_replace').removeClass('disabled')
1138 $('#paste_cell_replace').removeClass('disabled')
1140 .on('click', function () {that.paste_cell_replace();});
1139 .on('click', function () {that.paste_cell_replace();});
1141 $('#paste_cell_above').removeClass('disabled')
1140 $('#paste_cell_above').removeClass('disabled')
1142 .on('click', function () {that.paste_cell_above();});
1141 .on('click', function () {that.paste_cell_above();});
1143 $('#paste_cell_below').removeClass('disabled')
1142 $('#paste_cell_below').removeClass('disabled')
1144 .on('click', function () {that.paste_cell_below();});
1143 .on('click', function () {that.paste_cell_below();});
1145 this.paste_enabled = true;
1144 this.paste_enabled = true;
1146 }
1145 }
1147 };
1146 };
1148
1147
1149 /**
1148 /**
1150 * Disable UI elements for pasting cells.
1149 * Disable UI elements for pasting cells.
1151 *
1150 *
1152 * @method disable_paste
1151 * @method disable_paste
1153 */
1152 */
1154 Notebook.prototype.disable_paste = function () {
1153 Notebook.prototype.disable_paste = function () {
1155 if (this.paste_enabled) {
1154 if (this.paste_enabled) {
1156 $('#paste_cell_replace').addClass('disabled').off('click');
1155 $('#paste_cell_replace').addClass('disabled').off('click');
1157 $('#paste_cell_above').addClass('disabled').off('click');
1156 $('#paste_cell_above').addClass('disabled').off('click');
1158 $('#paste_cell_below').addClass('disabled').off('click');
1157 $('#paste_cell_below').addClass('disabled').off('click');
1159 this.paste_enabled = false;
1158 this.paste_enabled = false;
1160 }
1159 }
1161 };
1160 };
1162
1161
1163 /**
1162 /**
1164 * Cut a cell.
1163 * Cut a cell.
1165 *
1164 *
1166 * @method cut_cell
1165 * @method cut_cell
1167 */
1166 */
1168 Notebook.prototype.cut_cell = function () {
1167 Notebook.prototype.cut_cell = function () {
1169 this.copy_cell();
1168 this.copy_cell();
1170 this.delete_cell();
1169 this.delete_cell();
1171 };
1170 };
1172
1171
1173 /**
1172 /**
1174 * Copy a cell.
1173 * Copy a cell.
1175 *
1174 *
1176 * @method copy_cell
1175 * @method copy_cell
1177 */
1176 */
1178 Notebook.prototype.copy_cell = function () {
1177 Notebook.prototype.copy_cell = function () {
1179 var cell = this.get_selected_cell();
1178 var cell = this.get_selected_cell();
1180 this.clipboard = cell.toJSON();
1179 this.clipboard = cell.toJSON();
1181 // remove undeletable status from the copied cell
1180 // remove undeletable status from the copied cell
1182 if (this.clipboard.metadata.deletable !== undefined) {
1181 if (this.clipboard.metadata.deletable !== undefined) {
1183 delete this.clipboard.metadata.deletable;
1182 delete this.clipboard.metadata.deletable;
1184 }
1183 }
1185 this.enable_paste();
1184 this.enable_paste();
1186 };
1185 };
1187
1186
1188 /**
1187 /**
1189 * Replace the selected cell with a cell in the clipboard.
1188 * Replace the selected cell with a cell in the clipboard.
1190 *
1189 *
1191 * @method paste_cell_replace
1190 * @method paste_cell_replace
1192 */
1191 */
1193 Notebook.prototype.paste_cell_replace = function () {
1192 Notebook.prototype.paste_cell_replace = function () {
1194 if (this.clipboard !== null && this.paste_enabled) {
1193 if (this.clipboard !== null && this.paste_enabled) {
1195 var cell_data = this.clipboard;
1194 var cell_data = this.clipboard;
1196 var new_cell = this.insert_cell_above(cell_data.cell_type);
1195 var new_cell = this.insert_cell_above(cell_data.cell_type);
1197 new_cell.fromJSON(cell_data);
1196 new_cell.fromJSON(cell_data);
1198 var old_cell = this.get_next_cell(new_cell);
1197 var old_cell = this.get_next_cell(new_cell);
1199 this.delete_cell(this.find_cell_index(old_cell));
1198 this.delete_cell(this.find_cell_index(old_cell));
1200 this.select(this.find_cell_index(new_cell));
1199 this.select(this.find_cell_index(new_cell));
1201 }
1200 }
1202 };
1201 };
1203
1202
1204 /**
1203 /**
1205 * Paste a cell from the clipboard above the selected cell.
1204 * Paste a cell from the clipboard above the selected cell.
1206 *
1205 *
1207 * @method paste_cell_above
1206 * @method paste_cell_above
1208 */
1207 */
1209 Notebook.prototype.paste_cell_above = function () {
1208 Notebook.prototype.paste_cell_above = function () {
1210 if (this.clipboard !== null && this.paste_enabled) {
1209 if (this.clipboard !== null && this.paste_enabled) {
1211 var cell_data = this.clipboard;
1210 var cell_data = this.clipboard;
1212 var new_cell = this.insert_cell_above(cell_data.cell_type);
1211 var new_cell = this.insert_cell_above(cell_data.cell_type);
1213 new_cell.fromJSON(cell_data);
1212 new_cell.fromJSON(cell_data);
1214 new_cell.focus_cell();
1213 new_cell.focus_cell();
1215 }
1214 }
1216 };
1215 };
1217
1216
1218 /**
1217 /**
1219 * Paste a cell from the clipboard below the selected cell.
1218 * Paste a cell from the clipboard below the selected cell.
1220 *
1219 *
1221 * @method paste_cell_below
1220 * @method paste_cell_below
1222 */
1221 */
1223 Notebook.prototype.paste_cell_below = function () {
1222 Notebook.prototype.paste_cell_below = function () {
1224 if (this.clipboard !== null && this.paste_enabled) {
1223 if (this.clipboard !== null && this.paste_enabled) {
1225 var cell_data = this.clipboard;
1224 var cell_data = this.clipboard;
1226 var new_cell = this.insert_cell_below(cell_data.cell_type);
1225 var new_cell = this.insert_cell_below(cell_data.cell_type);
1227 new_cell.fromJSON(cell_data);
1226 new_cell.fromJSON(cell_data);
1228 new_cell.focus_cell();
1227 new_cell.focus_cell();
1229 }
1228 }
1230 };
1229 };
1231
1230
1232 // Split/merge
1231 // Split/merge
1233
1232
1234 /**
1233 /**
1235 * Split the selected cell into two, at the cursor.
1234 * Split the selected cell into two, at the cursor.
1236 *
1235 *
1237 * @method split_cell
1236 * @method split_cell
1238 */
1237 */
1239 Notebook.prototype.split_cell = function () {
1238 Notebook.prototype.split_cell = function () {
1240 var mdc = textcell.MarkdownCell;
1239 var mdc = textcell.MarkdownCell;
1241 var rc = textcell.RawCell;
1240 var rc = textcell.RawCell;
1242 var cell = this.get_selected_cell();
1241 var cell = this.get_selected_cell();
1243 if (cell.is_splittable()) {
1242 if (cell.is_splittable()) {
1244 var texta = cell.get_pre_cursor();
1243 var texta = cell.get_pre_cursor();
1245 var textb = cell.get_post_cursor();
1244 var textb = cell.get_post_cursor();
1246 cell.set_text(textb);
1245 cell.set_text(textb);
1247 var new_cell = this.insert_cell_above(cell.cell_type);
1246 var new_cell = this.insert_cell_above(cell.cell_type);
1248 // Unrender the new cell so we can call set_text.
1247 // Unrender the new cell so we can call set_text.
1249 new_cell.unrender();
1248 new_cell.unrender();
1250 new_cell.set_text(texta);
1249 new_cell.set_text(texta);
1251 }
1250 }
1252 };
1251 };
1253
1252
1254 /**
1253 /**
1255 * Combine the selected cell into the cell above it.
1254 * Combine the selected cell into the cell above it.
1256 *
1255 *
1257 * @method merge_cell_above
1256 * @method merge_cell_above
1258 */
1257 */
1259 Notebook.prototype.merge_cell_above = function () {
1258 Notebook.prototype.merge_cell_above = function () {
1260 var mdc = textcell.MarkdownCell;
1259 var mdc = textcell.MarkdownCell;
1261 var rc = textcell.RawCell;
1260 var rc = textcell.RawCell;
1262 var index = this.get_selected_index();
1261 var index = this.get_selected_index();
1263 var cell = this.get_cell(index);
1262 var cell = this.get_cell(index);
1264 var render = cell.rendered;
1263 var render = cell.rendered;
1265 if (!cell.is_mergeable()) {
1264 if (!cell.is_mergeable()) {
1266 return;
1265 return;
1267 }
1266 }
1268 if (index > 0) {
1267 if (index > 0) {
1269 var upper_cell = this.get_cell(index-1);
1268 var upper_cell = this.get_cell(index-1);
1270 if (!upper_cell.is_mergeable()) {
1269 if (!upper_cell.is_mergeable()) {
1271 return;
1270 return;
1272 }
1271 }
1273 var upper_text = upper_cell.get_text();
1272 var upper_text = upper_cell.get_text();
1274 var text = cell.get_text();
1273 var text = cell.get_text();
1275 if (cell instanceof codecell.CodeCell) {
1274 if (cell instanceof codecell.CodeCell) {
1276 cell.set_text(upper_text+'\n'+text);
1275 cell.set_text(upper_text+'\n'+text);
1277 } else {
1276 } else {
1278 cell.unrender(); // Must unrender before we set_text.
1277 cell.unrender(); // Must unrender before we set_text.
1279 cell.set_text(upper_text+'\n\n'+text);
1278 cell.set_text(upper_text+'\n\n'+text);
1280 if (render) {
1279 if (render) {
1281 // The rendered state of the final cell should match
1280 // The rendered state of the final cell should match
1282 // that of the original selected cell;
1281 // that of the original selected cell;
1283 cell.render();
1282 cell.render();
1284 }
1283 }
1285 }
1284 }
1286 this.delete_cell(index-1);
1285 this.delete_cell(index-1);
1287 this.select(this.find_cell_index(cell));
1286 this.select(this.find_cell_index(cell));
1288 }
1287 }
1289 };
1288 };
1290
1289
1291 /**
1290 /**
1292 * Combine the selected cell into the cell below it.
1291 * Combine the selected cell into the cell below it.
1293 *
1292 *
1294 * @method merge_cell_below
1293 * @method merge_cell_below
1295 */
1294 */
1296 Notebook.prototype.merge_cell_below = function () {
1295 Notebook.prototype.merge_cell_below = function () {
1297 var mdc = textcell.MarkdownCell;
1296 var mdc = textcell.MarkdownCell;
1298 var rc = textcell.RawCell;
1297 var rc = textcell.RawCell;
1299 var index = this.get_selected_index();
1298 var index = this.get_selected_index();
1300 var cell = this.get_cell(index);
1299 var cell = this.get_cell(index);
1301 var render = cell.rendered;
1300 var render = cell.rendered;
1302 if (!cell.is_mergeable()) {
1301 if (!cell.is_mergeable()) {
1303 return;
1302 return;
1304 }
1303 }
1305 if (index < this.ncells()-1) {
1304 if (index < this.ncells()-1) {
1306 var lower_cell = this.get_cell(index+1);
1305 var lower_cell = this.get_cell(index+1);
1307 if (!lower_cell.is_mergeable()) {
1306 if (!lower_cell.is_mergeable()) {
1308 return;
1307 return;
1309 }
1308 }
1310 var lower_text = lower_cell.get_text();
1309 var lower_text = lower_cell.get_text();
1311 var text = cell.get_text();
1310 var text = cell.get_text();
1312 if (cell instanceof codecell.CodeCell) {
1311 if (cell instanceof codecell.CodeCell) {
1313 cell.set_text(text+'\n'+lower_text);
1312 cell.set_text(text+'\n'+lower_text);
1314 } else {
1313 } else {
1315 cell.unrender(); // Must unrender before we set_text.
1314 cell.unrender(); // Must unrender before we set_text.
1316 cell.set_text(text+'\n\n'+lower_text);
1315 cell.set_text(text+'\n\n'+lower_text);
1317 if (render) {
1316 if (render) {
1318 // The rendered state of the final cell should match
1317 // The rendered state of the final cell should match
1319 // that of the original selected cell;
1318 // that of the original selected cell;
1320 cell.render();
1319 cell.render();
1321 }
1320 }
1322 }
1321 }
1323 this.delete_cell(index+1);
1322 this.delete_cell(index+1);
1324 this.select(this.find_cell_index(cell));
1323 this.select(this.find_cell_index(cell));
1325 }
1324 }
1326 };
1325 };
1327
1326
1328
1327
1329 // Cell collapsing and output clearing
1328 // Cell collapsing and output clearing
1330
1329
1331 /**
1330 /**
1332 * Hide a cell's output.
1331 * Hide a cell's output.
1333 *
1332 *
1334 * @method collapse_output
1333 * @method collapse_output
1335 * @param {Number} index A cell's numeric index
1334 * @param {Number} index A cell's numeric index
1336 */
1335 */
1337 Notebook.prototype.collapse_output = function (index) {
1336 Notebook.prototype.collapse_output = function (index) {
1338 var i = this.index_or_selected(index);
1337 var i = this.index_or_selected(index);
1339 var cell = this.get_cell(i);
1338 var cell = this.get_cell(i);
1340 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1339 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1341 cell.collapse_output();
1340 cell.collapse_output();
1342 this.set_dirty(true);
1341 this.set_dirty(true);
1343 }
1342 }
1344 };
1343 };
1345
1344
1346 /**
1345 /**
1347 * Hide each code cell's output area.
1346 * Hide each code cell's output area.
1348 *
1347 *
1349 * @method collapse_all_output
1348 * @method collapse_all_output
1350 */
1349 */
1351 Notebook.prototype.collapse_all_output = function () {
1350 Notebook.prototype.collapse_all_output = function () {
1352 $.map(this.get_cells(), function (cell, i) {
1351 $.map(this.get_cells(), function (cell, i) {
1353 if (cell instanceof codecell.CodeCell) {
1352 if (cell instanceof codecell.CodeCell) {
1354 cell.collapse_output();
1353 cell.collapse_output();
1355 }
1354 }
1356 });
1355 });
1357 // this should not be set if the `collapse` key is removed from nbformat
1356 // this should not be set if the `collapse` key is removed from nbformat
1358 this.set_dirty(true);
1357 this.set_dirty(true);
1359 };
1358 };
1360
1359
1361 /**
1360 /**
1362 * Show a cell's output.
1361 * Show a cell's output.
1363 *
1362 *
1364 * @method expand_output
1363 * @method expand_output
1365 * @param {Number} index A cell's numeric index
1364 * @param {Number} index A cell's numeric index
1366 */
1365 */
1367 Notebook.prototype.expand_output = function (index) {
1366 Notebook.prototype.expand_output = function (index) {
1368 var i = this.index_or_selected(index);
1367 var i = this.index_or_selected(index);
1369 var cell = this.get_cell(i);
1368 var cell = this.get_cell(i);
1370 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1369 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1371 cell.expand_output();
1370 cell.expand_output();
1372 this.set_dirty(true);
1371 this.set_dirty(true);
1373 }
1372 }
1374 };
1373 };
1375
1374
1376 /**
1375 /**
1377 * Expand each code cell's output area, and remove scrollbars.
1376 * Expand each code cell's output area, and remove scrollbars.
1378 *
1377 *
1379 * @method expand_all_output
1378 * @method expand_all_output
1380 */
1379 */
1381 Notebook.prototype.expand_all_output = function () {
1380 Notebook.prototype.expand_all_output = function () {
1382 $.map(this.get_cells(), function (cell, i) {
1381 $.map(this.get_cells(), function (cell, i) {
1383 if (cell instanceof codecell.CodeCell) {
1382 if (cell instanceof codecell.CodeCell) {
1384 cell.expand_output();
1383 cell.expand_output();
1385 }
1384 }
1386 });
1385 });
1387 // this should not be set if the `collapse` key is removed from nbformat
1386 // this should not be set if the `collapse` key is removed from nbformat
1388 this.set_dirty(true);
1387 this.set_dirty(true);
1389 };
1388 };
1390
1389
1391 /**
1390 /**
1392 * Clear the selected CodeCell's output area.
1391 * Clear the selected CodeCell's output area.
1393 *
1392 *
1394 * @method clear_output
1393 * @method clear_output
1395 * @param {Number} index A cell's numeric index
1394 * @param {Number} index A cell's numeric index
1396 */
1395 */
1397 Notebook.prototype.clear_output = function (index) {
1396 Notebook.prototype.clear_output = function (index) {
1398 var i = this.index_or_selected(index);
1397 var i = this.index_or_selected(index);
1399 var cell = this.get_cell(i);
1398 var cell = this.get_cell(i);
1400 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1399 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1401 cell.clear_output();
1400 cell.clear_output();
1402 this.set_dirty(true);
1401 this.set_dirty(true);
1403 }
1402 }
1404 };
1403 };
1405
1404
1406 /**
1405 /**
1407 * Clear each code cell's output area.
1406 * Clear each code cell's output area.
1408 *
1407 *
1409 * @method clear_all_output
1408 * @method clear_all_output
1410 */
1409 */
1411 Notebook.prototype.clear_all_output = function () {
1410 Notebook.prototype.clear_all_output = function () {
1412 $.map(this.get_cells(), function (cell, i) {
1411 $.map(this.get_cells(), function (cell, i) {
1413 if (cell instanceof codecell.CodeCell) {
1412 if (cell instanceof codecell.CodeCell) {
1414 cell.clear_output();
1413 cell.clear_output();
1415 }
1414 }
1416 });
1415 });
1417 this.set_dirty(true);
1416 this.set_dirty(true);
1418 };
1417 };
1419
1418
1420 /**
1419 /**
1421 * Scroll the selected CodeCell's output area.
1420 * Scroll the selected CodeCell's output area.
1422 *
1421 *
1423 * @method scroll_output
1422 * @method scroll_output
1424 * @param {Number} index A cell's numeric index
1423 * @param {Number} index A cell's numeric index
1425 */
1424 */
1426 Notebook.prototype.scroll_output = function (index) {
1425 Notebook.prototype.scroll_output = function (index) {
1427 var i = this.index_or_selected(index);
1426 var i = this.index_or_selected(index);
1428 var cell = this.get_cell(i);
1427 var cell = this.get_cell(i);
1429 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1428 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1430 cell.scroll_output();
1429 cell.scroll_output();
1431 this.set_dirty(true);
1430 this.set_dirty(true);
1432 }
1431 }
1433 };
1432 };
1434
1433
1435 /**
1434 /**
1436 * Expand each code cell's output area, and add a scrollbar for long output.
1435 * Expand each code cell's output area, and add a scrollbar for long output.
1437 *
1436 *
1438 * @method scroll_all_output
1437 * @method scroll_all_output
1439 */
1438 */
1440 Notebook.prototype.scroll_all_output = function () {
1439 Notebook.prototype.scroll_all_output = function () {
1441 $.map(this.get_cells(), function (cell, i) {
1440 $.map(this.get_cells(), function (cell, i) {
1442 if (cell instanceof codecell.CodeCell) {
1441 if (cell instanceof codecell.CodeCell) {
1443 cell.scroll_output();
1442 cell.scroll_output();
1444 }
1443 }
1445 });
1444 });
1446 // this should not be set if the `collapse` key is removed from nbformat
1445 // this should not be set if the `collapse` key is removed from nbformat
1447 this.set_dirty(true);
1446 this.set_dirty(true);
1448 };
1447 };
1449
1448
1450 /** Toggle whether a cell's output is collapsed or expanded.
1449 /** Toggle whether a cell's output is collapsed or expanded.
1451 *
1450 *
1452 * @method toggle_output
1451 * @method toggle_output
1453 * @param {Number} index A cell's numeric index
1452 * @param {Number} index A cell's numeric index
1454 */
1453 */
1455 Notebook.prototype.toggle_output = function (index) {
1454 Notebook.prototype.toggle_output = function (index) {
1456 var i = this.index_or_selected(index);
1455 var i = this.index_or_selected(index);
1457 var cell = this.get_cell(i);
1456 var cell = this.get_cell(i);
1458 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1457 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1459 cell.toggle_output();
1458 cell.toggle_output();
1460 this.set_dirty(true);
1459 this.set_dirty(true);
1461 }
1460 }
1462 };
1461 };
1463
1462
1464 /**
1463 /**
1465 * Hide/show the output of all cells.
1464 * Hide/show the output of all cells.
1466 *
1465 *
1467 * @method toggle_all_output
1466 * @method toggle_all_output
1468 */
1467 */
1469 Notebook.prototype.toggle_all_output = function () {
1468 Notebook.prototype.toggle_all_output = function () {
1470 $.map(this.get_cells(), function (cell, i) {
1469 $.map(this.get_cells(), function (cell, i) {
1471 if (cell instanceof codecell.CodeCell) {
1470 if (cell instanceof codecell.CodeCell) {
1472 cell.toggle_output();
1471 cell.toggle_output();
1473 }
1472 }
1474 });
1473 });
1475 // this should not be set if the `collapse` key is removed from nbformat
1474 // this should not be set if the `collapse` key is removed from nbformat
1476 this.set_dirty(true);
1475 this.set_dirty(true);
1477 };
1476 };
1478
1477
1479 /**
1478 /**
1480 * Toggle a scrollbar for long cell outputs.
1479 * Toggle a scrollbar for long cell outputs.
1481 *
1480 *
1482 * @method toggle_output_scroll
1481 * @method toggle_output_scroll
1483 * @param {Number} index A cell's numeric index
1482 * @param {Number} index A cell's numeric index
1484 */
1483 */
1485 Notebook.prototype.toggle_output_scroll = function (index) {
1484 Notebook.prototype.toggle_output_scroll = function (index) {
1486 var i = this.index_or_selected(index);
1485 var i = this.index_or_selected(index);
1487 var cell = this.get_cell(i);
1486 var cell = this.get_cell(i);
1488 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1487 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1489 cell.toggle_output_scroll();
1488 cell.toggle_output_scroll();
1490 this.set_dirty(true);
1489 this.set_dirty(true);
1491 }
1490 }
1492 };
1491 };
1493
1492
1494 /**
1493 /**
1495 * Toggle the scrolling of long output on all cells.
1494 * Toggle the scrolling of long output on all cells.
1496 *
1495 *
1497 * @method toggle_all_output_scrolling
1496 * @method toggle_all_output_scrolling
1498 */
1497 */
1499 Notebook.prototype.toggle_all_output_scroll = function () {
1498 Notebook.prototype.toggle_all_output_scroll = function () {
1500 $.map(this.get_cells(), function (cell, i) {
1499 $.map(this.get_cells(), function (cell, i) {
1501 if (cell instanceof codecell.CodeCell) {
1500 if (cell instanceof codecell.CodeCell) {
1502 cell.toggle_output_scroll();
1501 cell.toggle_output_scroll();
1503 }
1502 }
1504 });
1503 });
1505 // this should not be set if the `collapse` key is removed from nbformat
1504 // this should not be set if the `collapse` key is removed from nbformat
1506 this.set_dirty(true);
1505 this.set_dirty(true);
1507 };
1506 };
1508
1507
1509 // Other cell functions: line numbers, ...
1508 // Other cell functions: line numbers, ...
1510
1509
1511 /**
1510 /**
1512 * Toggle line numbers in the selected cell's input area.
1511 * Toggle line numbers in the selected cell's input area.
1513 *
1512 *
1514 * @method cell_toggle_line_numbers
1513 * @method cell_toggle_line_numbers
1515 */
1514 */
1516 Notebook.prototype.cell_toggle_line_numbers = function() {
1515 Notebook.prototype.cell_toggle_line_numbers = function() {
1517 this.get_selected_cell().toggle_line_numbers();
1516 this.get_selected_cell().toggle_line_numbers();
1518 };
1517 };
1519
1518
1520 /**
1519 /**
1521 * Set the codemirror mode for all code cells, including the default for
1520 * Set the codemirror mode for all code cells, including the default for
1522 * new code cells.
1521 * new code cells.
1523 *
1522 *
1524 * @method set_codemirror_mode
1523 * @method set_codemirror_mode
1525 */
1524 */
1526 Notebook.prototype.set_codemirror_mode = function(newmode){
1525 Notebook.prototype.set_codemirror_mode = function(newmode){
1527 if (newmode === this.codemirror_mode) {
1526 if (newmode === this.codemirror_mode) {
1528 return;
1527 return;
1529 }
1528 }
1530 this.codemirror_mode = newmode;
1529 this.codemirror_mode = newmode;
1531 codecell.CodeCell.options_default.cm_config.mode = newmode;
1530 codecell.CodeCell.options_default.cm_config.mode = newmode;
1532 modename = newmode.mode || newmode.name || newmode
1531 modename = newmode.mode || newmode.name || newmode
1533
1532
1534 that = this;
1533 that = this;
1535 utils.requireCodeMirrorMode(modename, function () {
1534 utils.requireCodeMirrorMode(modename, function () {
1536 $.map(that.get_cells(), function(cell, i) {
1535 $.map(that.get_cells(), function(cell, i) {
1537 if (cell.cell_type === 'code'){
1536 if (cell.cell_type === 'code'){
1538 cell.code_mirror.setOption('mode', newmode);
1537 cell.code_mirror.setOption('mode', newmode);
1539 // This is currently redundant, because cm_config ends up as
1538 // This is currently redundant, because cm_config ends up as
1540 // codemirror's own .options object, but I don't want to
1539 // codemirror's own .options object, but I don't want to
1541 // rely on that.
1540 // rely on that.
1542 cell.cm_config.mode = newmode;
1541 cell.cm_config.mode = newmode;
1543 }
1542 }
1544 });
1543 });
1545 })
1544 })
1546 };
1545 };
1547
1546
1548 // Session related things
1547 // Session related things
1549
1548
1550 /**
1549 /**
1551 * Start a new session and set it on each code cell.
1550 * Start a new session and set it on each code cell.
1552 *
1551 *
1553 * @method start_session
1552 * @method start_session
1554 */
1553 */
1555 Notebook.prototype.start_session = function (kernel_name) {
1554 Notebook.prototype.start_session = function (kernel_name) {
1556 var that = this;
1555 var that = this;
1557 if (this._session_starting) {
1556 if (this._session_starting) {
1558 throw new session.SessionAlreadyStarting();
1557 throw new session.SessionAlreadyStarting();
1559 }
1558 }
1560 this._session_starting = true;
1559 this._session_starting = true;
1561
1560
1562 var options = {
1561 var options = {
1563 base_url: this.base_url,
1562 base_url: this.base_url,
1564 ws_url: this.ws_url,
1563 ws_url: this.ws_url,
1565 notebook_path: this.notebook_path,
1564 notebook_path: this.notebook_path,
1566 notebook_name: this.notebook_name,
1565 notebook_name: this.notebook_name,
1567 kernel_name: kernel_name,
1566 kernel_name: kernel_name,
1568 notebook: this
1567 notebook: this
1569 };
1568 };
1570
1569
1571 var success = $.proxy(this._session_started, this);
1570 var success = $.proxy(this._session_started, this);
1572 var failure = $.proxy(this._session_start_failed, this);
1571 var failure = $.proxy(this._session_start_failed, this);
1573
1572
1574 if (this.session !== null) {
1573 if (this.session !== null) {
1575 this.session.restart(options, success, failure);
1574 this.session.restart(options, success, failure);
1576 } else {
1575 } else {
1577 this.session = new session.Session(options);
1576 this.session = new session.Session(options);
1578 this.session.start(success, failure);
1577 this.session.start(success, failure);
1579 }
1578 }
1580 };
1579 };
1581
1580
1582
1581
1583 /**
1582 /**
1584 * Once a session is started, link the code cells to the kernel and pass the
1583 * Once a session is started, link the code cells to the kernel and pass the
1585 * comm manager to the widget manager
1584 * comm manager to the widget manager
1586 *
1585 *
1587 */
1586 */
1588 Notebook.prototype._session_started = function (){
1587 Notebook.prototype._session_started = function (){
1589 this._session_starting = false;
1588 this._session_starting = false;
1590 this.kernel = this.session.kernel;
1589 this.kernel = this.session.kernel;
1591 var ncells = this.ncells();
1590 var ncells = this.ncells();
1592 for (var i=0; i<ncells; i++) {
1591 for (var i=0; i<ncells; i++) {
1593 var cell = this.get_cell(i);
1592 var cell = this.get_cell(i);
1594 if (cell instanceof codecell.CodeCell) {
1593 if (cell instanceof codecell.CodeCell) {
1595 cell.set_kernel(this.session.kernel);
1594 cell.set_kernel(this.session.kernel);
1596 }
1595 }
1597 }
1596 }
1598 };
1597 };
1599 Notebook.prototype._session_start_failed = function (jqxhr, status, error){
1598 Notebook.prototype._session_start_failed = function (jqxhr, status, error){
1600 this._session_starting = false;
1599 this._session_starting = false;
1601 utils.log_ajax_error(jqxhr, status, error);
1600 utils.log_ajax_error(jqxhr, status, error);
1602 };
1601 };
1603
1602
1604 /**
1603 /**
1605 * Prompt the user to restart the IPython kernel.
1604 * Prompt the user to restart the IPython kernel.
1606 *
1605 *
1607 * @method restart_kernel
1606 * @method restart_kernel
1608 */
1607 */
1609 Notebook.prototype.restart_kernel = function () {
1608 Notebook.prototype.restart_kernel = function () {
1610 var that = this;
1609 var that = this;
1611 dialog.modal({
1610 dialog.modal({
1612 notebook: this,
1611 notebook: this,
1613 keyboard_manager: this.keyboard_manager,
1612 keyboard_manager: this.keyboard_manager,
1614 title : "Restart kernel or continue running?",
1613 title : "Restart kernel or continue running?",
1615 body : $("<p/>").text(
1614 body : $("<p/>").text(
1616 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1615 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1617 ),
1616 ),
1618 buttons : {
1617 buttons : {
1619 "Continue running" : {},
1618 "Continue running" : {},
1620 "Restart" : {
1619 "Restart" : {
1621 "class" : "btn-danger",
1620 "class" : "btn-danger",
1622 "click" : function() {
1621 "click" : function() {
1623 that.kernel.restart();
1622 that.kernel.restart();
1624 }
1623 }
1625 }
1624 }
1626 }
1625 }
1627 });
1626 });
1628 };
1627 };
1629
1628
1630 /**
1629 /**
1631 * Execute or render cell outputs and go into command mode.
1630 * Execute or render cell outputs and go into command mode.
1632 *
1631 *
1633 * @method execute_cell
1632 * @method execute_cell
1634 */
1633 */
1635 Notebook.prototype.execute_cell = function () {
1634 Notebook.prototype.execute_cell = function () {
1636 // mode = shift, ctrl, alt
1635 // mode = shift, ctrl, alt
1637 var cell = this.get_selected_cell();
1636 var cell = this.get_selected_cell();
1638 var cell_index = this.find_cell_index(cell);
1637 var cell_index = this.find_cell_index(cell);
1639
1638
1640 cell.execute();
1639 cell.execute();
1641 this.command_mode();
1640 this.command_mode();
1642 this.set_dirty(true);
1641 this.set_dirty(true);
1643 };
1642 };
1644
1643
1645 /**
1644 /**
1646 * Execute or render cell outputs and insert a new cell below.
1645 * Execute or render cell outputs and insert a new cell below.
1647 *
1646 *
1648 * @method execute_cell_and_insert_below
1647 * @method execute_cell_and_insert_below
1649 */
1648 */
1650 Notebook.prototype.execute_cell_and_insert_below = function () {
1649 Notebook.prototype.execute_cell_and_insert_below = function () {
1651 var cell = this.get_selected_cell();
1650 var cell = this.get_selected_cell();
1652 var cell_index = this.find_cell_index(cell);
1651 var cell_index = this.find_cell_index(cell);
1653
1652
1654 cell.execute();
1653 cell.execute();
1655
1654
1656 // If we are at the end always insert a new cell and return
1655 // If we are at the end always insert a new cell and return
1657 if (cell_index === (this.ncells()-1)) {
1656 if (cell_index === (this.ncells()-1)) {
1658 this.command_mode();
1657 this.command_mode();
1659 this.insert_cell_below();
1658 this.insert_cell_below();
1660 this.select(cell_index+1);
1659 this.select(cell_index+1);
1661 this.edit_mode();
1660 this.edit_mode();
1662 this.scroll_to_bottom();
1661 this.scroll_to_bottom();
1663 this.set_dirty(true);
1662 this.set_dirty(true);
1664 return;
1663 return;
1665 }
1664 }
1666
1665
1667 this.command_mode();
1666 this.command_mode();
1668 this.insert_cell_below();
1667 this.insert_cell_below();
1669 this.select(cell_index+1);
1668 this.select(cell_index+1);
1670 this.edit_mode();
1669 this.edit_mode();
1671 this.set_dirty(true);
1670 this.set_dirty(true);
1672 };
1671 };
1673
1672
1674 /**
1673 /**
1675 * Execute or render cell outputs and select the next cell.
1674 * Execute or render cell outputs and select the next cell.
1676 *
1675 *
1677 * @method execute_cell_and_select_below
1676 * @method execute_cell_and_select_below
1678 */
1677 */
1679 Notebook.prototype.execute_cell_and_select_below = function () {
1678 Notebook.prototype.execute_cell_and_select_below = function () {
1680
1679
1681 var cell = this.get_selected_cell();
1680 var cell = this.get_selected_cell();
1682 var cell_index = this.find_cell_index(cell);
1681 var cell_index = this.find_cell_index(cell);
1683
1682
1684 cell.execute();
1683 cell.execute();
1685
1684
1686 // If we are at the end always insert a new cell and return
1685 // If we are at the end always insert a new cell and return
1687 if (cell_index === (this.ncells()-1)) {
1686 if (cell_index === (this.ncells()-1)) {
1688 this.command_mode();
1687 this.command_mode();
1689 this.insert_cell_below();
1688 this.insert_cell_below();
1690 this.select(cell_index+1);
1689 this.select(cell_index+1);
1691 this.edit_mode();
1690 this.edit_mode();
1692 this.scroll_to_bottom();
1691 this.scroll_to_bottom();
1693 this.set_dirty(true);
1692 this.set_dirty(true);
1694 return;
1693 return;
1695 }
1694 }
1696
1695
1697 this.command_mode();
1696 this.command_mode();
1698 this.select(cell_index+1);
1697 this.select(cell_index+1);
1699 this.focus_cell();
1698 this.focus_cell();
1700 this.set_dirty(true);
1699 this.set_dirty(true);
1701 };
1700 };
1702
1701
1703 /**
1702 /**
1704 * Execute all cells below the selected cell.
1703 * Execute all cells below the selected cell.
1705 *
1704 *
1706 * @method execute_cells_below
1705 * @method execute_cells_below
1707 */
1706 */
1708 Notebook.prototype.execute_cells_below = function () {
1707 Notebook.prototype.execute_cells_below = function () {
1709 this.execute_cell_range(this.get_selected_index(), this.ncells());
1708 this.execute_cell_range(this.get_selected_index(), this.ncells());
1710 this.scroll_to_bottom();
1709 this.scroll_to_bottom();
1711 };
1710 };
1712
1711
1713 /**
1712 /**
1714 * Execute all cells above the selected cell.
1713 * Execute all cells above the selected cell.
1715 *
1714 *
1716 * @method execute_cells_above
1715 * @method execute_cells_above
1717 */
1716 */
1718 Notebook.prototype.execute_cells_above = function () {
1717 Notebook.prototype.execute_cells_above = function () {
1719 this.execute_cell_range(0, this.get_selected_index());
1718 this.execute_cell_range(0, this.get_selected_index());
1720 };
1719 };
1721
1720
1722 /**
1721 /**
1723 * Execute all cells.
1722 * Execute all cells.
1724 *
1723 *
1725 * @method execute_all_cells
1724 * @method execute_all_cells
1726 */
1725 */
1727 Notebook.prototype.execute_all_cells = function () {
1726 Notebook.prototype.execute_all_cells = function () {
1728 this.execute_cell_range(0, this.ncells());
1727 this.execute_cell_range(0, this.ncells());
1729 this.scroll_to_bottom();
1728 this.scroll_to_bottom();
1730 };
1729 };
1731
1730
1732 /**
1731 /**
1733 * Execute a contiguous range of cells.
1732 * Execute a contiguous range of cells.
1734 *
1733 *
1735 * @method execute_cell_range
1734 * @method execute_cell_range
1736 * @param {Number} start Index of the first cell to execute (inclusive)
1735 * @param {Number} start Index of the first cell to execute (inclusive)
1737 * @param {Number} end Index of the last cell to execute (exclusive)
1736 * @param {Number} end Index of the last cell to execute (exclusive)
1738 */
1737 */
1739 Notebook.prototype.execute_cell_range = function (start, end) {
1738 Notebook.prototype.execute_cell_range = function (start, end) {
1740 this.command_mode();
1739 this.command_mode();
1741 for (var i=start; i<end; i++) {
1740 for (var i=start; i<end; i++) {
1742 this.select(i);
1741 this.select(i);
1743 this.execute_cell();
1742 this.execute_cell();
1744 }
1743 }
1745 };
1744 };
1746
1745
1747 // Persistance and loading
1746 // Persistance and loading
1748
1747
1749 /**
1748 /**
1750 * Getter method for this notebook's name.
1749 * Getter method for this notebook's name.
1751 *
1750 *
1752 * @method get_notebook_name
1751 * @method get_notebook_name
1753 * @return {String} This notebook's name (excluding file extension)
1752 * @return {String} This notebook's name (excluding file extension)
1754 */
1753 */
1755 Notebook.prototype.get_notebook_name = function () {
1754 Notebook.prototype.get_notebook_name = function () {
1756 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1755 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1757 return nbname;
1756 return nbname;
1758 };
1757 };
1759
1758
1760 /**
1759 /**
1761 * Setter method for this notebook's name.
1760 * Setter method for this notebook's name.
1762 *
1761 *
1763 * @method set_notebook_name
1762 * @method set_notebook_name
1764 * @param {String} name A new name for this notebook
1763 * @param {String} name A new name for this notebook
1765 */
1764 */
1766 Notebook.prototype.set_notebook_name = function (name) {
1765 Notebook.prototype.set_notebook_name = function (name) {
1767 this.notebook_name = name;
1766 this.notebook_name = name;
1768 };
1767 };
1769
1768
1770 /**
1769 /**
1771 * Check that a notebook's name is valid.
1770 * Check that a notebook's name is valid.
1772 *
1771 *
1773 * @method test_notebook_name
1772 * @method test_notebook_name
1774 * @param {String} nbname A name for this notebook
1773 * @param {String} nbname A name for this notebook
1775 * @return {Boolean} True if the name is valid, false if invalid
1774 * @return {Boolean} True if the name is valid, false if invalid
1776 */
1775 */
1777 Notebook.prototype.test_notebook_name = function (nbname) {
1776 Notebook.prototype.test_notebook_name = function (nbname) {
1778 nbname = nbname || '';
1777 nbname = nbname || '';
1779 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1778 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1780 return true;
1779 return true;
1781 } else {
1780 } else {
1782 return false;
1781 return false;
1783 }
1782 }
1784 };
1783 };
1785
1784
1786 /**
1785 /**
1787 * Load a notebook from JSON (.ipynb).
1786 * Load a notebook from JSON (.ipynb).
1788 *
1787 *
1789 * This currently handles one worksheet: others are deleted.
1788 * This currently handles one worksheet: others are deleted.
1790 *
1789 *
1791 * @method fromJSON
1790 * @method fromJSON
1792 * @param {Object} data JSON representation of a notebook
1791 * @param {Object} data JSON representation of a notebook
1793 */
1792 */
1794 Notebook.prototype.fromJSON = function (data) {
1793 Notebook.prototype.fromJSON = function (data) {
1795
1794
1796 var content = data.content;
1795 var content = data.content;
1797 var ncells = this.ncells();
1796 var ncells = this.ncells();
1798 var i;
1797 var i;
1799 for (i=0; i<ncells; i++) {
1798 for (i=0; i<ncells; i++) {
1800 // Always delete cell 0 as they get renumbered as they are deleted.
1799 // Always delete cell 0 as they get renumbered as they are deleted.
1801 this.delete_cell(0);
1800 this.delete_cell(0);
1802 }
1801 }
1803 // Save the metadata and name.
1802 // Save the metadata and name.
1804 this.metadata = content.metadata;
1803 this.metadata = content.metadata;
1805 this.notebook_name = data.name;
1804 this.notebook_name = data.name;
1806 var trusted = true;
1805 var trusted = true;
1807
1806
1808 // Trigger an event changing the kernel spec - this will set the default
1807 // Trigger an event changing the kernel spec - this will set the default
1809 // codemirror mode
1808 // codemirror mode
1810 if (this.metadata.kernelspec !== undefined) {
1809 if (this.metadata.kernelspec !== undefined) {
1811 this.events.trigger('spec_changed.Kernel', this.metadata.kernelspec);
1810 this.events.trigger('spec_changed.Kernel', this.metadata.kernelspec);
1812 }
1811 }
1813
1812
1813 // Set the codemirror mode from language_info metadata
1814 if (this.metadata.language_info !== undefined) {
1815 var langinfo = this.metadata.language_info;
1816 // Mode 'null' should be plain, unhighlighted text.
1817 var cm_mode = langinfo.codemirror_mode || langinfo.language || 'null'
1818 this.set_codemirror_mode(cm_mode);
1819 }
1820
1814 // Only handle 1 worksheet for now.
1821 // Only handle 1 worksheet for now.
1815 var worksheet = content.worksheets[0];
1822 var worksheet = content.worksheets[0];
1816 if (worksheet !== undefined) {
1823 if (worksheet !== undefined) {
1817 if (worksheet.metadata) {
1824 if (worksheet.metadata) {
1818 this.worksheet_metadata = worksheet.metadata;
1825 this.worksheet_metadata = worksheet.metadata;
1819 }
1826 }
1820 var new_cells = worksheet.cells;
1827 var new_cells = worksheet.cells;
1821 ncells = new_cells.length;
1828 ncells = new_cells.length;
1822 var cell_data = null;
1829 var cell_data = null;
1823 var new_cell = null;
1830 var new_cell = null;
1824 for (i=0; i<ncells; i++) {
1831 for (i=0; i<ncells; i++) {
1825 cell_data = new_cells[i];
1832 cell_data = new_cells[i];
1826 // VERSIONHACK: plaintext -> raw
1833 // VERSIONHACK: plaintext -> raw
1827 // handle never-released plaintext name for raw cells
1834 // handle never-released plaintext name for raw cells
1828 if (cell_data.cell_type === 'plaintext'){
1835 if (cell_data.cell_type === 'plaintext'){
1829 cell_data.cell_type = 'raw';
1836 cell_data.cell_type = 'raw';
1830 }
1837 }
1831
1838
1832 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1839 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1833 new_cell.fromJSON(cell_data);
1840 new_cell.fromJSON(cell_data);
1834 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1841 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1835 trusted = false;
1842 trusted = false;
1836 }
1843 }
1837 }
1844 }
1838 }
1845 }
1839 if (trusted !== this.trusted) {
1846 if (trusted !== this.trusted) {
1840 this.trusted = trusted;
1847 this.trusted = trusted;
1841 this.events.trigger("trust_changed.Notebook", trusted);
1848 this.events.trigger("trust_changed.Notebook", trusted);
1842 }
1849 }
1843 if (content.worksheets.length > 1) {
1850 if (content.worksheets.length > 1) {
1844 dialog.modal({
1851 dialog.modal({
1845 notebook: this,
1852 notebook: this,
1846 keyboard_manager: this.keyboard_manager,
1853 keyboard_manager: this.keyboard_manager,
1847 title : "Multiple worksheets",
1854 title : "Multiple worksheets",
1848 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1855 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1849 "but this version of IPython can only handle the first. " +
1856 "but this version of IPython can only handle the first. " +
1850 "If you save this notebook, worksheets after the first will be lost.",
1857 "If you save this notebook, worksheets after the first will be lost.",
1851 buttons : {
1858 buttons : {
1852 OK : {
1859 OK : {
1853 class : "btn-danger"
1860 class : "btn-danger"
1854 }
1861 }
1855 }
1862 }
1856 });
1863 });
1857 }
1864 }
1858 };
1865 };
1859
1866
1860 /**
1867 /**
1861 * Dump this notebook into a JSON-friendly object.
1868 * Dump this notebook into a JSON-friendly object.
1862 *
1869 *
1863 * @method toJSON
1870 * @method toJSON
1864 * @return {Object} A JSON-friendly representation of this notebook.
1871 * @return {Object} A JSON-friendly representation of this notebook.
1865 */
1872 */
1866 Notebook.prototype.toJSON = function () {
1873 Notebook.prototype.toJSON = function () {
1867 var cells = this.get_cells();
1874 var cells = this.get_cells();
1868 var ncells = cells.length;
1875 var ncells = cells.length;
1869 var cell_array = new Array(ncells);
1876 var cell_array = new Array(ncells);
1870 var trusted = true;
1877 var trusted = true;
1871 for (var i=0; i<ncells; i++) {
1878 for (var i=0; i<ncells; i++) {
1872 var cell = cells[i];
1879 var cell = cells[i];
1873 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1880 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1874 trusted = false;
1881 trusted = false;
1875 }
1882 }
1876 cell_array[i] = cell.toJSON();
1883 cell_array[i] = cell.toJSON();
1877 }
1884 }
1878 var data = {
1885 var data = {
1879 // Only handle 1 worksheet for now.
1886 // Only handle 1 worksheet for now.
1880 worksheets : [{
1887 worksheets : [{
1881 cells: cell_array,
1888 cells: cell_array,
1882 metadata: this.worksheet_metadata
1889 metadata: this.worksheet_metadata
1883 }],
1890 }],
1884 metadata : this.metadata
1891 metadata : this.metadata
1885 };
1892 };
1886 if (trusted != this.trusted) {
1893 if (trusted != this.trusted) {
1887 this.trusted = trusted;
1894 this.trusted = trusted;
1888 this.events.trigger("trust_changed.Notebook", trusted);
1895 this.events.trigger("trust_changed.Notebook", trusted);
1889 }
1896 }
1890 return data;
1897 return data;
1891 };
1898 };
1892
1899
1893 /**
1900 /**
1894 * Start an autosave timer, for periodically saving the notebook.
1901 * Start an autosave timer, for periodically saving the notebook.
1895 *
1902 *
1896 * @method set_autosave_interval
1903 * @method set_autosave_interval
1897 * @param {Integer} interval the autosave interval in milliseconds
1904 * @param {Integer} interval the autosave interval in milliseconds
1898 */
1905 */
1899 Notebook.prototype.set_autosave_interval = function (interval) {
1906 Notebook.prototype.set_autosave_interval = function (interval) {
1900 var that = this;
1907 var that = this;
1901 // clear previous interval, so we don't get simultaneous timers
1908 // clear previous interval, so we don't get simultaneous timers
1902 if (this.autosave_timer) {
1909 if (this.autosave_timer) {
1903 clearInterval(this.autosave_timer);
1910 clearInterval(this.autosave_timer);
1904 }
1911 }
1905
1912
1906 this.autosave_interval = this.minimum_autosave_interval = interval;
1913 this.autosave_interval = this.minimum_autosave_interval = interval;
1907 if (interval) {
1914 if (interval) {
1908 this.autosave_timer = setInterval(function() {
1915 this.autosave_timer = setInterval(function() {
1909 if (that.dirty) {
1916 if (that.dirty) {
1910 that.save_notebook();
1917 that.save_notebook();
1911 }
1918 }
1912 }, interval);
1919 }, interval);
1913 this.events.trigger("autosave_enabled.Notebook", interval);
1920 this.events.trigger("autosave_enabled.Notebook", interval);
1914 } else {
1921 } else {
1915 this.autosave_timer = null;
1922 this.autosave_timer = null;
1916 this.events.trigger("autosave_disabled.Notebook");
1923 this.events.trigger("autosave_disabled.Notebook");
1917 }
1924 }
1918 };
1925 };
1919
1926
1920 /**
1927 /**
1921 * Save this notebook on the server. This becomes a notebook instance's
1928 * Save this notebook on the server. This becomes a notebook instance's
1922 * .save_notebook method *after* the entire notebook has been loaded.
1929 * .save_notebook method *after* the entire notebook has been loaded.
1923 *
1930 *
1924 * @method save_notebook
1931 * @method save_notebook
1925 */
1932 */
1926 Notebook.prototype.save_notebook = function (extra_settings) {
1933 Notebook.prototype.save_notebook = function (extra_settings) {
1927 // Create a JSON model to be sent to the server.
1934 // Create a JSON model to be sent to the server.
1928 var model = {};
1935 var model = {};
1929 model.name = this.notebook_name;
1936 model.name = this.notebook_name;
1930 model.path = this.notebook_path;
1937 model.path = this.notebook_path;
1931 model.type = 'notebook';
1938 model.type = 'notebook';
1932 model.format = 'json';
1939 model.format = 'json';
1933 model.content = this.toJSON();
1940 model.content = this.toJSON();
1934 model.content.nbformat = this.nbformat;
1941 model.content.nbformat = this.nbformat;
1935 model.content.nbformat_minor = this.nbformat_minor;
1942 model.content.nbformat_minor = this.nbformat_minor;
1936 // time the ajax call for autosave tuning purposes.
1943 // time the ajax call for autosave tuning purposes.
1937 var start = new Date().getTime();
1944 var start = new Date().getTime();
1938 // We do the call with settings so we can set cache to false.
1945 // We do the call with settings so we can set cache to false.
1939 var settings = {
1946 var settings = {
1940 processData : false,
1947 processData : false,
1941 cache : false,
1948 cache : false,
1942 type : "PUT",
1949 type : "PUT",
1943 data : JSON.stringify(model),
1950 data : JSON.stringify(model),
1944 contentType: 'application/json',
1951 contentType: 'application/json',
1945 dataType : "json",
1952 dataType : "json",
1946 success : $.proxy(this.save_notebook_success, this, start),
1953 success : $.proxy(this.save_notebook_success, this, start),
1947 error : $.proxy(this.save_notebook_error, this)
1954 error : $.proxy(this.save_notebook_error, this)
1948 };
1955 };
1949 if (extra_settings) {
1956 if (extra_settings) {
1950 for (var key in extra_settings) {
1957 for (var key in extra_settings) {
1951 settings[key] = extra_settings[key];
1958 settings[key] = extra_settings[key];
1952 }
1959 }
1953 }
1960 }
1954 this.events.trigger('notebook_saving.Notebook');
1961 this.events.trigger('notebook_saving.Notebook');
1955 var url = utils.url_join_encode(
1962 var url = utils.url_join_encode(
1956 this.base_url,
1963 this.base_url,
1957 'api/contents',
1964 'api/contents',
1958 this.notebook_path,
1965 this.notebook_path,
1959 this.notebook_name
1966 this.notebook_name
1960 );
1967 );
1961 $.ajax(url, settings);
1968 $.ajax(url, settings);
1962 };
1969 };
1963
1970
1964 /**
1971 /**
1965 * Success callback for saving a notebook.
1972 * Success callback for saving a notebook.
1966 *
1973 *
1967 * @method save_notebook_success
1974 * @method save_notebook_success
1968 * @param {Integer} start the time when the save request started
1975 * @param {Integer} start the time when the save request started
1969 * @param {Object} data JSON representation of a notebook
1976 * @param {Object} data JSON representation of a notebook
1970 * @param {String} status Description of response status
1977 * @param {String} status Description of response status
1971 * @param {jqXHR} xhr jQuery Ajax object
1978 * @param {jqXHR} xhr jQuery Ajax object
1972 */
1979 */
1973 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1980 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1974 this.set_dirty(false);
1981 this.set_dirty(false);
1975 if (data.message) {
1982 if (data.message) {
1976 // save succeeded, but validation failed.
1983 // save succeeded, but validation failed.
1977 var body = $("<div>");
1984 var body = $("<div>");
1978 var title = "Notebook validation failed";
1985 var title = "Notebook validation failed";
1979
1986
1980 body.append($("<p>").text(
1987 body.append($("<p>").text(
1981 "The save operation succeeded," +
1988 "The save operation succeeded," +
1982 " but the notebook does not appear to be valid." +
1989 " but the notebook does not appear to be valid." +
1983 " The validation error was:"
1990 " The validation error was:"
1984 )).append($("<div>").addClass("validation-error").append(
1991 )).append($("<div>").addClass("validation-error").append(
1985 $("<pre>").text(data.message)
1992 $("<pre>").text(data.message)
1986 ));
1993 ));
1987 dialog.modal({
1994 dialog.modal({
1988 notebook: this,
1995 notebook: this,
1989 keyboard_manager: this.keyboard_manager,
1996 keyboard_manager: this.keyboard_manager,
1990 title: title,
1997 title: title,
1991 body: body,
1998 body: body,
1992 buttons : {
1999 buttons : {
1993 OK : {
2000 OK : {
1994 "class" : "btn-primary"
2001 "class" : "btn-primary"
1995 }
2002 }
1996 }
2003 }
1997 });
2004 });
1998 }
2005 }
1999 this.events.trigger('notebook_saved.Notebook');
2006 this.events.trigger('notebook_saved.Notebook');
2000 this._update_autosave_interval(start);
2007 this._update_autosave_interval(start);
2001 if (this._checkpoint_after_save) {
2008 if (this._checkpoint_after_save) {
2002 this.create_checkpoint();
2009 this.create_checkpoint();
2003 this._checkpoint_after_save = false;
2010 this._checkpoint_after_save = false;
2004 }
2011 }
2005 };
2012 };
2006
2013
2007 /**
2014 /**
2008 * update the autosave interval based on how long the last save took
2015 * update the autosave interval based on how long the last save took
2009 *
2016 *
2010 * @method _update_autosave_interval
2017 * @method _update_autosave_interval
2011 * @param {Integer} timestamp when the save request started
2018 * @param {Integer} timestamp when the save request started
2012 */
2019 */
2013 Notebook.prototype._update_autosave_interval = function (start) {
2020 Notebook.prototype._update_autosave_interval = function (start) {
2014 var duration = (new Date().getTime() - start);
2021 var duration = (new Date().getTime() - start);
2015 if (this.autosave_interval) {
2022 if (this.autosave_interval) {
2016 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
2023 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
2017 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
2024 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
2018 // round to 10 seconds, otherwise we will be setting a new interval too often
2025 // round to 10 seconds, otherwise we will be setting a new interval too often
2019 interval = 10000 * Math.round(interval / 10000);
2026 interval = 10000 * Math.round(interval / 10000);
2020 // set new interval, if it's changed
2027 // set new interval, if it's changed
2021 if (interval != this.autosave_interval) {
2028 if (interval != this.autosave_interval) {
2022 this.set_autosave_interval(interval);
2029 this.set_autosave_interval(interval);
2023 }
2030 }
2024 }
2031 }
2025 };
2032 };
2026
2033
2027 /**
2034 /**
2028 * Failure callback for saving a notebook.
2035 * Failure callback for saving a notebook.
2029 *
2036 *
2030 * @method save_notebook_error
2037 * @method save_notebook_error
2031 * @param {jqXHR} xhr jQuery Ajax object
2038 * @param {jqXHR} xhr jQuery Ajax object
2032 * @param {String} status Description of response status
2039 * @param {String} status Description of response status
2033 * @param {String} error HTTP error message
2040 * @param {String} error HTTP error message
2034 */
2041 */
2035 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
2042 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
2036 this.events.trigger('notebook_save_failed.Notebook', [xhr, status, error]);
2043 this.events.trigger('notebook_save_failed.Notebook', [xhr, status, error]);
2037 };
2044 };
2038
2045
2039 /**
2046 /**
2040 * Explicitly trust the output of this notebook.
2047 * Explicitly trust the output of this notebook.
2041 *
2048 *
2042 * @method trust_notebook
2049 * @method trust_notebook
2043 */
2050 */
2044 Notebook.prototype.trust_notebook = function (extra_settings) {
2051 Notebook.prototype.trust_notebook = function (extra_settings) {
2045 var body = $("<div>").append($("<p>")
2052 var body = $("<div>").append($("<p>")
2046 .text("A trusted IPython notebook may execute hidden malicious code ")
2053 .text("A trusted IPython notebook may execute hidden malicious code ")
2047 .append($("<strong>")
2054 .append($("<strong>")
2048 .append(
2055 .append(
2049 $("<em>").text("when you open it")
2056 $("<em>").text("when you open it")
2050 )
2057 )
2051 ).append(".").append(
2058 ).append(".").append(
2052 " Selecting trust will immediately reload this notebook in a trusted state."
2059 " Selecting trust will immediately reload this notebook in a trusted state."
2053 ).append(
2060 ).append(
2054 " For more information, see the "
2061 " For more information, see the "
2055 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
2062 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
2056 .text("IPython security documentation")
2063 .text("IPython security documentation")
2057 ).append(".")
2064 ).append(".")
2058 );
2065 );
2059
2066
2060 var nb = this;
2067 var nb = this;
2061 dialog.modal({
2068 dialog.modal({
2062 notebook: this,
2069 notebook: this,
2063 keyboard_manager: this.keyboard_manager,
2070 keyboard_manager: this.keyboard_manager,
2064 title: "Trust this notebook?",
2071 title: "Trust this notebook?",
2065 body: body,
2072 body: body,
2066
2073
2067 buttons: {
2074 buttons: {
2068 Cancel : {},
2075 Cancel : {},
2069 Trust : {
2076 Trust : {
2070 class : "btn-danger",
2077 class : "btn-danger",
2071 click : function () {
2078 click : function () {
2072 var cells = nb.get_cells();
2079 var cells = nb.get_cells();
2073 for (var i = 0; i < cells.length; i++) {
2080 for (var i = 0; i < cells.length; i++) {
2074 var cell = cells[i];
2081 var cell = cells[i];
2075 if (cell.cell_type == 'code') {
2082 if (cell.cell_type == 'code') {
2076 cell.output_area.trusted = true;
2083 cell.output_area.trusted = true;
2077 }
2084 }
2078 }
2085 }
2079 nb.events.on('notebook_saved.Notebook', function () {
2086 nb.events.on('notebook_saved.Notebook', function () {
2080 window.location.reload();
2087 window.location.reload();
2081 });
2088 });
2082 nb.save_notebook();
2089 nb.save_notebook();
2083 }
2090 }
2084 }
2091 }
2085 }
2092 }
2086 });
2093 });
2087 };
2094 };
2088
2095
2089 Notebook.prototype.new_notebook = function(){
2096 Notebook.prototype.new_notebook = function(){
2090 var path = this.notebook_path;
2097 var path = this.notebook_path;
2091 var base_url = this.base_url;
2098 var base_url = this.base_url;
2092 var settings = {
2099 var settings = {
2093 processData : false,
2100 processData : false,
2094 cache : false,
2101 cache : false,
2095 type : "POST",
2102 type : "POST",
2096 dataType : "json",
2103 dataType : "json",
2097 async : false,
2104 async : false,
2098 success : function (data, status, xhr){
2105 success : function (data, status, xhr){
2099 var notebook_name = data.name;
2106 var notebook_name = data.name;
2100 window.open(
2107 window.open(
2101 utils.url_join_encode(
2108 utils.url_join_encode(
2102 base_url,
2109 base_url,
2103 'notebooks',
2110 'notebooks',
2104 path,
2111 path,
2105 notebook_name
2112 notebook_name
2106 ),
2113 ),
2107 '_blank'
2114 '_blank'
2108 );
2115 );
2109 },
2116 },
2110 error : utils.log_ajax_error,
2117 error : utils.log_ajax_error,
2111 };
2118 };
2112 var url = utils.url_join_encode(
2119 var url = utils.url_join_encode(
2113 base_url,
2120 base_url,
2114 'api/contents',
2121 'api/contents',
2115 path
2122 path
2116 );
2123 );
2117 $.ajax(url,settings);
2124 $.ajax(url,settings);
2118 };
2125 };
2119
2126
2120
2127
2121 Notebook.prototype.copy_notebook = function(){
2128 Notebook.prototype.copy_notebook = function(){
2122 var path = this.notebook_path;
2129 var path = this.notebook_path;
2123 var base_url = this.base_url;
2130 var base_url = this.base_url;
2124 var settings = {
2131 var settings = {
2125 processData : false,
2132 processData : false,
2126 cache : false,
2133 cache : false,
2127 type : "POST",
2134 type : "POST",
2128 dataType : "json",
2135 dataType : "json",
2129 data : JSON.stringify({copy_from : this.notebook_name}),
2136 data : JSON.stringify({copy_from : this.notebook_name}),
2130 async : false,
2137 async : false,
2131 success : function (data, status, xhr) {
2138 success : function (data, status, xhr) {
2132 window.open(utils.url_join_encode(
2139 window.open(utils.url_join_encode(
2133 base_url,
2140 base_url,
2134 'notebooks',
2141 'notebooks',
2135 data.path,
2142 data.path,
2136 data.name
2143 data.name
2137 ), '_blank');
2144 ), '_blank');
2138 },
2145 },
2139 error : utils.log_ajax_error,
2146 error : utils.log_ajax_error,
2140 };
2147 };
2141 var url = utils.url_join_encode(
2148 var url = utils.url_join_encode(
2142 base_url,
2149 base_url,
2143 'api/contents',
2150 'api/contents',
2144 path
2151 path
2145 );
2152 );
2146 $.ajax(url,settings);
2153 $.ajax(url,settings);
2147 };
2154 };
2148
2155
2149 Notebook.prototype.rename = function (nbname) {
2156 Notebook.prototype.rename = function (nbname) {
2150 var that = this;
2157 var that = this;
2151 if (!nbname.match(/\.ipynb$/)) {
2158 if (!nbname.match(/\.ipynb$/)) {
2152 nbname = nbname + ".ipynb";
2159 nbname = nbname + ".ipynb";
2153 }
2160 }
2154 var data = {name: nbname};
2161 var data = {name: nbname};
2155 var settings = {
2162 var settings = {
2156 processData : false,
2163 processData : false,
2157 cache : false,
2164 cache : false,
2158 type : "PATCH",
2165 type : "PATCH",
2159 data : JSON.stringify(data),
2166 data : JSON.stringify(data),
2160 dataType: "json",
2167 dataType: "json",
2161 contentType: 'application/json',
2168 contentType: 'application/json',
2162 success : $.proxy(that.rename_success, this),
2169 success : $.proxy(that.rename_success, this),
2163 error : $.proxy(that.rename_error, this)
2170 error : $.proxy(that.rename_error, this)
2164 };
2171 };
2165 this.events.trigger('rename_notebook.Notebook', data);
2172 this.events.trigger('rename_notebook.Notebook', data);
2166 var url = utils.url_join_encode(
2173 var url = utils.url_join_encode(
2167 this.base_url,
2174 this.base_url,
2168 'api/contents',
2175 'api/contents',
2169 this.notebook_path,
2176 this.notebook_path,
2170 this.notebook_name
2177 this.notebook_name
2171 );
2178 );
2172 $.ajax(url, settings);
2179 $.ajax(url, settings);
2173 };
2180 };
2174
2181
2175 Notebook.prototype.delete = function () {
2182 Notebook.prototype.delete = function () {
2176 var that = this;
2183 var that = this;
2177 var settings = {
2184 var settings = {
2178 processData : false,
2185 processData : false,
2179 cache : false,
2186 cache : false,
2180 type : "DELETE",
2187 type : "DELETE",
2181 dataType: "json",
2188 dataType: "json",
2182 error : utils.log_ajax_error,
2189 error : utils.log_ajax_error,
2183 };
2190 };
2184 var url = utils.url_join_encode(
2191 var url = utils.url_join_encode(
2185 this.base_url,
2192 this.base_url,
2186 'api/contents',
2193 'api/contents',
2187 this.notebook_path,
2194 this.notebook_path,
2188 this.notebook_name
2195 this.notebook_name
2189 );
2196 );
2190 $.ajax(url, settings);
2197 $.ajax(url, settings);
2191 };
2198 };
2192
2199
2193
2200
2194 Notebook.prototype.rename_success = function (json, status, xhr) {
2201 Notebook.prototype.rename_success = function (json, status, xhr) {
2195 var name = this.notebook_name = json.name;
2202 var name = this.notebook_name = json.name;
2196 var path = json.path;
2203 var path = json.path;
2197 this.session.rename_notebook(name, path);
2204 this.session.rename_notebook(name, path);
2198 this.events.trigger('notebook_renamed.Notebook', json);
2205 this.events.trigger('notebook_renamed.Notebook', json);
2199 };
2206 };
2200
2207
2201 Notebook.prototype.rename_error = function (xhr, status, error) {
2208 Notebook.prototype.rename_error = function (xhr, status, error) {
2202 var that = this;
2209 var that = this;
2203 var dialog_body = $('<div/>').append(
2210 var dialog_body = $('<div/>').append(
2204 $("<p/>").text('This notebook name already exists.')
2211 $("<p/>").text('This notebook name already exists.')
2205 );
2212 );
2206 this.events.trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
2213 this.events.trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
2207 dialog.modal({
2214 dialog.modal({
2208 notebook: this,
2215 notebook: this,
2209 keyboard_manager: this.keyboard_manager,
2216 keyboard_manager: this.keyboard_manager,
2210 title: "Notebook Rename Error!",
2217 title: "Notebook Rename Error!",
2211 body: dialog_body,
2218 body: dialog_body,
2212 buttons : {
2219 buttons : {
2213 "Cancel": {},
2220 "Cancel": {},
2214 "OK": {
2221 "OK": {
2215 class: "btn-primary",
2222 class: "btn-primary",
2216 click: function () {
2223 click: function () {
2217 this.save_widget.rename_notebook({notebook:that});
2224 this.save_widget.rename_notebook({notebook:that});
2218 }}
2225 }}
2219 },
2226 },
2220 open : function (event, ui) {
2227 open : function (event, ui) {
2221 var that = $(this);
2228 var that = $(this);
2222 // Upon ENTER, click the OK button.
2229 // Upon ENTER, click the OK button.
2223 that.find('input[type="text"]').keydown(function (event, ui) {
2230 that.find('input[type="text"]').keydown(function (event, ui) {
2224 if (event.which === this.keyboard.keycodes.enter) {
2231 if (event.which === this.keyboard.keycodes.enter) {
2225 that.find('.btn-primary').first().click();
2232 that.find('.btn-primary').first().click();
2226 }
2233 }
2227 });
2234 });
2228 that.find('input[type="text"]').focus();
2235 that.find('input[type="text"]').focus();
2229 }
2236 }
2230 });
2237 });
2231 };
2238 };
2232
2239
2233 /**
2240 /**
2234 * Request a notebook's data from the server.
2241 * Request a notebook's data from the server.
2235 *
2242 *
2236 * @method load_notebook
2243 * @method load_notebook
2237 * @param {String} notebook_name and path A notebook to load
2244 * @param {String} notebook_name and path A notebook to load
2238 */
2245 */
2239 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
2246 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
2240 var that = this;
2247 var that = this;
2241 this.notebook_name = notebook_name;
2248 this.notebook_name = notebook_name;
2242 this.notebook_path = notebook_path;
2249 this.notebook_path = notebook_path;
2243 // We do the call with settings so we can set cache to false.
2250 // We do the call with settings so we can set cache to false.
2244 var settings = {
2251 var settings = {
2245 processData : false,
2252 processData : false,
2246 cache : false,
2253 cache : false,
2247 type : "GET",
2254 type : "GET",
2248 dataType : "json",
2255 dataType : "json",
2249 success : $.proxy(this.load_notebook_success,this),
2256 success : $.proxy(this.load_notebook_success,this),
2250 error : $.proxy(this.load_notebook_error,this),
2257 error : $.proxy(this.load_notebook_error,this),
2251 };
2258 };
2252 this.events.trigger('notebook_loading.Notebook');
2259 this.events.trigger('notebook_loading.Notebook');
2253 var url = utils.url_join_encode(
2260 var url = utils.url_join_encode(
2254 this.base_url,
2261 this.base_url,
2255 'api/contents',
2262 'api/contents',
2256 this.notebook_path,
2263 this.notebook_path,
2257 this.notebook_name
2264 this.notebook_name
2258 );
2265 );
2259 $.ajax(url, settings);
2266 $.ajax(url, settings);
2260 };
2267 };
2261
2268
2262 /**
2269 /**
2263 * Success callback for loading a notebook from the server.
2270 * Success callback for loading a notebook from the server.
2264 *
2271 *
2265 * Load notebook data from the JSON response.
2272 * Load notebook data from the JSON response.
2266 *
2273 *
2267 * @method load_notebook_success
2274 * @method load_notebook_success
2268 * @param {Object} data JSON representation of a notebook
2275 * @param {Object} data JSON representation of a notebook
2269 * @param {String} status Description of response status
2276 * @param {String} status Description of response status
2270 * @param {jqXHR} xhr jQuery Ajax object
2277 * @param {jqXHR} xhr jQuery Ajax object
2271 */
2278 */
2272 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
2279 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
2273 var failed;
2280 var failed;
2274 try {
2281 try {
2275 this.fromJSON(data);
2282 this.fromJSON(data);
2276 } catch (e) {
2283 } catch (e) {
2277 failed = e;
2284 failed = e;
2278 console.log("Notebook failed to load from JSON:", e);
2285 console.log("Notebook failed to load from JSON:", e);
2279 }
2286 }
2280 if (failed || data.message) {
2287 if (failed || data.message) {
2281 // *either* fromJSON failed or validation failed
2288 // *either* fromJSON failed or validation failed
2282 var body = $("<div>");
2289 var body = $("<div>");
2283 var title;
2290 var title;
2284 if (failed) {
2291 if (failed) {
2285 title = "Notebook failed to load";
2292 title = "Notebook failed to load";
2286 body.append($("<p>").text(
2293 body.append($("<p>").text(
2287 "The error was: "
2294 "The error was: "
2288 )).append($("<div>").addClass("js-error").text(
2295 )).append($("<div>").addClass("js-error").text(
2289 failed.toString()
2296 failed.toString()
2290 )).append($("<p>").text(
2297 )).append($("<p>").text(
2291 "See the error console for details."
2298 "See the error console for details."
2292 ));
2299 ));
2293 } else {
2300 } else {
2294 title = "Notebook validation failed";
2301 title = "Notebook validation failed";
2295 }
2302 }
2296
2303
2297 if (data.message) {
2304 if (data.message) {
2298 var msg;
2305 var msg;
2299 if (failed) {
2306 if (failed) {
2300 msg = "The notebook also failed validation:"
2307 msg = "The notebook also failed validation:"
2301 } else {
2308 } else {
2302 msg = "An invalid notebook may not function properly." +
2309 msg = "An invalid notebook may not function properly." +
2303 " The validation error was:"
2310 " The validation error was:"
2304 }
2311 }
2305 body.append($("<p>").text(
2312 body.append($("<p>").text(
2306 msg
2313 msg
2307 )).append($("<div>").addClass("validation-error").append(
2314 )).append($("<div>").addClass("validation-error").append(
2308 $("<pre>").text(data.message)
2315 $("<pre>").text(data.message)
2309 ));
2316 ));
2310 }
2317 }
2311
2318
2312 dialog.modal({
2319 dialog.modal({
2313 notebook: this,
2320 notebook: this,
2314 keyboard_manager: this.keyboard_manager,
2321 keyboard_manager: this.keyboard_manager,
2315 title: title,
2322 title: title,
2316 body: body,
2323 body: body,
2317 buttons : {
2324 buttons : {
2318 OK : {
2325 OK : {
2319 "class" : "btn-primary"
2326 "class" : "btn-primary"
2320 }
2327 }
2321 }
2328 }
2322 });
2329 });
2323 }
2330 }
2324 if (this.ncells() === 0) {
2331 if (this.ncells() === 0) {
2325 this.insert_cell_below('code');
2332 this.insert_cell_below('code');
2326 this.edit_mode(0);
2333 this.edit_mode(0);
2327 } else {
2334 } else {
2328 this.select(0);
2335 this.select(0);
2329 this.handle_command_mode(this.get_cell(0));
2336 this.handle_command_mode(this.get_cell(0));
2330 }
2337 }
2331 this.set_dirty(false);
2338 this.set_dirty(false);
2332 this.scroll_to_top();
2339 this.scroll_to_top();
2333 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
2340 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
2334 var msg = "This notebook has been converted from an older " +
2341 var msg = "This notebook has been converted from an older " +
2335 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
2342 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
2336 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
2343 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
2337 "newer notebook format will be used and older versions of IPython " +
2344 "newer notebook format will be used and older versions of IPython " +
2338 "may not be able to read it. To keep the older version, close the " +
2345 "may not be able to read it. To keep the older version, close the " +
2339 "notebook without saving it.";
2346 "notebook without saving it.";
2340 dialog.modal({
2347 dialog.modal({
2341 notebook: this,
2348 notebook: this,
2342 keyboard_manager: this.keyboard_manager,
2349 keyboard_manager: this.keyboard_manager,
2343 title : "Notebook converted",
2350 title : "Notebook converted",
2344 body : msg,
2351 body : msg,
2345 buttons : {
2352 buttons : {
2346 OK : {
2353 OK : {
2347 class : "btn-primary"
2354 class : "btn-primary"
2348 }
2355 }
2349 }
2356 }
2350 });
2357 });
2351 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
2358 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
2352 var that = this;
2359 var that = this;
2353 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
2360 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
2354 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
2361 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
2355 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
2362 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
2356 this_vs + ". You can still work with this notebook, but some features " +
2363 this_vs + ". You can still work with this notebook, but some features " +
2357 "introduced in later notebook versions may not be available.";
2364 "introduced in later notebook versions may not be available.";
2358
2365
2359 dialog.modal({
2366 dialog.modal({
2360 notebook: this,
2367 notebook: this,
2361 keyboard_manager: this.keyboard_manager,
2368 keyboard_manager: this.keyboard_manager,
2362 title : "Newer Notebook",
2369 title : "Newer Notebook",
2363 body : msg,
2370 body : msg,
2364 buttons : {
2371 buttons : {
2365 OK : {
2372 OK : {
2366 class : "btn-danger"
2373 class : "btn-danger"
2367 }
2374 }
2368 }
2375 }
2369 });
2376 });
2370
2377
2371 }
2378 }
2372
2379
2373 // Create the session after the notebook is completely loaded to prevent
2380 // Create the session after the notebook is completely loaded to prevent
2374 // code execution upon loading, which is a security risk.
2381 // code execution upon loading, which is a security risk.
2375 if (this.session === null) {
2382 if (this.session === null) {
2376 var kernelspec = this.metadata.kernelspec || {};
2383 var kernelspec = this.metadata.kernelspec || {};
2377 var kernel_name = kernelspec.name;
2384 var kernel_name = kernelspec.name;
2378
2385
2379 this.start_session(kernel_name);
2386 this.start_session(kernel_name);
2380 }
2387 }
2381 // load our checkpoint list
2388 // load our checkpoint list
2382 this.list_checkpoints();
2389 this.list_checkpoints();
2383
2390
2384 // load toolbar state
2391 // load toolbar state
2385 if (this.metadata.celltoolbar) {
2392 if (this.metadata.celltoolbar) {
2386 celltoolbar.CellToolbar.global_show();
2393 celltoolbar.CellToolbar.global_show();
2387 celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar);
2394 celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar);
2388 } else {
2395 } else {
2389 celltoolbar.CellToolbar.global_hide();
2396 celltoolbar.CellToolbar.global_hide();
2390 }
2397 }
2391
2398
2392 // now that we're fully loaded, it is safe to restore save functionality
2399 // now that we're fully loaded, it is safe to restore save functionality
2393 delete(this.save_notebook);
2400 delete(this.save_notebook);
2394 this.events.trigger('notebook_loaded.Notebook');
2401 this.events.trigger('notebook_loaded.Notebook');
2395 };
2402 };
2396
2403
2397 /**
2404 /**
2398 * Failure callback for loading a notebook from the server.
2405 * Failure callback for loading a notebook from the server.
2399 *
2406 *
2400 * @method load_notebook_error
2407 * @method load_notebook_error
2401 * @param {jqXHR} xhr jQuery Ajax object
2408 * @param {jqXHR} xhr jQuery Ajax object
2402 * @param {String} status Description of response status
2409 * @param {String} status Description of response status
2403 * @param {String} error HTTP error message
2410 * @param {String} error HTTP error message
2404 */
2411 */
2405 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2412 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2406 this.events.trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2413 this.events.trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2407 utils.log_ajax_error(xhr, status, error);
2414 utils.log_ajax_error(xhr, status, error);
2408 var msg;
2415 var msg;
2409 if (xhr.status === 400) {
2416 if (xhr.status === 400) {
2410 msg = escape(utils.ajax_error_msg(xhr));
2417 msg = escape(utils.ajax_error_msg(xhr));
2411 } else if (xhr.status === 500) {
2418 } else if (xhr.status === 500) {
2412 msg = "An unknown error occurred while loading this notebook. " +
2419 msg = "An unknown error occurred while loading this notebook. " +
2413 "This version can load notebook formats " +
2420 "This version can load notebook formats " +
2414 "v" + this.nbformat + " or earlier. See the server log for details.";
2421 "v" + this.nbformat + " or earlier. See the server log for details.";
2415 }
2422 }
2416 dialog.modal({
2423 dialog.modal({
2417 notebook: this,
2424 notebook: this,
2418 keyboard_manager: this.keyboard_manager,
2425 keyboard_manager: this.keyboard_manager,
2419 title: "Error loading notebook",
2426 title: "Error loading notebook",
2420 body : msg,
2427 body : msg,
2421 buttons : {
2428 buttons : {
2422 "OK": {}
2429 "OK": {}
2423 }
2430 }
2424 });
2431 });
2425 };
2432 };
2426
2433
2427 /********************* checkpoint-related *********************/
2434 /********************* checkpoint-related *********************/
2428
2435
2429 /**
2436 /**
2430 * Save the notebook then immediately create a checkpoint.
2437 * Save the notebook then immediately create a checkpoint.
2431 *
2438 *
2432 * @method save_checkpoint
2439 * @method save_checkpoint
2433 */
2440 */
2434 Notebook.prototype.save_checkpoint = function () {
2441 Notebook.prototype.save_checkpoint = function () {
2435 this._checkpoint_after_save = true;
2442 this._checkpoint_after_save = true;
2436 this.save_notebook();
2443 this.save_notebook();
2437 };
2444 };
2438
2445
2439 /**
2446 /**
2440 * Add a checkpoint for this notebook.
2447 * Add a checkpoint for this notebook.
2441 * for use as a callback from checkpoint creation.
2448 * for use as a callback from checkpoint creation.
2442 *
2449 *
2443 * @method add_checkpoint
2450 * @method add_checkpoint
2444 */
2451 */
2445 Notebook.prototype.add_checkpoint = function (checkpoint) {
2452 Notebook.prototype.add_checkpoint = function (checkpoint) {
2446 var found = false;
2453 var found = false;
2447 for (var i = 0; i < this.checkpoints.length; i++) {
2454 for (var i = 0; i < this.checkpoints.length; i++) {
2448 var existing = this.checkpoints[i];
2455 var existing = this.checkpoints[i];
2449 if (existing.id == checkpoint.id) {
2456 if (existing.id == checkpoint.id) {
2450 found = true;
2457 found = true;
2451 this.checkpoints[i] = checkpoint;
2458 this.checkpoints[i] = checkpoint;
2452 break;
2459 break;
2453 }
2460 }
2454 }
2461 }
2455 if (!found) {
2462 if (!found) {
2456 this.checkpoints.push(checkpoint);
2463 this.checkpoints.push(checkpoint);
2457 }
2464 }
2458 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2465 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2459 };
2466 };
2460
2467
2461 /**
2468 /**
2462 * List checkpoints for this notebook.
2469 * List checkpoints for this notebook.
2463 *
2470 *
2464 * @method list_checkpoints
2471 * @method list_checkpoints
2465 */
2472 */
2466 Notebook.prototype.list_checkpoints = function () {
2473 Notebook.prototype.list_checkpoints = function () {
2467 var url = utils.url_join_encode(
2474 var url = utils.url_join_encode(
2468 this.base_url,
2475 this.base_url,
2469 'api/contents',
2476 'api/contents',
2470 this.notebook_path,
2477 this.notebook_path,
2471 this.notebook_name,
2478 this.notebook_name,
2472 'checkpoints'
2479 'checkpoints'
2473 );
2480 );
2474 $.get(url).done(
2481 $.get(url).done(
2475 $.proxy(this.list_checkpoints_success, this)
2482 $.proxy(this.list_checkpoints_success, this)
2476 ).fail(
2483 ).fail(
2477 $.proxy(this.list_checkpoints_error, this)
2484 $.proxy(this.list_checkpoints_error, this)
2478 );
2485 );
2479 };
2486 };
2480
2487
2481 /**
2488 /**
2482 * Success callback for listing checkpoints.
2489 * Success callback for listing checkpoints.
2483 *
2490 *
2484 * @method list_checkpoint_success
2491 * @method list_checkpoint_success
2485 * @param {Object} data JSON representation of a checkpoint
2492 * @param {Object} data JSON representation of a checkpoint
2486 * @param {String} status Description of response status
2493 * @param {String} status Description of response status
2487 * @param {jqXHR} xhr jQuery Ajax object
2494 * @param {jqXHR} xhr jQuery Ajax object
2488 */
2495 */
2489 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2496 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2490 data = $.parseJSON(data);
2497 data = $.parseJSON(data);
2491 this.checkpoints = data;
2498 this.checkpoints = data;
2492 if (data.length) {
2499 if (data.length) {
2493 this.last_checkpoint = data[data.length - 1];
2500 this.last_checkpoint = data[data.length - 1];
2494 } else {
2501 } else {
2495 this.last_checkpoint = null;
2502 this.last_checkpoint = null;
2496 }
2503 }
2497 this.events.trigger('checkpoints_listed.Notebook', [data]);
2504 this.events.trigger('checkpoints_listed.Notebook', [data]);
2498 };
2505 };
2499
2506
2500 /**
2507 /**
2501 * Failure callback for listing a checkpoint.
2508 * Failure callback for listing a checkpoint.
2502 *
2509 *
2503 * @method list_checkpoint_error
2510 * @method list_checkpoint_error
2504 * @param {jqXHR} xhr jQuery Ajax object
2511 * @param {jqXHR} xhr jQuery Ajax object
2505 * @param {String} status Description of response status
2512 * @param {String} status Description of response status
2506 * @param {String} error_msg HTTP error message
2513 * @param {String} error_msg HTTP error message
2507 */
2514 */
2508 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2515 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2509 this.events.trigger('list_checkpoints_failed.Notebook');
2516 this.events.trigger('list_checkpoints_failed.Notebook');
2510 };
2517 };
2511
2518
2512 /**
2519 /**
2513 * Create a checkpoint of this notebook on the server from the most recent save.
2520 * Create a checkpoint of this notebook on the server from the most recent save.
2514 *
2521 *
2515 * @method create_checkpoint
2522 * @method create_checkpoint
2516 */
2523 */
2517 Notebook.prototype.create_checkpoint = function () {
2524 Notebook.prototype.create_checkpoint = function () {
2518 var url = utils.url_join_encode(
2525 var url = utils.url_join_encode(
2519 this.base_url,
2526 this.base_url,
2520 'api/contents',
2527 'api/contents',
2521 this.notebook_path,
2528 this.notebook_path,
2522 this.notebook_name,
2529 this.notebook_name,
2523 'checkpoints'
2530 'checkpoints'
2524 );
2531 );
2525 $.post(url).done(
2532 $.post(url).done(
2526 $.proxy(this.create_checkpoint_success, this)
2533 $.proxy(this.create_checkpoint_success, this)
2527 ).fail(
2534 ).fail(
2528 $.proxy(this.create_checkpoint_error, this)
2535 $.proxy(this.create_checkpoint_error, this)
2529 );
2536 );
2530 };
2537 };
2531
2538
2532 /**
2539 /**
2533 * Success callback for creating a checkpoint.
2540 * Success callback for creating a checkpoint.
2534 *
2541 *
2535 * @method create_checkpoint_success
2542 * @method create_checkpoint_success
2536 * @param {Object} data JSON representation of a checkpoint
2543 * @param {Object} data JSON representation of a checkpoint
2537 * @param {String} status Description of response status
2544 * @param {String} status Description of response status
2538 * @param {jqXHR} xhr jQuery Ajax object
2545 * @param {jqXHR} xhr jQuery Ajax object
2539 */
2546 */
2540 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2547 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2541 data = $.parseJSON(data);
2548 data = $.parseJSON(data);
2542 this.add_checkpoint(data);
2549 this.add_checkpoint(data);
2543 this.events.trigger('checkpoint_created.Notebook', data);
2550 this.events.trigger('checkpoint_created.Notebook', data);
2544 };
2551 };
2545
2552
2546 /**
2553 /**
2547 * Failure callback for creating a checkpoint.
2554 * Failure callback for creating a checkpoint.
2548 *
2555 *
2549 * @method create_checkpoint_error
2556 * @method create_checkpoint_error
2550 * @param {jqXHR} xhr jQuery Ajax object
2557 * @param {jqXHR} xhr jQuery Ajax object
2551 * @param {String} status Description of response status
2558 * @param {String} status Description of response status
2552 * @param {String} error_msg HTTP error message
2559 * @param {String} error_msg HTTP error message
2553 */
2560 */
2554 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2561 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2555 this.events.trigger('checkpoint_failed.Notebook');
2562 this.events.trigger('checkpoint_failed.Notebook');
2556 };
2563 };
2557
2564
2558 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2565 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2559 var that = this;
2566 var that = this;
2560 checkpoint = checkpoint || this.last_checkpoint;
2567 checkpoint = checkpoint || this.last_checkpoint;
2561 if ( ! checkpoint ) {
2568 if ( ! checkpoint ) {
2562 console.log("restore dialog, but no checkpoint to restore to!");
2569 console.log("restore dialog, but no checkpoint to restore to!");
2563 return;
2570 return;
2564 }
2571 }
2565 var body = $('<div/>').append(
2572 var body = $('<div/>').append(
2566 $('<p/>').addClass("p-space").text(
2573 $('<p/>').addClass("p-space").text(
2567 "Are you sure you want to revert the notebook to " +
2574 "Are you sure you want to revert the notebook to " +
2568 "the latest checkpoint?"
2575 "the latest checkpoint?"
2569 ).append(
2576 ).append(
2570 $("<strong/>").text(
2577 $("<strong/>").text(
2571 " This cannot be undone."
2578 " This cannot be undone."
2572 )
2579 )
2573 )
2580 )
2574 ).append(
2581 ).append(
2575 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2582 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2576 ).append(
2583 ).append(
2577 $('<p/>').addClass("p-space").text(
2584 $('<p/>').addClass("p-space").text(
2578 Date(checkpoint.last_modified)
2585 Date(checkpoint.last_modified)
2579 ).css("text-align", "center")
2586 ).css("text-align", "center")
2580 );
2587 );
2581
2588
2582 dialog.modal({
2589 dialog.modal({
2583 notebook: this,
2590 notebook: this,
2584 keyboard_manager: this.keyboard_manager,
2591 keyboard_manager: this.keyboard_manager,
2585 title : "Revert notebook to checkpoint",
2592 title : "Revert notebook to checkpoint",
2586 body : body,
2593 body : body,
2587 buttons : {
2594 buttons : {
2588 Revert : {
2595 Revert : {
2589 class : "btn-danger",
2596 class : "btn-danger",
2590 click : function () {
2597 click : function () {
2591 that.restore_checkpoint(checkpoint.id);
2598 that.restore_checkpoint(checkpoint.id);
2592 }
2599 }
2593 },
2600 },
2594 Cancel : {}
2601 Cancel : {}
2595 }
2602 }
2596 });
2603 });
2597 };
2604 };
2598
2605
2599 /**
2606 /**
2600 * Restore the notebook to a checkpoint state.
2607 * Restore the notebook to a checkpoint state.
2601 *
2608 *
2602 * @method restore_checkpoint
2609 * @method restore_checkpoint
2603 * @param {String} checkpoint ID
2610 * @param {String} checkpoint ID
2604 */
2611 */
2605 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2612 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2606 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2613 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2607 var url = utils.url_join_encode(
2614 var url = utils.url_join_encode(
2608 this.base_url,
2615 this.base_url,
2609 'api/contents',
2616 'api/contents',
2610 this.notebook_path,
2617 this.notebook_path,
2611 this.notebook_name,
2618 this.notebook_name,
2612 'checkpoints',
2619 'checkpoints',
2613 checkpoint
2620 checkpoint
2614 );
2621 );
2615 $.post(url).done(
2622 $.post(url).done(
2616 $.proxy(this.restore_checkpoint_success, this)
2623 $.proxy(this.restore_checkpoint_success, this)
2617 ).fail(
2624 ).fail(
2618 $.proxy(this.restore_checkpoint_error, this)
2625 $.proxy(this.restore_checkpoint_error, this)
2619 );
2626 );
2620 };
2627 };
2621
2628
2622 /**
2629 /**
2623 * Success callback for restoring a notebook to a checkpoint.
2630 * Success callback for restoring a notebook to a checkpoint.
2624 *
2631 *
2625 * @method restore_checkpoint_success
2632 * @method restore_checkpoint_success
2626 * @param {Object} data (ignored, should be empty)
2633 * @param {Object} data (ignored, should be empty)
2627 * @param {String} status Description of response status
2634 * @param {String} status Description of response status
2628 * @param {jqXHR} xhr jQuery Ajax object
2635 * @param {jqXHR} xhr jQuery Ajax object
2629 */
2636 */
2630 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2637 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2631 this.events.trigger('checkpoint_restored.Notebook');
2638 this.events.trigger('checkpoint_restored.Notebook');
2632 this.load_notebook(this.notebook_name, this.notebook_path);
2639 this.load_notebook(this.notebook_name, this.notebook_path);
2633 };
2640 };
2634
2641
2635 /**
2642 /**
2636 * Failure callback for restoring a notebook to a checkpoint.
2643 * Failure callback for restoring a notebook to a checkpoint.
2637 *
2644 *
2638 * @method restore_checkpoint_error
2645 * @method restore_checkpoint_error
2639 * @param {jqXHR} xhr jQuery Ajax object
2646 * @param {jqXHR} xhr jQuery Ajax object
2640 * @param {String} status Description of response status
2647 * @param {String} status Description of response status
2641 * @param {String} error_msg HTTP error message
2648 * @param {String} error_msg HTTP error message
2642 */
2649 */
2643 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2650 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2644 this.events.trigger('checkpoint_restore_failed.Notebook');
2651 this.events.trigger('checkpoint_restore_failed.Notebook');
2645 };
2652 };
2646
2653
2647 /**
2654 /**
2648 * Delete a notebook checkpoint.
2655 * Delete a notebook checkpoint.
2649 *
2656 *
2650 * @method delete_checkpoint
2657 * @method delete_checkpoint
2651 * @param {String} checkpoint ID
2658 * @param {String} checkpoint ID
2652 */
2659 */
2653 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2660 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2654 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2661 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2655 var url = utils.url_join_encode(
2662 var url = utils.url_join_encode(
2656 this.base_url,
2663 this.base_url,
2657 'api/contents',
2664 'api/contents',
2658 this.notebook_path,
2665 this.notebook_path,
2659 this.notebook_name,
2666 this.notebook_name,
2660 'checkpoints',
2667 'checkpoints',
2661 checkpoint
2668 checkpoint
2662 );
2669 );
2663 $.ajax(url, {
2670 $.ajax(url, {
2664 type: 'DELETE',
2671 type: 'DELETE',
2665 success: $.proxy(this.delete_checkpoint_success, this),
2672 success: $.proxy(this.delete_checkpoint_success, this),
2666 error: $.proxy(this.delete_checkpoint_error, this)
2673 error: $.proxy(this.delete_checkpoint_error, this)
2667 });
2674 });
2668 };
2675 };
2669
2676
2670 /**
2677 /**
2671 * Success callback for deleting a notebook checkpoint
2678 * Success callback for deleting a notebook checkpoint
2672 *
2679 *
2673 * @method delete_checkpoint_success
2680 * @method delete_checkpoint_success
2674 * @param {Object} data (ignored, should be empty)
2681 * @param {Object} data (ignored, should be empty)
2675 * @param {String} status Description of response status
2682 * @param {String} status Description of response status
2676 * @param {jqXHR} xhr jQuery Ajax object
2683 * @param {jqXHR} xhr jQuery Ajax object
2677 */
2684 */
2678 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2685 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2679 this.events.trigger('checkpoint_deleted.Notebook', data);
2686 this.events.trigger('checkpoint_deleted.Notebook', data);
2680 this.load_notebook(this.notebook_name, this.notebook_path);
2687 this.load_notebook(this.notebook_name, this.notebook_path);
2681 };
2688 };
2682
2689
2683 /**
2690 /**
2684 * Failure callback for deleting a notebook checkpoint.
2691 * Failure callback for deleting a notebook checkpoint.
2685 *
2692 *
2686 * @method delete_checkpoint_error
2693 * @method delete_checkpoint_error
2687 * @param {jqXHR} xhr jQuery Ajax object
2694 * @param {jqXHR} xhr jQuery Ajax object
2688 * @param {String} status Description of response status
2695 * @param {String} status Description of response status
2689 * @param {String} error HTTP error message
2696 * @param {String} error HTTP error message
2690 */
2697 */
2691 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error) {
2698 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error) {
2692 this.events.trigger('checkpoint_delete_failed.Notebook', [xhr, status, error]);
2699 this.events.trigger('checkpoint_delete_failed.Notebook', [xhr, status, error]);
2693 };
2700 };
2694
2701
2695
2702
2696 // For backwards compatability.
2703 // For backwards compatability.
2697 IPython.Notebook = Notebook;
2704 IPython.Notebook = Notebook;
2698
2705
2699 return {'Notebook': Notebook};
2706 return {'Notebook': Notebook};
2700 });
2707 });
@@ -1,1026 +1,1029 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 './comm',
8 './comm',
9 './serialize',
9 './serialize',
10 'widgets/js/init'
10 'widgets/js/init'
11 ], function(IPython, $, utils, comm, serialize, widgetmanager) {
11 ], function(IPython, $, utils, comm, serialize, widgetmanager) {
12 "use strict";
12 "use strict";
13
13
14 /**
14 /**
15 * A Kernel class to communicate with the Python kernel. This
15 * A Kernel class to communicate with the Python kernel. This
16 * should generally not be constructed directly, but be created
16 * should generally not be constructed directly, but be created
17 * by. the `Session` object. Once created, this object should be
17 * by. the `Session` object. Once created, this object should be
18 * used to communicate with the kernel.
18 * used to communicate with the kernel.
19 *
19 *
20 * @class Kernel
20 * @class Kernel
21 * @param {string} kernel_service_url - the URL to access the kernel REST api
21 * @param {string} kernel_service_url - the URL to access the kernel REST api
22 * @param {string} ws_url - the websockets URL
22 * @param {string} ws_url - the websockets URL
23 * @param {Notebook} notebook - notebook object
23 * @param {Notebook} notebook - notebook object
24 * @param {string} name - the kernel type (e.g. python3)
24 * @param {string} name - the kernel type (e.g. python3)
25 */
25 */
26 var Kernel = function (kernel_service_url, ws_url, notebook, name) {
26 var Kernel = function (kernel_service_url, ws_url, notebook, name) {
27 this.events = notebook.events;
27 this.events = notebook.events;
28
28
29 this.id = null;
29 this.id = null;
30 this.name = name;
30 this.name = name;
31
31
32 this.channels = {
32 this.channels = {
33 'shell': null,
33 'shell': null,
34 'iopub': null,
34 'iopub': null,
35 'stdin': null
35 'stdin': null
36 };
36 };
37
37
38 this.kernel_service_url = kernel_service_url;
38 this.kernel_service_url = kernel_service_url;
39 this.kernel_url = null;
39 this.kernel_url = null;
40 this.ws_url = ws_url || IPython.utils.get_body_data("wsUrl");
40 this.ws_url = ws_url || IPython.utils.get_body_data("wsUrl");
41 if (!this.ws_url) {
41 if (!this.ws_url) {
42 // trailing 's' in https will become wss for secure web sockets
42 // trailing 's' in https will become wss for secure web sockets
43 this.ws_url = location.protocol.replace('http', 'ws') + "//" + location.host;
43 this.ws_url = location.protocol.replace('http', 'ws') + "//" + location.host;
44 }
44 }
45
45
46 this.username = "username";
46 this.username = "username";
47 this.session_id = utils.uuid();
47 this.session_id = utils.uuid();
48 this._msg_callbacks = {};
48 this._msg_callbacks = {};
49 this.info_reply = {}; // kernel_info_reply stored here after starting
49
50
50 if (typeof(WebSocket) !== 'undefined') {
51 if (typeof(WebSocket) !== 'undefined') {
51 this.WebSocket = WebSocket;
52 this.WebSocket = WebSocket;
52 } else if (typeof(MozWebSocket) !== 'undefined') {
53 } else if (typeof(MozWebSocket) !== 'undefined') {
53 this.WebSocket = MozWebSocket;
54 this.WebSocket = MozWebSocket;
54 } else {
55 } else {
55 alert('Your browser does not have WebSocket support, please try Chrome, Safari or Firefox β‰₯ 6. Firefox 4 and 5 are also supported by you have to enable WebSockets in about:config.');
56 alert('Your browser does not have WebSocket support, please try Chrome, Safari or Firefox β‰₯ 6. Firefox 4 and 5 are also supported by you have to enable WebSockets in about:config.');
56 }
57 }
57
58
58 this.bind_events();
59 this.bind_events();
59 this.init_iopub_handlers();
60 this.init_iopub_handlers();
60 this.comm_manager = new comm.CommManager(this);
61 this.comm_manager = new comm.CommManager(this);
61 this.widget_manager = new widgetmanager.WidgetManager(this.comm_manager, notebook);
62 this.widget_manager = new widgetmanager.WidgetManager(this.comm_manager, notebook);
62
63
63 this.last_msg_id = null;
64 this.last_msg_id = null;
64 this.last_msg_callbacks = {};
65 this.last_msg_callbacks = {};
65
66
66 this._autorestart_attempt = 0;
67 this._autorestart_attempt = 0;
67 this._reconnect_attempt = 0;
68 this._reconnect_attempt = 0;
68 };
69 };
69
70
70 /**
71 /**
71 * @function _get_msg
72 * @function _get_msg
72 */
73 */
73 Kernel.prototype._get_msg = function (msg_type, content, metadata, buffers) {
74 Kernel.prototype._get_msg = function (msg_type, content, metadata, buffers) {
74 var msg = {
75 var msg = {
75 header : {
76 header : {
76 msg_id : utils.uuid(),
77 msg_id : utils.uuid(),
77 username : this.username,
78 username : this.username,
78 session : this.session_id,
79 session : this.session_id,
79 msg_type : msg_type,
80 msg_type : msg_type,
80 version : "5.0"
81 version : "5.0"
81 },
82 },
82 metadata : metadata || {},
83 metadata : metadata || {},
83 content : content,
84 content : content,
84 buffers : buffers || [],
85 buffers : buffers || [],
85 parent_header : {}
86 parent_header : {}
86 };
87 };
87 return msg;
88 return msg;
88 };
89 };
89
90
90 /**
91 /**
91 * @function bind_events
92 * @function bind_events
92 */
93 */
93 Kernel.prototype.bind_events = function () {
94 Kernel.prototype.bind_events = function () {
94 var that = this;
95 var that = this;
95 this.events.on('send_input_reply.Kernel', function(evt, data) {
96 this.events.on('send_input_reply.Kernel', function(evt, data) {
96 that.send_input_reply(data);
97 that.send_input_reply(data);
97 });
98 });
98
99
99 var record_status = function (evt, info) {
100 var record_status = function (evt, info) {
100 console.log('Kernel: ' + evt.type + ' (' + info.kernel.id + ')');
101 console.log('Kernel: ' + evt.type + ' (' + info.kernel.id + ')');
101 };
102 };
102
103
103 this.events.on('kernel_created.Kernel', record_status);
104 this.events.on('kernel_created.Kernel', record_status);
104 this.events.on('kernel_reconnecting.Kernel', record_status);
105 this.events.on('kernel_reconnecting.Kernel', record_status);
105 this.events.on('kernel_connected.Kernel', record_status);
106 this.events.on('kernel_connected.Kernel', record_status);
106 this.events.on('kernel_starting.Kernel', record_status);
107 this.events.on('kernel_starting.Kernel', record_status);
107 this.events.on('kernel_restarting.Kernel', record_status);
108 this.events.on('kernel_restarting.Kernel', record_status);
108 this.events.on('kernel_autorestarting.Kernel', record_status);
109 this.events.on('kernel_autorestarting.Kernel', record_status);
109 this.events.on('kernel_interrupting.Kernel', record_status);
110 this.events.on('kernel_interrupting.Kernel', record_status);
110 this.events.on('kernel_disconnected.Kernel', record_status);
111 this.events.on('kernel_disconnected.Kernel', record_status);
111 // these are commented out because they are triggered a lot, but can
112 // these are commented out because they are triggered a lot, but can
112 // be uncommented for debugging purposes
113 // be uncommented for debugging purposes
113 //this.events.on('kernel_idle.Kernel', record_status);
114 //this.events.on('kernel_idle.Kernel', record_status);
114 //this.events.on('kernel_busy.Kernel', record_status);
115 //this.events.on('kernel_busy.Kernel', record_status);
115 this.events.on('kernel_ready.Kernel', record_status);
116 this.events.on('kernel_ready.Kernel', record_status);
116 this.events.on('kernel_killed.Kernel', record_status);
117 this.events.on('kernel_killed.Kernel', record_status);
117 this.events.on('kernel_dead.Kernel', record_status);
118 this.events.on('kernel_dead.Kernel', record_status);
118
119
119 this.events.on('kernel_ready.Kernel', function () {
120 this.events.on('kernel_ready.Kernel', function () {
120 that._autorestart_attempt = 0;
121 that._autorestart_attempt = 0;
121 });
122 });
122 this.events.on('kernel_connected.Kernel', function () {
123 this.events.on('kernel_connected.Kernel', function () {
123 that._reconnect_attempt = 0;
124 that._reconnect_attempt = 0;
124 });
125 });
125 };
126 };
126
127
127 /**
128 /**
128 * Initialize the iopub handlers.
129 * Initialize the iopub handlers.
129 *
130 *
130 * @function init_iopub_handlers
131 * @function init_iopub_handlers
131 */
132 */
132 Kernel.prototype.init_iopub_handlers = function () {
133 Kernel.prototype.init_iopub_handlers = function () {
133 var output_msg_types = ['stream', 'display_data', 'execute_result', 'error'];
134 var output_msg_types = ['stream', 'display_data', 'execute_result', 'error'];
134 this._iopub_handlers = {};
135 this._iopub_handlers = {};
135 this.register_iopub_handler('status', $.proxy(this._handle_status_message, this));
136 this.register_iopub_handler('status', $.proxy(this._handle_status_message, this));
136 this.register_iopub_handler('clear_output', $.proxy(this._handle_clear_output, this));
137 this.register_iopub_handler('clear_output', $.proxy(this._handle_clear_output, this));
137
138
138 for (var i=0; i < output_msg_types.length; i++) {
139 for (var i=0; i < output_msg_types.length; i++) {
139 this.register_iopub_handler(output_msg_types[i], $.proxy(this._handle_output_message, this));
140 this.register_iopub_handler(output_msg_types[i], $.proxy(this._handle_output_message, this));
140 }
141 }
141 };
142 };
142
143
143 /**
144 /**
144 * GET /api/kernels
145 * GET /api/kernels
145 *
146 *
146 * Get the list of running kernels.
147 * Get the list of running kernels.
147 *
148 *
148 * @function list
149 * @function list
149 * @param {function} [success] - function executed on ajax success
150 * @param {function} [success] - function executed on ajax success
150 * @param {function} [error] - functon executed on ajax error
151 * @param {function} [error] - functon executed on ajax error
151 */
152 */
152 Kernel.prototype.list = function (success, error) {
153 Kernel.prototype.list = function (success, error) {
153 $.ajax(this.kernel_service_url, {
154 $.ajax(this.kernel_service_url, {
154 processData: false,
155 processData: false,
155 cache: false,
156 cache: false,
156 type: "GET",
157 type: "GET",
157 dataType: "json",
158 dataType: "json",
158 success: success,
159 success: success,
159 error: this._on_error(error)
160 error: this._on_error(error)
160 });
161 });
161 };
162 };
162
163
163 /**
164 /**
164 * POST /api/kernels
165 * POST /api/kernels
165 *
166 *
166 * Start a new kernel.
167 * Start a new kernel.
167 *
168 *
168 * In general this shouldn't be used -- the kernel should be
169 * In general this shouldn't be used -- the kernel should be
169 * started through the session API. If you use this function and
170 * started through the session API. If you use this function and
170 * are also using the session API then your session and kernel
171 * are also using the session API then your session and kernel
171 * WILL be out of sync!
172 * WILL be out of sync!
172 *
173 *
173 * @function start
174 * @function start
174 * @param {params} [Object] - parameters to include in the query string
175 * @param {params} [Object] - parameters to include in the query string
175 * @param {function} [success] - function executed on ajax success
176 * @param {function} [success] - function executed on ajax success
176 * @param {function} [error] - functon executed on ajax error
177 * @param {function} [error] - functon executed on ajax error
177 */
178 */
178 Kernel.prototype.start = function (params, success, error) {
179 Kernel.prototype.start = function (params, success, error) {
179 var url = this.kernel_service_url;
180 var url = this.kernel_service_url;
180 var qs = $.param(params || {}); // query string for sage math stuff
181 var qs = $.param(params || {}); // query string for sage math stuff
181 if (qs !== "") {
182 if (qs !== "") {
182 url = url + "?" + qs;
183 url = url + "?" + qs;
183 }
184 }
184
185
185 var that = this;
186 var that = this;
186 var on_success = function (data, status, xhr) {
187 var on_success = function (data, status, xhr) {
187 that.events.trigger('kernel_created.Kernel', {kernel: that});
188 that.events.trigger('kernel_created.Kernel', {kernel: that});
188 that._kernel_created(data);
189 that._kernel_created(data);
189 if (success) {
190 if (success) {
190 success(data, status, xhr);
191 success(data, status, xhr);
191 }
192 }
192 };
193 };
193
194
194 $.ajax(url, {
195 $.ajax(url, {
195 processData: false,
196 processData: false,
196 cache: false,
197 cache: false,
197 type: "POST",
198 type: "POST",
198 data: JSON.stringify({name: this.name}),
199 data: JSON.stringify({name: this.name}),
199 dataType: "json",
200 dataType: "json",
200 success: this._on_success(on_success),
201 success: this._on_success(on_success),
201 error: this._on_error(error)
202 error: this._on_error(error)
202 });
203 });
203
204
204 return url;
205 return url;
205 };
206 };
206
207
207 /**
208 /**
208 * GET /api/kernels/[:kernel_id]
209 * GET /api/kernels/[:kernel_id]
209 *
210 *
210 * Get information about the kernel.
211 * Get information about the kernel.
211 *
212 *
212 * @function get_info
213 * @function get_info
213 * @param {function} [success] - function executed on ajax success
214 * @param {function} [success] - function executed on ajax success
214 * @param {function} [error] - functon executed on ajax error
215 * @param {function} [error] - functon executed on ajax error
215 */
216 */
216 Kernel.prototype.get_info = function (success, error) {
217 Kernel.prototype.get_info = function (success, error) {
217 $.ajax(this.kernel_url, {
218 $.ajax(this.kernel_url, {
218 processData: false,
219 processData: false,
219 cache: false,
220 cache: false,
220 type: "GET",
221 type: "GET",
221 dataType: "json",
222 dataType: "json",
222 success: this._on_success(success),
223 success: this._on_success(success),
223 error: this._on_error(error)
224 error: this._on_error(error)
224 });
225 });
225 };
226 };
226
227
227 /**
228 /**
228 * DELETE /api/kernels/[:kernel_id]
229 * DELETE /api/kernels/[:kernel_id]
229 *
230 *
230 * Shutdown the kernel.
231 * Shutdown the kernel.
231 *
232 *
232 * If you are also using sessions, then this function shoul NOT be
233 * If you are also using sessions, then this function shoul NOT be
233 * used. Instead, use Session.delete. Otherwise, the session and
234 * used. Instead, use Session.delete. Otherwise, the session and
234 * kernel WILL be out of sync.
235 * kernel WILL be out of sync.
235 *
236 *
236 * @function kill
237 * @function kill
237 * @param {function} [success] - function executed on ajax success
238 * @param {function} [success] - function executed on ajax success
238 * @param {function} [error] - functon executed on ajax error
239 * @param {function} [error] - functon executed on ajax error
239 */
240 */
240 Kernel.prototype.kill = function (success, error) {
241 Kernel.prototype.kill = function (success, error) {
241 this.events.trigger('kernel_killed.Kernel', {kernel: this});
242 this.events.trigger('kernel_killed.Kernel', {kernel: this});
242 this._kernel_dead();
243 this._kernel_dead();
243 $.ajax(this.kernel_url, {
244 $.ajax(this.kernel_url, {
244 processData: false,
245 processData: false,
245 cache: false,
246 cache: false,
246 type: "DELETE",
247 type: "DELETE",
247 dataType: "json",
248 dataType: "json",
248 success: this._on_success(success),
249 success: this._on_success(success),
249 error: this._on_error(error)
250 error: this._on_error(error)
250 });
251 });
251 };
252 };
252
253
253 /**
254 /**
254 * POST /api/kernels/[:kernel_id]/interrupt
255 * POST /api/kernels/[:kernel_id]/interrupt
255 *
256 *
256 * Interrupt the kernel.
257 * Interrupt the kernel.
257 *
258 *
258 * @function interrupt
259 * @function interrupt
259 * @param {function} [success] - function executed on ajax success
260 * @param {function} [success] - function executed on ajax success
260 * @param {function} [error] - functon executed on ajax error
261 * @param {function} [error] - functon executed on ajax error
261 */
262 */
262 Kernel.prototype.interrupt = function (success, error) {
263 Kernel.prototype.interrupt = function (success, error) {
263 this.events.trigger('kernel_interrupting.Kernel', {kernel: this});
264 this.events.trigger('kernel_interrupting.Kernel', {kernel: this});
264
265
265 var that = this;
266 var that = this;
266 var on_success = function (data, status, xhr) {
267 var on_success = function (data, status, xhr) {
267 // get kernel info so we know what state the kernel is in
268 // get kernel info so we know what state the kernel is in
268 that.kernel_info();
269 that.kernel_info();
269 if (success) {
270 if (success) {
270 success(data, status, xhr);
271 success(data, status, xhr);
271 }
272 }
272 };
273 };
273
274
274 var url = utils.url_join_encode(this.kernel_url, 'interrupt');
275 var url = utils.url_join_encode(this.kernel_url, 'interrupt');
275 $.ajax(url, {
276 $.ajax(url, {
276 processData: false,
277 processData: false,
277 cache: false,
278 cache: false,
278 type: "POST",
279 type: "POST",
279 dataType: "json",
280 dataType: "json",
280 success: this._on_success(on_success),
281 success: this._on_success(on_success),
281 error: this._on_error(error)
282 error: this._on_error(error)
282 });
283 });
283 };
284 };
284
285
285 /**
286 /**
286 * POST /api/kernels/[:kernel_id]/restart
287 * POST /api/kernels/[:kernel_id]/restart
287 *
288 *
288 * Restart the kernel.
289 * Restart the kernel.
289 *
290 *
290 * @function interrupt
291 * @function interrupt
291 * @param {function} [success] - function executed on ajax success
292 * @param {function} [success] - function executed on ajax success
292 * @param {function} [error] - functon executed on ajax error
293 * @param {function} [error] - functon executed on ajax error
293 */
294 */
294 Kernel.prototype.restart = function (success, error) {
295 Kernel.prototype.restart = function (success, error) {
295 this.events.trigger('kernel_restarting.Kernel', {kernel: this});
296 this.events.trigger('kernel_restarting.Kernel', {kernel: this});
296 this.stop_channels();
297 this.stop_channels();
297
298
298 var that = this;
299 var that = this;
299 var on_success = function (data, status, xhr) {
300 var on_success = function (data, status, xhr) {
300 that.events.trigger('kernel_created.Kernel', {kernel: that});
301 that.events.trigger('kernel_created.Kernel', {kernel: that});
301 that._kernel_created(data);
302 that._kernel_created(data);
302 if (success) {
303 if (success) {
303 success(data, status, xhr);
304 success(data, status, xhr);
304 }
305 }
305 };
306 };
306
307
307 var on_error = function (xhr, status, err) {
308 var on_error = function (xhr, status, err) {
308 that.events.trigger('kernel_dead.Kernel', {kernel: that});
309 that.events.trigger('kernel_dead.Kernel', {kernel: that});
309 that._kernel_dead();
310 that._kernel_dead();
310 if (error) {
311 if (error) {
311 error(xhr, status, err);
312 error(xhr, status, err);
312 }
313 }
313 };
314 };
314
315
315 var url = utils.url_join_encode(this.kernel_url, 'restart');
316 var url = utils.url_join_encode(this.kernel_url, 'restart');
316 $.ajax(url, {
317 $.ajax(url, {
317 processData: false,
318 processData: false,
318 cache: false,
319 cache: false,
319 type: "POST",
320 type: "POST",
320 dataType: "json",
321 dataType: "json",
321 success: this._on_success(on_success),
322 success: this._on_success(on_success),
322 error: this._on_error(on_error)
323 error: this._on_error(on_error)
323 });
324 });
324 };
325 };
325
326
326 /**
327 /**
327 * Reconnect to a disconnected kernel. This is not actually a
328 * Reconnect to a disconnected kernel. This is not actually a
328 * standard HTTP request, but useful function nonetheless for
329 * standard HTTP request, but useful function nonetheless for
329 * reconnecting to the kernel if the connection is somehow lost.
330 * reconnecting to the kernel if the connection is somehow lost.
330 *
331 *
331 * @function reconnect
332 * @function reconnect
332 */
333 */
333 Kernel.prototype.reconnect = function () {
334 Kernel.prototype.reconnect = function () {
334 this.events.trigger('kernel_reconnecting.Kernel', {kernel: this});
335 this.events.trigger('kernel_reconnecting.Kernel', {kernel: this});
335 setTimeout($.proxy(this.start_channels, this), 3000);
336 setTimeout($.proxy(this.start_channels, this), 3000);
336 };
337 };
337
338
338 /**
339 /**
339 * Handle a successful AJAX request by updating the kernel id and
340 * Handle a successful AJAX request by updating the kernel id and
340 * name from the response, and then optionally calling a provided
341 * name from the response, and then optionally calling a provided
341 * callback.
342 * callback.
342 *
343 *
343 * @function _on_success
344 * @function _on_success
344 * @param {function} success - callback
345 * @param {function} success - callback
345 */
346 */
346 Kernel.prototype._on_success = function (success) {
347 Kernel.prototype._on_success = function (success) {
347 var that = this;
348 var that = this;
348 return function (data, status, xhr) {
349 return function (data, status, xhr) {
349 if (data) {
350 if (data) {
350 that.id = data.id;
351 that.id = data.id;
351 that.name = data.name;
352 that.name = data.name;
352 }
353 }
353 that.kernel_url = utils.url_join_encode(that.kernel_service_url, that.id);
354 that.kernel_url = utils.url_join_encode(that.kernel_service_url, that.id);
354 if (success) {
355 if (success) {
355 success(data, status, xhr);
356 success(data, status, xhr);
356 }
357 }
357 };
358 };
358 };
359 };
359
360
360 /**
361 /**
361 * Handle a failed AJAX request by logging the error message, and
362 * Handle a failed AJAX request by logging the error message, and
362 * then optionally calling a provided callback.
363 * then optionally calling a provided callback.
363 *
364 *
364 * @function _on_error
365 * @function _on_error
365 * @param {function} error - callback
366 * @param {function} error - callback
366 */
367 */
367 Kernel.prototype._on_error = function (error) {
368 Kernel.prototype._on_error = function (error) {
368 return function (xhr, status, err) {
369 return function (xhr, status, err) {
369 utils.log_ajax_error(xhr, status, err);
370 utils.log_ajax_error(xhr, status, err);
370 if (error) {
371 if (error) {
371 error(xhr, status, err);
372 error(xhr, status, err);
372 }
373 }
373 };
374 };
374 };
375 };
375
376
376 /**
377 /**
377 * Perform necessary tasks once the kernel has been started,
378 * Perform necessary tasks once the kernel has been started,
378 * including actually connecting to the kernel.
379 * including actually connecting to the kernel.
379 *
380 *
380 * @function _kernel_created
381 * @function _kernel_created
381 * @param {Object} data - information about the kernel including id
382 * @param {Object} data - information about the kernel including id
382 */
383 */
383 Kernel.prototype._kernel_created = function (data) {
384 Kernel.prototype._kernel_created = function (data) {
384 this.id = data.id;
385 this.id = data.id;
385 this.kernel_url = utils.url_join_encode(this.kernel_service_url, this.id);
386 this.kernel_url = utils.url_join_encode(this.kernel_service_url, this.id);
386 this.start_channels();
387 this.start_channels();
387 };
388 };
388
389
389 /**
390 /**
390 * Perform necessary tasks once the connection to the kernel has
391 * Perform necessary tasks once the connection to the kernel has
391 * been established. This includes requesting information about
392 * been established. This includes requesting information about
392 * the kernel.
393 * the kernel.
393 *
394 *
394 * @function _kernel_connected
395 * @function _kernel_connected
395 */
396 */
396 Kernel.prototype._kernel_connected = function () {
397 Kernel.prototype._kernel_connected = function () {
397 this.events.trigger('kernel_connected.Kernel', {kernel: this});
398 this.events.trigger('kernel_connected.Kernel', {kernel: this});
398 this.events.trigger('kernel_starting.Kernel', {kernel: this});
399 this.events.trigger('kernel_starting.Kernel', {kernel: this});
399 // get kernel info so we know what state the kernel is in
400 // get kernel info so we know what state the kernel is in
400 var that = this;
401 var that = this;
401 this.kernel_info(function () {
402 this.kernel_info(function (reply) {
403 that.info_reply = reply.content;
402 that.events.trigger('kernel_ready.Kernel', {kernel: that});
404 that.events.trigger('kernel_ready.Kernel', {kernel: that});
403 });
405 });
404 };
406 };
405
407
406 /**
408 /**
407 * Perform necessary tasks after the kernel has died. This closing
409 * Perform necessary tasks after the kernel has died. This closing
408 * communication channels to the kernel if they are still somehow
410 * communication channels to the kernel if they are still somehow
409 * open.
411 * open.
410 *
412 *
411 * @function _kernel_dead
413 * @function _kernel_dead
412 */
414 */
413 Kernel.prototype._kernel_dead = function () {
415 Kernel.prototype._kernel_dead = function () {
414 this.stop_channels();
416 this.stop_channels();
415 };
417 };
416
418
417 /**
419 /**
418 * Start the `shell`and `iopub` channels.
420 * Start the `shell`and `iopub` channels.
419 * Will stop and restart them if they already exist.
421 * Will stop and restart them if they already exist.
420 *
422 *
421 * @function start_channels
423 * @function start_channels
422 */
424 */
423 Kernel.prototype.start_channels = function () {
425 Kernel.prototype.start_channels = function () {
424 var that = this;
426 var that = this;
425 this.stop_channels();
427 this.stop_channels();
426 var ws_host_url = this.ws_url + this.kernel_url;
428 var ws_host_url = this.ws_url + this.kernel_url;
427
429
428 console.log("Starting WebSockets:", ws_host_url);
430 console.log("Starting WebSockets:", ws_host_url);
429
431
430 var channel_url = function(channel) {
432 var channel_url = function(channel) {
431 return [
433 return [
432 that.ws_url,
434 that.ws_url,
433 utils.url_join_encode(that.kernel_url, channel),
435 utils.url_join_encode(that.kernel_url, channel),
434 "?session_id=" + that.session_id
436 "?session_id=" + that.session_id
435 ].join('');
437 ].join('');
436 };
438 };
437 this.channels.shell = new this.WebSocket(channel_url("shell"));
439 this.channels.shell = new this.WebSocket(channel_url("shell"));
438 this.channels.stdin = new this.WebSocket(channel_url("stdin"));
440 this.channels.stdin = new this.WebSocket(channel_url("stdin"));
439 this.channels.iopub = new this.WebSocket(channel_url("iopub"));
441 this.channels.iopub = new this.WebSocket(channel_url("iopub"));
440
442
441 var already_called_onclose = false; // only alert once
443 var already_called_onclose = false; // only alert once
442 var ws_closed_early = function(evt){
444 var ws_closed_early = function(evt){
443 if (already_called_onclose){
445 if (already_called_onclose){
444 return;
446 return;
445 }
447 }
446 already_called_onclose = true;
448 already_called_onclose = true;
447 if ( ! evt.wasClean ){
449 if ( ! evt.wasClean ){
448 // If the websocket was closed early, that could mean
450 // If the websocket was closed early, that could mean
449 // that the kernel is actually dead. Try getting
451 // that the kernel is actually dead. Try getting
450 // information about the kernel from the API call --
452 // information about the kernel from the API call --
451 // if that fails, then assume the kernel is dead,
453 // if that fails, then assume the kernel is dead,
452 // otherwise just follow the typical websocket closed
454 // otherwise just follow the typical websocket closed
453 // protocol.
455 // protocol.
454 that.get_info(function () {
456 that.get_info(function () {
455 that._ws_closed(ws_host_url, false);
457 that._ws_closed(ws_host_url, false);
456 }, function () {
458 }, function () {
457 that.events.trigger('kernel_dead.Kernel', {kernel: that});
459 that.events.trigger('kernel_dead.Kernel', {kernel: that});
458 that._kernel_dead();
460 that._kernel_dead();
459 });
461 });
460 }
462 }
461 };
463 };
462 var ws_closed_late = function(evt){
464 var ws_closed_late = function(evt){
463 if (already_called_onclose){
465 if (already_called_onclose){
464 return;
466 return;
465 }
467 }
466 already_called_onclose = true;
468 already_called_onclose = true;
467 if ( ! evt.wasClean ){
469 if ( ! evt.wasClean ){
468 that._ws_closed(ws_host_url, false);
470 that._ws_closed(ws_host_url, false);
469 }
471 }
470 };
472 };
471 var ws_error = function(evt){
473 var ws_error = function(evt){
472 if (already_called_onclose){
474 if (already_called_onclose){
473 return;
475 return;
474 }
476 }
475 already_called_onclose = true;
477 already_called_onclose = true;
476 that._ws_closed(ws_host_url, true);
478 that._ws_closed(ws_host_url, true);
477 };
479 };
478
480
479 for (var c in this.channels) {
481 for (var c in this.channels) {
480 this.channels[c].onopen = $.proxy(this._ws_opened, this);
482 this.channels[c].onopen = $.proxy(this._ws_opened, this);
481 this.channels[c].onclose = ws_closed_early;
483 this.channels[c].onclose = ws_closed_early;
482 this.channels[c].onerror = ws_error;
484 this.channels[c].onerror = ws_error;
483 }
485 }
484 // switch from early-close to late-close message after 1s
486 // switch from early-close to late-close message after 1s
485 setTimeout(function() {
487 setTimeout(function() {
486 for (var c in that.channels) {
488 for (var c in that.channels) {
487 if (that.channels[c] !== null) {
489 if (that.channels[c] !== null) {
488 that.channels[c].onclose = ws_closed_late;
490 that.channels[c].onclose = ws_closed_late;
489 }
491 }
490 }
492 }
491 }, 1000);
493 }, 1000);
492 this.channels.shell.onmessage = $.proxy(this._handle_shell_reply, this);
494 this.channels.shell.onmessage = $.proxy(this._handle_shell_reply, this);
493 this.channels.iopub.onmessage = $.proxy(this._handle_iopub_message, this);
495 this.channels.iopub.onmessage = $.proxy(this._handle_iopub_message, this);
494 this.channels.stdin.onmessage = $.proxy(this._handle_input_request, this);
496 this.channels.stdin.onmessage = $.proxy(this._handle_input_request, this);
495 };
497 };
496
498
497 /**
499 /**
498 * Handle a websocket entering the open state,
500 * Handle a websocket entering the open state,
499 * signaling that the kernel is connected when all channels are open.
501 * signaling that the kernel is connected when all channels are open.
500 *
502 *
501 * @function _ws_opened
503 * @function _ws_opened
502 */
504 */
503 Kernel.prototype._ws_opened = function (evt) {
505 Kernel.prototype._ws_opened = function (evt) {
504 if (this.is_connected()) {
506 if (this.is_connected()) {
505 // all events ready, trigger started event.
507 // all events ready, trigger started event.
506 this._kernel_connected();
508 this._kernel_connected();
507 }
509 }
508 };
510 };
509
511
510 /**
512 /**
511 * Handle a websocket entering the closed state. This closes the
513 * Handle a websocket entering the closed state. This closes the
512 * other communication channels if they are open. If the websocket
514 * other communication channels if they are open. If the websocket
513 * was not closed due to an error, try to reconnect to the kernel.
515 * was not closed due to an error, try to reconnect to the kernel.
514 *
516 *
515 * @function _ws_closed
517 * @function _ws_closed
516 * @param {string} ws_url - the websocket url
518 * @param {string} ws_url - the websocket url
517 * @param {bool} error - whether the connection was closed due to an error
519 * @param {bool} error - whether the connection was closed due to an error
518 */
520 */
519 Kernel.prototype._ws_closed = function(ws_url, error) {
521 Kernel.prototype._ws_closed = function(ws_url, error) {
520 this.stop_channels();
522 this.stop_channels();
521
523
522 this.events.trigger('kernel_disconnected.Kernel', {kernel: this});
524 this.events.trigger('kernel_disconnected.Kernel', {kernel: this});
523 if (error) {
525 if (error) {
524 console.log('WebSocket connection failed: ', ws_url);
526 console.log('WebSocket connection failed: ', ws_url);
525 this._reconnect_attempt = this._reconnect_attempt + 1;
527 this._reconnect_attempt = this._reconnect_attempt + 1;
526 this.events.trigger('kernel_connection_failed.Kernel', {kernel: this, ws_url: ws_url, attempt: this._reconnect_attempt});
528 this.events.trigger('kernel_connection_failed.Kernel', {kernel: this, ws_url: ws_url, attempt: this._reconnect_attempt});
527 }
529 }
528 this.reconnect();
530 this.reconnect();
529 };
531 };
530
532
531 /**
533 /**
532 * Close the websocket channels. After successful close, the value
534 * Close the websocket channels. After successful close, the value
533 * in `this.channels[channel_name]` will be null.
535 * in `this.channels[channel_name]` will be null.
534 *
536 *
535 * @function stop_channels
537 * @function stop_channels
536 */
538 */
537 Kernel.prototype.stop_channels = function () {
539 Kernel.prototype.stop_channels = function () {
538 var that = this;
540 var that = this;
539 var close = function (c) {
541 var close = function (c) {
540 return function () {
542 return function () {
541 if (that.channels[c] && that.channels[c].readyState === WebSocket.CLOSED) {
543 if (that.channels[c] && that.channels[c].readyState === WebSocket.CLOSED) {
542 that.channels[c] = null;
544 that.channels[c] = null;
543 }
545 }
544 };
546 };
545 };
547 };
546 for (var c in this.channels) {
548 for (var c in this.channels) {
547 if ( this.channels[c] !== null ) {
549 if ( this.channels[c] !== null ) {
548 if (this.channels[c].readyState === WebSocket.OPEN) {
550 if (this.channels[c].readyState === WebSocket.OPEN) {
549 this.channels[c].onclose = close(c);
551 this.channels[c].onclose = close(c);
550 this.channels[c].close();
552 this.channels[c].close();
551 } else {
553 } else {
552 close(c)();
554 close(c)();
553 }
555 }
554 }
556 }
555 }
557 }
556 };
558 };
557
559
558 /**
560 /**
559 * Check whether there is a connection to the kernel. This
561 * Check whether there is a connection to the kernel. This
560 * function only returns true if all channel objects have been
562 * function only returns true if all channel objects have been
561 * created and have a state of WebSocket.OPEN.
563 * created and have a state of WebSocket.OPEN.
562 *
564 *
563 * @function is_connected
565 * @function is_connected
564 * @returns {bool} - whether there is a connection
566 * @returns {bool} - whether there is a connection
565 */
567 */
566 Kernel.prototype.is_connected = function () {
568 Kernel.prototype.is_connected = function () {
567 for (var c in this.channels) {
569 for (var c in this.channels) {
568 // if any channel is not ready, then we're not connected
570 // if any channel is not ready, then we're not connected
569 if (this.channels[c] === null) {
571 if (this.channels[c] === null) {
570 return false;
572 return false;
571 }
573 }
572 if (this.channels[c].readyState !== WebSocket.OPEN) {
574 if (this.channels[c].readyState !== WebSocket.OPEN) {
573 return false;
575 return false;
574 }
576 }
575 }
577 }
576 return true;
578 return true;
577 };
579 };
578
580
579 /**
581 /**
580 * Check whether the connection to the kernel has been completely
582 * Check whether the connection to the kernel has been completely
581 * severed. This function only returns true if all channel objects
583 * severed. This function only returns true if all channel objects
582 * are null.
584 * are null.
583 *
585 *
584 * @function is_fully_disconnected
586 * @function is_fully_disconnected
585 * @returns {bool} - whether the kernel is fully disconnected
587 * @returns {bool} - whether the kernel is fully disconnected
586 */
588 */
587 Kernel.prototype.is_fully_disconnected = function () {
589 Kernel.prototype.is_fully_disconnected = function () {
588 for (var c in this.channels) {
590 for (var c in this.channels) {
589 if (this.channels[c] === null) {
591 if (this.channels[c] === null) {
590 return true;
592 return true;
591 }
593 }
592 }
594 }
593 return false;
595 return false;
594 };
596 };
595
597
596 /**
598 /**
597 * Send a message on the Kernel's shell channel
599 * Send a message on the Kernel's shell channel
598 *
600 *
599 * @function send_shell_message
601 * @function send_shell_message
600 */
602 */
601 Kernel.prototype.send_shell_message = function (msg_type, content, callbacks, metadata, buffers) {
603 Kernel.prototype.send_shell_message = function (msg_type, content, callbacks, metadata, buffers) {
602 if (!this.is_connected()) {
604 if (!this.is_connected()) {
603 throw new Error("kernel is not connected");
605 throw new Error("kernel is not connected");
604 }
606 }
605 var msg = this._get_msg(msg_type, content, metadata, buffers);
607 var msg = this._get_msg(msg_type, content, metadata, buffers);
606 this.channels.shell.send(serialize.serialize(msg));
608 this.channels.shell.send(serialize.serialize(msg));
607 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
609 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
608 return msg.header.msg_id;
610 return msg.header.msg_id;
609 };
611 };
610
612
611 /**
613 /**
612 * Get kernel info
614 * Get kernel info
613 *
615 *
614 * @function kernel_info
616 * @function kernel_info
615 * @param callback {function}
617 * @param callback {function}
616 *
618 *
617 * When calling this method, pass a callback function that expects one argument.
619 * When calling this method, pass a callback function that expects one argument.
618 * The callback will be passed the complete `kernel_info_reply` message documented
620 * The callback will be passed the complete `kernel_info_reply` message documented
619 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#kernel-info)
621 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#kernel-info)
620 */
622 */
621 Kernel.prototype.kernel_info = function (callback) {
623 Kernel.prototype.kernel_info = function (callback) {
622 var callbacks;
624 var callbacks;
623 if (callback) {
625 if (callback) {
624 callbacks = { shell : { reply : callback } };
626 callbacks = { shell : { reply : callback } };
625 }
627 }
626 return this.send_shell_message("kernel_info_request", {}, callbacks);
628 return this.send_shell_message("kernel_info_request", {}, callbacks);
627 };
629 };
628
630
629 /**
631 /**
630 * Get info on an object
632 * Get info on an object
631 *
633 *
632 * When calling this method, pass a callback function that expects one argument.
634 * When calling this method, pass a callback function that expects one argument.
633 * The callback will be passed the complete `inspect_reply` message documented
635 * The callback will be passed the complete `inspect_reply` message documented
634 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#object-information)
636 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#object-information)
635 *
637 *
636 * @function inspect
638 * @function inspect
637 * @param code {string}
639 * @param code {string}
638 * @param cursor_pos {integer}
640 * @param cursor_pos {integer}
639 * @param callback {function}
641 * @param callback {function}
640 */
642 */
641 Kernel.prototype.inspect = function (code, cursor_pos, callback) {
643 Kernel.prototype.inspect = function (code, cursor_pos, callback) {
642 var callbacks;
644 var callbacks;
643 if (callback) {
645 if (callback) {
644 callbacks = { shell : { reply : callback } };
646 callbacks = { shell : { reply : callback } };
645 }
647 }
646
648
647 var content = {
649 var content = {
648 code : code,
650 code : code,
649 cursor_pos : cursor_pos,
651 cursor_pos : cursor_pos,
650 detail_level : 0
652 detail_level : 0
651 };
653 };
652 return this.send_shell_message("inspect_request", content, callbacks);
654 return this.send_shell_message("inspect_request", content, callbacks);
653 };
655 };
654
656
655 /**
657 /**
656 * Execute given code into kernel, and pass result to callback.
658 * Execute given code into kernel, and pass result to callback.
657 *
659 *
658 * @async
660 * @async
659 * @function execute
661 * @function execute
660 * @param {string} code
662 * @param {string} code
661 * @param [callbacks] {Object} With the following keys (all optional)
663 * @param [callbacks] {Object} With the following keys (all optional)
662 * @param callbacks.shell.reply {function}
664 * @param callbacks.shell.reply {function}
663 * @param callbacks.shell.payload.[payload_name] {function}
665 * @param callbacks.shell.payload.[payload_name] {function}
664 * @param callbacks.iopub.output {function}
666 * @param callbacks.iopub.output {function}
665 * @param callbacks.iopub.clear_output {function}
667 * @param callbacks.iopub.clear_output {function}
666 * @param callbacks.input {function}
668 * @param callbacks.input {function}
667 * @param {object} [options]
669 * @param {object} [options]
668 * @param [options.silent=false] {Boolean}
670 * @param [options.silent=false] {Boolean}
669 * @param [options.user_expressions=empty_dict] {Dict}
671 * @param [options.user_expressions=empty_dict] {Dict}
670 * @param [options.allow_stdin=false] {Boolean} true|false
672 * @param [options.allow_stdin=false] {Boolean} true|false
671 *
673 *
672 * @example
674 * @example
673 *
675 *
674 * The options object should contain the options for the execute
676 * The options object should contain the options for the execute
675 * call. Its default values are:
677 * call. Its default values are:
676 *
678 *
677 * options = {
679 * options = {
678 * silent : true,
680 * silent : true,
679 * user_expressions : {},
681 * user_expressions : {},
680 * allow_stdin : false
682 * allow_stdin : false
681 * }
683 * }
682 *
684 *
683 * When calling this method pass a callbacks structure of the
685 * When calling this method pass a callbacks structure of the
684 * form:
686 * form:
685 *
687 *
686 * callbacks = {
688 * callbacks = {
687 * shell : {
689 * shell : {
688 * reply : execute_reply_callback,
690 * reply : execute_reply_callback,
689 * payload : {
691 * payload : {
690 * set_next_input : set_next_input_callback,
692 * set_next_input : set_next_input_callback,
691 * }
693 * }
692 * },
694 * },
693 * iopub : {
695 * iopub : {
694 * output : output_callback,
696 * output : output_callback,
695 * clear_output : clear_output_callback,
697 * clear_output : clear_output_callback,
696 * },
698 * },
697 * input : raw_input_callback
699 * input : raw_input_callback
698 * }
700 * }
699 *
701 *
700 * Each callback will be passed the entire message as a single
702 * Each callback will be passed the entire message as a single
701 * arugment. Payload handlers will be passed the corresponding
703 * arugment. Payload handlers will be passed the corresponding
702 * payload and the execute_reply message.
704 * payload and the execute_reply message.
703 */
705 */
704 Kernel.prototype.execute = function (code, callbacks, options) {
706 Kernel.prototype.execute = function (code, callbacks, options) {
705 var content = {
707 var content = {
706 code : code,
708 code : code,
707 silent : true,
709 silent : true,
708 store_history : false,
710 store_history : false,
709 user_expressions : {},
711 user_expressions : {},
710 allow_stdin : false
712 allow_stdin : false
711 };
713 };
712 callbacks = callbacks || {};
714 callbacks = callbacks || {};
713 if (callbacks.input !== undefined) {
715 if (callbacks.input !== undefined) {
714 content.allow_stdin = true;
716 content.allow_stdin = true;
715 }
717 }
716 $.extend(true, content, options);
718 $.extend(true, content, options);
717 this.events.trigger('execution_request.Kernel', {kernel: this, content: content});
719 this.events.trigger('execution_request.Kernel', {kernel: this, content: content});
718 return this.send_shell_message("execute_request", content, callbacks);
720 return this.send_shell_message("execute_request", content, callbacks);
719 };
721 };
720
722
721 /**
723 /**
722 * When calling this method, pass a function to be called with the
724 * When calling this method, pass a function to be called with the
723 * `complete_reply` message as its only argument when it arrives.
725 * `complete_reply` message as its only argument when it arrives.
724 *
726 *
725 * `complete_reply` is documented
727 * `complete_reply` is documented
726 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#complete)
728 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#complete)
727 *
729 *
728 * @function complete
730 * @function complete
729 * @param code {string}
731 * @param code {string}
730 * @param cursor_pos {integer}
732 * @param cursor_pos {integer}
731 * @param callback {function}
733 * @param callback {function}
732 */
734 */
733 Kernel.prototype.complete = function (code, cursor_pos, callback) {
735 Kernel.prototype.complete = function (code, cursor_pos, callback) {
734 var callbacks;
736 var callbacks;
735 if (callback) {
737 if (callback) {
736 callbacks = { shell : { reply : callback } };
738 callbacks = { shell : { reply : callback } };
737 }
739 }
738 var content = {
740 var content = {
739 code : code,
741 code : code,
740 cursor_pos : cursor_pos
742 cursor_pos : cursor_pos
741 };
743 };
742 return this.send_shell_message("complete_request", content, callbacks);
744 return this.send_shell_message("complete_request", content, callbacks);
743 };
745 };
744
746
745 /**
747 /**
746 * @function send_input_reply
748 * @function send_input_reply
747 */
749 */
748 Kernel.prototype.send_input_reply = function (input) {
750 Kernel.prototype.send_input_reply = function (input) {
749 if (!this.is_connected()) {
751 if (!this.is_connected()) {
750 throw new Error("kernel is not connected");
752 throw new Error("kernel is not connected");
751 }
753 }
752 var content = {
754 var content = {
753 value : input
755 value : input
754 };
756 };
755 this.events.trigger('input_reply.Kernel', {kernel: this, content: content});
757 this.events.trigger('input_reply.Kernel', {kernel: this, content: content});
756 var msg = this._get_msg("input_reply", content);
758 var msg = this._get_msg("input_reply", content);
757 this.channels.stdin.send(serialize.serialize(msg));
759 this.channels.stdin.send(serialize.serialize(msg));
758 return msg.header.msg_id;
760 return msg.header.msg_id;
759 };
761 };
760
762
761 /**
763 /**
762 * @function register_iopub_handler
764 * @function register_iopub_handler
763 */
765 */
764 Kernel.prototype.register_iopub_handler = function (msg_type, callback) {
766 Kernel.prototype.register_iopub_handler = function (msg_type, callback) {
765 this._iopub_handlers[msg_type] = callback;
767 this._iopub_handlers[msg_type] = callback;
766 };
768 };
767
769
768 /**
770 /**
769 * Get the iopub handler for a specific message type.
771 * Get the iopub handler for a specific message type.
770 *
772 *
771 * @function get_iopub_handler
773 * @function get_iopub_handler
772 */
774 */
773 Kernel.prototype.get_iopub_handler = function (msg_type) {
775 Kernel.prototype.get_iopub_handler = function (msg_type) {
774 return this._iopub_handlers[msg_type];
776 return this._iopub_handlers[msg_type];
775 };
777 };
776
778
777 /**
779 /**
778 * Get callbacks for a specific message.
780 * Get callbacks for a specific message.
779 *
781 *
780 * @function get_callbacks_for_msg
782 * @function get_callbacks_for_msg
781 */
783 */
782 Kernel.prototype.get_callbacks_for_msg = function (msg_id) {
784 Kernel.prototype.get_callbacks_for_msg = function (msg_id) {
783 if (msg_id == this.last_msg_id) {
785 if (msg_id == this.last_msg_id) {
784 return this.last_msg_callbacks;
786 return this.last_msg_callbacks;
785 } else {
787 } else {
786 return this._msg_callbacks[msg_id];
788 return this._msg_callbacks[msg_id];
787 }
789 }
788 };
790 };
789
791
790 /**
792 /**
791 * Clear callbacks for a specific message.
793 * Clear callbacks for a specific message.
792 *
794 *
793 * @function clear_callbacks_for_msg
795 * @function clear_callbacks_for_msg
794 */
796 */
795 Kernel.prototype.clear_callbacks_for_msg = function (msg_id) {
797 Kernel.prototype.clear_callbacks_for_msg = function (msg_id) {
796 if (this._msg_callbacks[msg_id] !== undefined ) {
798 if (this._msg_callbacks[msg_id] !== undefined ) {
797 delete this._msg_callbacks[msg_id];
799 delete this._msg_callbacks[msg_id];
798 }
800 }
799 };
801 };
800
802
801 /**
803 /**
802 * @function _finish_shell
804 * @function _finish_shell
803 */
805 */
804 Kernel.prototype._finish_shell = function (msg_id) {
806 Kernel.prototype._finish_shell = function (msg_id) {
805 var callbacks = this._msg_callbacks[msg_id];
807 var callbacks = this._msg_callbacks[msg_id];
806 if (callbacks !== undefined) {
808 if (callbacks !== undefined) {
807 callbacks.shell_done = true;
809 callbacks.shell_done = true;
808 if (callbacks.iopub_done) {
810 if (callbacks.iopub_done) {
809 this.clear_callbacks_for_msg(msg_id);
811 this.clear_callbacks_for_msg(msg_id);
810 }
812 }
811 }
813 }
812 };
814 };
813
815
814 /**
816 /**
815 * @function _finish_iopub
817 * @function _finish_iopub
816 */
818 */
817 Kernel.prototype._finish_iopub = function (msg_id) {
819 Kernel.prototype._finish_iopub = function (msg_id) {
818 var callbacks = this._msg_callbacks[msg_id];
820 var callbacks = this._msg_callbacks[msg_id];
819 if (callbacks !== undefined) {
821 if (callbacks !== undefined) {
820 callbacks.iopub_done = true;
822 callbacks.iopub_done = true;
821 if (callbacks.shell_done) {
823 if (callbacks.shell_done) {
822 this.clear_callbacks_for_msg(msg_id);
824 this.clear_callbacks_for_msg(msg_id);
823 }
825 }
824 }
826 }
825 };
827 };
826
828
827 /**
829 /**
828 * Set callbacks for a particular message.
830 * Set callbacks for a particular message.
829 * Callbacks should be a struct of the following form:
831 * Callbacks should be a struct of the following form:
830 * shell : {
832 * shell : {
831 *
833 *
832 * }
834 * }
833 *
835 *
834 * @function set_callbacks_for_msg
836 * @function set_callbacks_for_msg
835 */
837 */
836 Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks) {
838 Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks) {
837 this.last_msg_id = msg_id;
839 this.last_msg_id = msg_id;
838 if (callbacks) {
840 if (callbacks) {
839 // shallow-copy mapping, because we will modify it at the top level
841 // shallow-copy mapping, because we will modify it at the top level
840 var cbcopy = this._msg_callbacks[msg_id] = this.last_msg_callbacks = {};
842 var cbcopy = this._msg_callbacks[msg_id] = this.last_msg_callbacks = {};
841 cbcopy.shell = callbacks.shell;
843 cbcopy.shell = callbacks.shell;
842 cbcopy.iopub = callbacks.iopub;
844 cbcopy.iopub = callbacks.iopub;
843 cbcopy.input = callbacks.input;
845 cbcopy.input = callbacks.input;
844 cbcopy.shell_done = (!callbacks.shell);
846 cbcopy.shell_done = (!callbacks.shell);
845 cbcopy.iopub_done = (!callbacks.iopub);
847 cbcopy.iopub_done = (!callbacks.iopub);
846 } else {
848 } else {
847 this.last_msg_callbacks = {};
849 this.last_msg_callbacks = {};
848 }
850 }
849 };
851 };
850
852
851 /**
853 /**
852 * @function _handle_shell_reply
854 * @function _handle_shell_reply
853 */
855 */
854 Kernel.prototype._handle_shell_reply = function (e) {
856 Kernel.prototype._handle_shell_reply = function (e) {
855 serialize.deserialize(e.data, $.proxy(this._finish_shell_reply, this));
857 serialize.deserialize(e.data, $.proxy(this._finish_shell_reply, this));
856 };
858 };
857
859
858 Kernel.prototype._finish_shell_reply = function (reply) {
860 Kernel.prototype._finish_shell_reply = function (reply) {
859 this.events.trigger('shell_reply.Kernel', {kernel: this, reply:reply});
861 this.events.trigger('shell_reply.Kernel', {kernel: this, reply:reply});
860 var content = reply.content;
862 var content = reply.content;
861 var metadata = reply.metadata;
863 var metadata = reply.metadata;
862 var parent_id = reply.parent_header.msg_id;
864 var parent_id = reply.parent_header.msg_id;
863 var callbacks = this.get_callbacks_for_msg(parent_id);
865 var callbacks = this.get_callbacks_for_msg(parent_id);
864 if (!callbacks || !callbacks.shell) {
866 if (!callbacks || !callbacks.shell) {
865 return;
867 return;
866 }
868 }
867 var shell_callbacks = callbacks.shell;
869 var shell_callbacks = callbacks.shell;
868
870
869 // signal that shell callbacks are done
871 // signal that shell callbacks are done
870 this._finish_shell(parent_id);
872 this._finish_shell(parent_id);
871
873
872 if (shell_callbacks.reply !== undefined) {
874 if (shell_callbacks.reply !== undefined) {
873 shell_callbacks.reply(reply);
875 shell_callbacks.reply(reply);
874 }
876 }
875 if (content.payload && shell_callbacks.payload) {
877 if (content.payload && shell_callbacks.payload) {
876 this._handle_payloads(content.payload, shell_callbacks.payload, reply);
878 this._handle_payloads(content.payload, shell_callbacks.payload, reply);
877 }
879 }
878 };
880 };
879
881
880 /**
882 /**
881 * @function _handle_payloads
883 * @function _handle_payloads
882 */
884 */
883 Kernel.prototype._handle_payloads = function (payloads, payload_callbacks, msg) {
885 Kernel.prototype._handle_payloads = function (payloads, payload_callbacks, msg) {
884 var l = payloads.length;
886 var l = payloads.length;
885 // Payloads are handled by triggering events because we don't want the Kernel
887 // Payloads are handled by triggering events because we don't want the Kernel
886 // to depend on the Notebook or Pager classes.
888 // to depend on the Notebook or Pager classes.
887 for (var i=0; i<l; i++) {
889 for (var i=0; i<l; i++) {
888 var payload = payloads[i];
890 var payload = payloads[i];
889 var callback = payload_callbacks[payload.source];
891 var callback = payload_callbacks[payload.source];
890 if (callback) {
892 if (callback) {
891 callback(payload, msg);
893 callback(payload, msg);
892 }
894 }
893 }
895 }
894 };
896 };
895
897
896 /**
898 /**
897 * @function _handle_status_message
899 * @function _handle_status_message
898 */
900 */
899 Kernel.prototype._handle_status_message = function (msg) {
901 Kernel.prototype._handle_status_message = function (msg) {
900 var execution_state = msg.content.execution_state;
902 var execution_state = msg.content.execution_state;
901 var parent_id = msg.parent_header.msg_id;
903 var parent_id = msg.parent_header.msg_id;
902
904
903 // dispatch status msg callbacks, if any
905 // dispatch status msg callbacks, if any
904 var callbacks = this.get_callbacks_for_msg(parent_id);
906 var callbacks = this.get_callbacks_for_msg(parent_id);
905 if (callbacks && callbacks.iopub && callbacks.iopub.status) {
907 if (callbacks && callbacks.iopub && callbacks.iopub.status) {
906 try {
908 try {
907 callbacks.iopub.status(msg);
909 callbacks.iopub.status(msg);
908 } catch (e) {
910 } catch (e) {
909 console.log("Exception in status msg handler", e, e.stack);
911 console.log("Exception in status msg handler", e, e.stack);
910 }
912 }
911 }
913 }
912
914
913 if (execution_state === 'busy') {
915 if (execution_state === 'busy') {
914 this.events.trigger('kernel_busy.Kernel', {kernel: this});
916 this.events.trigger('kernel_busy.Kernel', {kernel: this});
915
917
916 } else if (execution_state === 'idle') {
918 } else if (execution_state === 'idle') {
917 // signal that iopub callbacks are (probably) done
919 // signal that iopub callbacks are (probably) done
918 // async output may still arrive,
920 // async output may still arrive,
919 // but only for the most recent request
921 // but only for the most recent request
920 this._finish_iopub(parent_id);
922 this._finish_iopub(parent_id);
921
923
922 // trigger status_idle event
924 // trigger status_idle event
923 this.events.trigger('kernel_idle.Kernel', {kernel: this});
925 this.events.trigger('kernel_idle.Kernel', {kernel: this});
924
926
925 } else if (execution_state === 'starting') {
927 } else if (execution_state === 'starting') {
926 this.events.trigger('kernel_starting.Kernel', {kernel: this});
928 this.events.trigger('kernel_starting.Kernel', {kernel: this});
927 var that = this;
929 var that = this;
928 this.kernel_info(function () {
930 this.kernel_info(function (reply) {
931 that.info_reply = reply.content;
929 that.events.trigger('kernel_ready.Kernel', {kernel: that});
932 that.events.trigger('kernel_ready.Kernel', {kernel: that});
930 });
933 });
931
934
932 } else if (execution_state === 'restarting') {
935 } else if (execution_state === 'restarting') {
933 // autorestarting is distinct from restarting,
936 // autorestarting is distinct from restarting,
934 // in that it means the kernel died and the server is restarting it.
937 // in that it means the kernel died and the server is restarting it.
935 // kernel_restarting sets the notification widget,
938 // kernel_restarting sets the notification widget,
936 // autorestart shows the more prominent dialog.
939 // autorestart shows the more prominent dialog.
937 this._autorestart_attempt = this._autorestart_attempt + 1;
940 this._autorestart_attempt = this._autorestart_attempt + 1;
938 this.events.trigger('kernel_restarting.Kernel', {kernel: this});
941 this.events.trigger('kernel_restarting.Kernel', {kernel: this});
939 this.events.trigger('kernel_autorestarting.Kernel', {kernel: this, attempt: this._autorestart_attempt});
942 this.events.trigger('kernel_autorestarting.Kernel', {kernel: this, attempt: this._autorestart_attempt});
940
943
941 } else if (execution_state === 'dead') {
944 } else if (execution_state === 'dead') {
942 this.events.trigger('kernel_dead.Kernel', {kernel: this});
945 this.events.trigger('kernel_dead.Kernel', {kernel: this});
943 this._kernel_dead();
946 this._kernel_dead();
944 }
947 }
945 };
948 };
946
949
947 /**
950 /**
948 * Handle clear_output message
951 * Handle clear_output message
949 *
952 *
950 * @function _handle_clear_output
953 * @function _handle_clear_output
951 */
954 */
952 Kernel.prototype._handle_clear_output = function (msg) {
955 Kernel.prototype._handle_clear_output = function (msg) {
953 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
956 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
954 if (!callbacks || !callbacks.iopub) {
957 if (!callbacks || !callbacks.iopub) {
955 return;
958 return;
956 }
959 }
957 var callback = callbacks.iopub.clear_output;
960 var callback = callbacks.iopub.clear_output;
958 if (callback) {
961 if (callback) {
959 callback(msg);
962 callback(msg);
960 }
963 }
961 };
964 };
962
965
963 /**
966 /**
964 * handle an output message (execute_result, display_data, etc.)
967 * handle an output message (execute_result, display_data, etc.)
965 *
968 *
966 * @function _handle_output_message
969 * @function _handle_output_message
967 */
970 */
968 Kernel.prototype._handle_output_message = function (msg) {
971 Kernel.prototype._handle_output_message = function (msg) {
969 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
972 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
970 if (!callbacks || !callbacks.iopub) {
973 if (!callbacks || !callbacks.iopub) {
971 return;
974 return;
972 }
975 }
973 var callback = callbacks.iopub.output;
976 var callback = callbacks.iopub.output;
974 if (callback) {
977 if (callback) {
975 callback(msg);
978 callback(msg);
976 }
979 }
977 };
980 };
978
981
979 /**
982 /**
980 * Dispatch IOPub messages to respective handlers. Each message
983 * Dispatch IOPub messages to respective handlers. Each message
981 * type should have a handler.
984 * type should have a handler.
982 *
985 *
983 * @function _handle_iopub_message
986 * @function _handle_iopub_message
984 */
987 */
985 Kernel.prototype._handle_iopub_message = function (e) {
988 Kernel.prototype._handle_iopub_message = function (e) {
986 serialize.deserialize(e.data, $.proxy(this._finish_iopub_message, this));
989 serialize.deserialize(e.data, $.proxy(this._finish_iopub_message, this));
987 };
990 };
988
991
989
992
990 Kernel.prototype._finish_iopub_message = function (msg) {
993 Kernel.prototype._finish_iopub_message = function (msg) {
991 var handler = this.get_iopub_handler(msg.header.msg_type);
994 var handler = this.get_iopub_handler(msg.header.msg_type);
992 if (handler !== undefined) {
995 if (handler !== undefined) {
993 handler(msg);
996 handler(msg);
994 }
997 }
995 };
998 };
996
999
997 /**
1000 /**
998 * @function _handle_input_request
1001 * @function _handle_input_request
999 */
1002 */
1000 Kernel.prototype._handle_input_request = function (e) {
1003 Kernel.prototype._handle_input_request = function (e) {
1001 serialize.deserialize(e.data, $.proxy(this._finish_input_request, this));
1004 serialize.deserialize(e.data, $.proxy(this._finish_input_request, this));
1002 };
1005 };
1003
1006
1004
1007
1005 Kernel.prototype._finish_input_request = function (request) {
1008 Kernel.prototype._finish_input_request = function (request) {
1006 var header = request.header;
1009 var header = request.header;
1007 var content = request.content;
1010 var content = request.content;
1008 var metadata = request.metadata;
1011 var metadata = request.metadata;
1009 var msg_type = header.msg_type;
1012 var msg_type = header.msg_type;
1010 if (msg_type !== 'input_request') {
1013 if (msg_type !== 'input_request') {
1011 console.log("Invalid input request!", request);
1014 console.log("Invalid input request!", request);
1012 return;
1015 return;
1013 }
1016 }
1014 var callbacks = this.get_callbacks_for_msg(request.parent_header.msg_id);
1017 var callbacks = this.get_callbacks_for_msg(request.parent_header.msg_id);
1015 if (callbacks) {
1018 if (callbacks) {
1016 if (callbacks.input) {
1019 if (callbacks.input) {
1017 callbacks.input(request);
1020 callbacks.input(request);
1018 }
1021 }
1019 }
1022 }
1020 };
1023 };
1021
1024
1022 // Backwards compatability.
1025 // Backwards compatability.
1023 IPython.Kernel = Kernel;
1026 IPython.Kernel = Kernel;
1024
1027
1025 return {'Kernel': Kernel};
1028 return {'Kernel': Kernel};
1026 });
1029 });
@@ -1,231 +1,213 b''
1 import io
1 import io
2 import json
2 import json
3 import os
3 import os
4 import shutil
4 import shutil
5 import sys
5 import sys
6
6
7 pjoin = os.path.join
7 pjoin = os.path.join
8
8
9 from IPython.utils.path import get_ipython_dir
9 from IPython.utils.path import get_ipython_dir
10 from IPython.utils.py3compat import PY3
10 from IPython.utils.py3compat import PY3
11 from IPython.utils.traitlets import HasTraits, List, Unicode, Dict, Any
11 from IPython.utils.traitlets import HasTraits, List, Unicode, Dict, Any
12 from .launcher import make_ipkernel_cmd
12 from .launcher import make_ipkernel_cmd
13
13
14 if os.name == 'nt':
14 if os.name == 'nt':
15 programdata = os.environ.get('PROGRAMDATA', None)
15 programdata = os.environ.get('PROGRAMDATA', None)
16 if programdata:
16 if programdata:
17 SYSTEM_KERNEL_DIRS = [pjoin(programdata, 'ipython', 'kernels')]
17 SYSTEM_KERNEL_DIRS = [pjoin(programdata, 'ipython', 'kernels')]
18 else: # PROGRAMDATA is not defined by default on XP.
18 else: # PROGRAMDATA is not defined by default on XP.
19 SYSTEM_KERNEL_DIRS = []
19 SYSTEM_KERNEL_DIRS = []
20 else:
20 else:
21 SYSTEM_KERNEL_DIRS = ["/usr/share/ipython/kernels",
21 SYSTEM_KERNEL_DIRS = ["/usr/share/ipython/kernels",
22 "/usr/local/share/ipython/kernels",
22 "/usr/local/share/ipython/kernels",
23 ]
23 ]
24
24
25 NATIVE_KERNEL_NAME = 'python3' if PY3 else 'python2'
25 NATIVE_KERNEL_NAME = 'python3' if PY3 else 'python2'
26
26
27 def _pythonfirst(s):
27 def _pythonfirst(s):
28 "Sort key function that will put strings starting with 'python' first."
28 "Sort key function that will put strings starting with 'python' first."
29 if s == NATIVE_KERNEL_NAME:
29 if s == NATIVE_KERNEL_NAME:
30 return ' ' + s # Two spaces to sort this first of all
30 return ' ' + s # Two spaces to sort this first of all
31 elif s.startswith('python'):
31 elif s.startswith('python'):
32 # Space is not valid in kernel names, so this should sort first
32 # Space is not valid in kernel names, so this should sort first
33 return ' ' + s
33 return ' ' + s
34 return s
34 return s
35
35
36 class KernelSpec(HasTraits):
36 class KernelSpec(HasTraits):
37 argv = List()
37 argv = List()
38 display_name = Unicode()
38 display_name = Unicode()
39 language = Unicode()
40 codemirror_mode = Any() # can be unicode or dict
41 pygments_lexer = Unicode()
42 env = Dict()
39 env = Dict()
43 resource_dir = Unicode()
40 resource_dir = Unicode()
44
41
45 def _codemirror_mode_default(self):
46 return self.language
47
48 def _pygments_lexer_default(self):
49 return self.language
50
51 @classmethod
42 @classmethod
52 def from_resource_dir(cls, resource_dir):
43 def from_resource_dir(cls, resource_dir):
53 """Create a KernelSpec object by reading kernel.json
44 """Create a KernelSpec object by reading kernel.json
54
45
55 Pass the path to the *directory* containing kernel.json.
46 Pass the path to the *directory* containing kernel.json.
56 """
47 """
57 kernel_file = pjoin(resource_dir, 'kernel.json')
48 kernel_file = pjoin(resource_dir, 'kernel.json')
58 with io.open(kernel_file, 'r', encoding='utf-8') as f:
49 with io.open(kernel_file, 'r', encoding='utf-8') as f:
59 kernel_dict = json.load(f)
50 kernel_dict = json.load(f)
60 return cls(resource_dir=resource_dir, **kernel_dict)
51 return cls(resource_dir=resource_dir, **kernel_dict)
61
52
62 def to_dict(self):
53 def to_dict(self):
63 d = dict(argv=self.argv,
54 d = dict(argv=self.argv,
64 env=self.env,
55 env=self.env,
65 display_name=self.display_name,
56 display_name=self.display_name,
66 language=self.language,
67 )
57 )
68 if self.codemirror_mode != self.language:
69 d['codemirror_mode'] = self.codemirror_mode
70 if self.pygments_lexer != self.language:
71 d['pygments_lexer'] = self.pygments_lexer
72
58
73 return d
59 return d
74
60
75 def to_json(self):
61 def to_json(self):
76 return json.dumps(self.to_dict())
62 return json.dumps(self.to_dict())
77
63
78 def _is_kernel_dir(path):
64 def _is_kernel_dir(path):
79 """Is ``path`` a kernel directory?"""
65 """Is ``path`` a kernel directory?"""
80 return os.path.isdir(path) and os.path.isfile(pjoin(path, 'kernel.json'))
66 return os.path.isdir(path) and os.path.isfile(pjoin(path, 'kernel.json'))
81
67
82 def _list_kernels_in(dir):
68 def _list_kernels_in(dir):
83 """Return a mapping of kernel names to resource directories from dir.
69 """Return a mapping of kernel names to resource directories from dir.
84
70
85 If dir is None or does not exist, returns an empty dict.
71 If dir is None or does not exist, returns an empty dict.
86 """
72 """
87 if dir is None or not os.path.isdir(dir):
73 if dir is None or not os.path.isdir(dir):
88 return {}
74 return {}
89 return {f.lower(): pjoin(dir, f) for f in os.listdir(dir)
75 return {f.lower(): pjoin(dir, f) for f in os.listdir(dir)
90 if _is_kernel_dir(pjoin(dir, f))}
76 if _is_kernel_dir(pjoin(dir, f))}
91
77
92 class NoSuchKernel(KeyError):
78 class NoSuchKernel(KeyError):
93 def __init__(self, name):
79 def __init__(self, name):
94 self.name = name
80 self.name = name
95
81
96 class KernelSpecManager(HasTraits):
82 class KernelSpecManager(HasTraits):
97 ipython_dir = Unicode()
83 ipython_dir = Unicode()
98 def _ipython_dir_default(self):
84 def _ipython_dir_default(self):
99 return get_ipython_dir()
85 return get_ipython_dir()
100
86
101 user_kernel_dir = Unicode()
87 user_kernel_dir = Unicode()
102 def _user_kernel_dir_default(self):
88 def _user_kernel_dir_default(self):
103 return pjoin(self.ipython_dir, 'kernels')
89 return pjoin(self.ipython_dir, 'kernels')
104
90
105 kernel_dirs = List(
91 kernel_dirs = List(
106 help="List of kernel directories to search. Later ones take priority over earlier."
92 help="List of kernel directories to search. Later ones take priority over earlier."
107 )
93 )
108 def _kernel_dirs_default(self):
94 def _kernel_dirs_default(self):
109 return SYSTEM_KERNEL_DIRS + [
95 return SYSTEM_KERNEL_DIRS + [
110 self.user_kernel_dir,
96 self.user_kernel_dir,
111 ]
97 ]
112
98
113 @property
99 @property
114 def _native_kernel_dict(self):
100 def _native_kernel_dict(self):
115 """Makes a kernel directory for the native kernel.
101 """Makes a kernel directory for the native kernel.
116
102
117 The native kernel is the kernel using the same Python runtime as this
103 The native kernel is the kernel using the same Python runtime as this
118 process. This will put its informatino in the user kernels directory.
104 process. This will put its informatino in the user kernels directory.
119 """
105 """
120 return {'argv': make_ipkernel_cmd(),
106 return {'argv': make_ipkernel_cmd(),
121 'display_name': 'IPython (Python %d)' % (3 if PY3 else 2),
107 'display_name': 'IPython (Python %d)' % (3 if PY3 else 2),
122 'language': 'python',
123 'codemirror_mode': {'name': 'ipython',
124 'version': sys.version_info[0]},
125 'pygments_lexer': 'ipython%d' % (3 if PY3 else 2),
126 }
108 }
127
109
128 @property
110 @property
129 def _native_kernel_resource_dir(self):
111 def _native_kernel_resource_dir(self):
130 # TODO: This may be different when we actually have any resources
112 # TODO: This may be different when we actually have any resources
131 return os.path.dirname(__file__)
113 return os.path.dirname(__file__)
132
114
133 def find_kernel_specs(self):
115 def find_kernel_specs(self):
134 """Returns a dict mapping kernel names to resource directories."""
116 """Returns a dict mapping kernel names to resource directories."""
135 d = {}
117 d = {}
136 for kernel_dir in self.kernel_dirs:
118 for kernel_dir in self.kernel_dirs:
137 d.update(_list_kernels_in(kernel_dir))
119 d.update(_list_kernels_in(kernel_dir))
138
120
139 d[NATIVE_KERNEL_NAME] = self._native_kernel_resource_dir
121 d[NATIVE_KERNEL_NAME] = self._native_kernel_resource_dir
140 return d
122 return d
141 # TODO: Caching?
123 # TODO: Caching?
142
124
143 def get_kernel_spec(self, kernel_name):
125 def get_kernel_spec(self, kernel_name):
144 """Returns a :class:`KernelSpec` instance for the given kernel_name.
126 """Returns a :class:`KernelSpec` instance for the given kernel_name.
145
127
146 Raises :exc:`NoSuchKernel` if the given kernel name is not found.
128 Raises :exc:`NoSuchKernel` if the given kernel name is not found.
147 """
129 """
148 if kernel_name in {'python', NATIVE_KERNEL_NAME}:
130 if kernel_name in {'python', NATIVE_KERNEL_NAME}:
149 return KernelSpec(self._native_kernel_resource_dir, **self._native_kernel_dict)
131 return KernelSpec(self._native_kernel_resource_dir, **self._native_kernel_dict)
150
132
151 d = self.find_kernel_specs()
133 d = self.find_kernel_specs()
152 try:
134 try:
153 resource_dir = d[kernel_name.lower()]
135 resource_dir = d[kernel_name.lower()]
154 except KeyError:
136 except KeyError:
155 raise NoSuchKernel(kernel_name)
137 raise NoSuchKernel(kernel_name)
156 return KernelSpec.from_resource_dir(resource_dir)
138 return KernelSpec.from_resource_dir(resource_dir)
157
139
158 def _get_destination_dir(self, kernel_name, system=False):
140 def _get_destination_dir(self, kernel_name, system=False):
159 if system:
141 if system:
160 if SYSTEM_KERNEL_DIRS:
142 if SYSTEM_KERNEL_DIRS:
161 return os.path.join(SYSTEM_KERNEL_DIRS[-1], kernel_name)
143 return os.path.join(SYSTEM_KERNEL_DIRS[-1], kernel_name)
162 else:
144 else:
163 raise EnvironmentError("No system kernel directory is available")
145 raise EnvironmentError("No system kernel directory is available")
164 else:
146 else:
165 return os.path.join(self.user_kernel_dir, kernel_name)
147 return os.path.join(self.user_kernel_dir, kernel_name)
166
148
167 def install_kernel_spec(self, source_dir, kernel_name=None, system=False,
149 def install_kernel_spec(self, source_dir, kernel_name=None, system=False,
168 replace=False):
150 replace=False):
169 """Install a kernel spec by copying its directory.
151 """Install a kernel spec by copying its directory.
170
152
171 If ``kernel_name`` is not given, the basename of ``source_dir`` will
153 If ``kernel_name`` is not given, the basename of ``source_dir`` will
172 be used.
154 be used.
173
155
174 If ``system`` is True, it will attempt to install into the systemwide
156 If ``system`` is True, it will attempt to install into the systemwide
175 kernel registry. If the process does not have appropriate permissions,
157 kernel registry. If the process does not have appropriate permissions,
176 an :exc:`OSError` will be raised.
158 an :exc:`OSError` will be raised.
177
159
178 If ``replace`` is True, this will replace an existing kernel of the same
160 If ``replace`` is True, this will replace an existing kernel of the same
179 name. Otherwise, if the destination already exists, an :exc:`OSError`
161 name. Otherwise, if the destination already exists, an :exc:`OSError`
180 will be raised.
162 will be raised.
181 """
163 """
182 if not kernel_name:
164 if not kernel_name:
183 kernel_name = os.path.basename(source_dir)
165 kernel_name = os.path.basename(source_dir)
184 kernel_name = kernel_name.lower()
166 kernel_name = kernel_name.lower()
185
167
186 destination = self._get_destination_dir(kernel_name, system=system)
168 destination = self._get_destination_dir(kernel_name, system=system)
187
169
188 if replace and os.path.isdir(destination):
170 if replace and os.path.isdir(destination):
189 shutil.rmtree(destination)
171 shutil.rmtree(destination)
190
172
191 shutil.copytree(source_dir, destination)
173 shutil.copytree(source_dir, destination)
192
174
193 def install_native_kernel_spec(self, system=False):
175 def install_native_kernel_spec(self, system=False):
194 """Install the native kernel spec to the filesystem
176 """Install the native kernel spec to the filesystem
195
177
196 This allows a Python 3 frontend to use a Python 2 kernel, or vice versa.
178 This allows a Python 3 frontend to use a Python 2 kernel, or vice versa.
197 The kernelspec will be written pointing to the Python executable on
179 The kernelspec will be written pointing to the Python executable on
198 which this is run.
180 which this is run.
199
181
200 If ``system`` is True, it will attempt to install into the systemwide
182 If ``system`` is True, it will attempt to install into the systemwide
201 kernel registry. If the process does not have appropriate permissions,
183 kernel registry. If the process does not have appropriate permissions,
202 an :exc:`OSError` will be raised.
184 an :exc:`OSError` will be raised.
203 """
185 """
204 path = self._get_destination_dir(NATIVE_KERNEL_NAME, system=system)
186 path = self._get_destination_dir(NATIVE_KERNEL_NAME, system=system)
205 os.makedirs(path, mode=0o755)
187 os.makedirs(path, mode=0o755)
206 with open(pjoin(path, 'kernel.json'), 'w') as f:
188 with open(pjoin(path, 'kernel.json'), 'w') as f:
207 json.dump(self._native_kernel_dict, f, indent=1)
189 json.dump(self._native_kernel_dict, f, indent=1)
208 # TODO: Copy icons into directory
190 # TODO: Copy icons into directory
209 return path
191 return path
210
192
211 def find_kernel_specs():
193 def find_kernel_specs():
212 """Returns a dict mapping kernel names to resource directories."""
194 """Returns a dict mapping kernel names to resource directories."""
213 return KernelSpecManager().find_kernel_specs()
195 return KernelSpecManager().find_kernel_specs()
214
196
215 def get_kernel_spec(kernel_name):
197 def get_kernel_spec(kernel_name):
216 """Returns a :class:`KernelSpec` instance for the given kernel_name.
198 """Returns a :class:`KernelSpec` instance for the given kernel_name.
217
199
218 Raises KeyError if the given kernel name is not found.
200 Raises KeyError if the given kernel name is not found.
219 """
201 """
220 return KernelSpecManager().get_kernel_spec(kernel_name)
202 return KernelSpecManager().get_kernel_spec(kernel_name)
221
203
222 def install_kernel_spec(source_dir, kernel_name=None, system=False, replace=False):
204 def install_kernel_spec(source_dir, kernel_name=None, system=False, replace=False):
223 return KernelSpecManager().install_kernel_spec(source_dir, kernel_name,
205 return KernelSpecManager().install_kernel_spec(source_dir, kernel_name,
224 system, replace)
206 system, replace)
225
207
226 install_kernel_spec.__doc__ = KernelSpecManager.install_kernel_spec.__doc__
208 install_kernel_spec.__doc__ = KernelSpecManager.install_kernel_spec.__doc__
227
209
228 def install_native_kernel_spec(self, system=False):
210 def install_native_kernel_spec(self, system=False):
229 return KernelSpecManager().install_native_kernel_spec(system=system)
211 return KernelSpecManager().install_native_kernel_spec(system=system)
230
212
231 install_native_kernel_spec.__doc__ = KernelSpecManager.install_native_kernel_spec.__doc__
213 install_native_kernel_spec.__doc__ = KernelSpecManager.install_native_kernel_spec.__doc__
@@ -1,65 +1,62 b''
1 import json
1 import json
2 import os
2 import os
3 from os.path import join as pjoin
3 from os.path import join as pjoin
4 import unittest
4 import unittest
5
5
6 from IPython.testing.decorators import onlyif
6 from IPython.testing.decorators import onlyif
7 from IPython.utils.tempdir import TemporaryDirectory
7 from IPython.utils.tempdir import TemporaryDirectory
8 from IPython.kernel import kernelspec
8 from IPython.kernel import kernelspec
9
9
10 sample_kernel_json = {'argv':['cat', '{connection_file}'],
10 sample_kernel_json = {'argv':['cat', '{connection_file}'],
11 'display_name':'Test kernel',
11 'display_name':'Test kernel',
12 'language':'bash',
13 }
12 }
14
13
15 class KernelSpecTests(unittest.TestCase):
14 class KernelSpecTests(unittest.TestCase):
16 def setUp(self):
15 def setUp(self):
17 td = TemporaryDirectory()
16 td = TemporaryDirectory()
18 self.addCleanup(td.cleanup)
17 self.addCleanup(td.cleanup)
19 self.sample_kernel_dir = pjoin(td.name, 'kernels', 'Sample')
18 self.sample_kernel_dir = pjoin(td.name, 'kernels', 'Sample')
20 os.makedirs(self.sample_kernel_dir)
19 os.makedirs(self.sample_kernel_dir)
21 json_file = pjoin(self.sample_kernel_dir, 'kernel.json')
20 json_file = pjoin(self.sample_kernel_dir, 'kernel.json')
22 with open(json_file, 'w') as f:
21 with open(json_file, 'w') as f:
23 json.dump(sample_kernel_json, f)
22 json.dump(sample_kernel_json, f)
24
23
25 self.ksm = kernelspec.KernelSpecManager(ipython_dir=td.name)
24 self.ksm = kernelspec.KernelSpecManager(ipython_dir=td.name)
26
25
27 td2 = TemporaryDirectory()
26 td2 = TemporaryDirectory()
28 self.addCleanup(td2.cleanup)
27 self.addCleanup(td2.cleanup)
29 self.installable_kernel = td2.name
28 self.installable_kernel = td2.name
30 with open(pjoin(self.installable_kernel, 'kernel.json'), 'w') as f:
29 with open(pjoin(self.installable_kernel, 'kernel.json'), 'w') as f:
31 json.dump(sample_kernel_json, f)
30 json.dump(sample_kernel_json, f)
32
31
33 def test_find_kernel_specs(self):
32 def test_find_kernel_specs(self):
34 kernels = self.ksm.find_kernel_specs()
33 kernels = self.ksm.find_kernel_specs()
35 self.assertEqual(kernels['sample'], self.sample_kernel_dir)
34 self.assertEqual(kernels['sample'], self.sample_kernel_dir)
36
35
37 def test_get_kernel_spec(self):
36 def test_get_kernel_spec(self):
38 ks = self.ksm.get_kernel_spec('SAMPLE') # Case insensitive
37 ks = self.ksm.get_kernel_spec('SAMPLE') # Case insensitive
39 self.assertEqual(ks.resource_dir, self.sample_kernel_dir)
38 self.assertEqual(ks.resource_dir, self.sample_kernel_dir)
40 self.assertEqual(ks.argv, sample_kernel_json['argv'])
39 self.assertEqual(ks.argv, sample_kernel_json['argv'])
41 self.assertEqual(ks.display_name, sample_kernel_json['display_name'])
40 self.assertEqual(ks.display_name, sample_kernel_json['display_name'])
42 self.assertEqual(ks.language, sample_kernel_json['language'])
43 self.assertEqual(ks.codemirror_mode, sample_kernel_json['language'])
44 self.assertEqual(ks.env, {})
41 self.assertEqual(ks.env, {})
45
42
46 def test_install_kernel_spec(self):
43 def test_install_kernel_spec(self):
47 self.ksm.install_kernel_spec(self.installable_kernel,
44 self.ksm.install_kernel_spec(self.installable_kernel,
48 kernel_name='tstinstalled')
45 kernel_name='tstinstalled')
49 self.assertIn('tstinstalled', self.ksm.find_kernel_specs())
46 self.assertIn('tstinstalled', self.ksm.find_kernel_specs())
50
47
51 with self.assertRaises(OSError):
48 with self.assertRaises(OSError):
52 self.ksm.install_kernel_spec(self.installable_kernel,
49 self.ksm.install_kernel_spec(self.installable_kernel,
53 kernel_name='tstinstalled')
50 kernel_name='tstinstalled')
54
51
55 # Smoketest that this succeeds
52 # Smoketest that this succeeds
56 self.ksm.install_kernel_spec(self.installable_kernel,
53 self.ksm.install_kernel_spec(self.installable_kernel,
57 kernel_name='tstinstalled',
54 kernel_name='tstinstalled',
58 replace=True)
55 replace=True)
59
56
60 @onlyif(os.name != 'nt' and not os.access('/usr/local/share', os.W_OK), "needs Unix system without root privileges")
57 @onlyif(os.name != 'nt' and not os.access('/usr/local/share', os.W_OK), "needs Unix system without root privileges")
61 def test_cant_install_kernel_spec(self):
58 def test_cant_install_kernel_spec(self):
62 with self.assertRaises(OSError):
59 with self.assertRaises(OSError):
63 self.ksm.install_kernel_spec(self.installable_kernel,
60 self.ksm.install_kernel_spec(self.installable_kernel,
64 kernel_name='tstinstalled',
61 kernel_name='tstinstalled',
65 system=True)
62 system=True)
@@ -1,428 +1,429 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 from distutils.version import LooseVersion as V
7 from distutils.version import LooseVersion as V
8 from subprocess import PIPE
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
16 from IPython.kernel import KernelManager
17
17
18 from IPython.utils.traitlets import (
18 from IPython.utils.traitlets import (
19 HasTraits, TraitError, Bool, Unicode, Dict, Integer, List, Enum, Any,
19 HasTraits, TraitError, Bool, Unicode, Dict, Integer, List, Enum, Any,
20 )
20 )
21 from IPython.utils.py3compat import string_types, iteritems
21 from IPython.utils.py3compat import string_types, iteritems
22
22
23 from .utils import TIMEOUT, start_global_kernel, flush_channels, execute
23 from .utils import TIMEOUT, start_global_kernel, flush_channels, execute
24
24
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26 # Globals
26 # Globals
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 KC = None
28 KC = None
29
29
30 def setup():
30 def setup():
31 global KC
31 global KC
32 KC = start_global_kernel()
32 KC = start_global_kernel()
33
33
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35 # Message Spec References
35 # Message Spec References
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37
37
38 class Reference(HasTraits):
38 class Reference(HasTraits):
39
39
40 """
40 """
41 Base class for message spec specification testing.
41 Base class for message spec specification testing.
42
42
43 This class is the core of the message specification test. The
43 This class is the core of the message specification test. The
44 idea is that child classes implement trait attributes for each
44 idea is that child classes implement trait attributes for each
45 message keys, so that message keys can be tested against these
45 message keys, so that message keys can be tested against these
46 traits using :meth:`check` method.
46 traits using :meth:`check` method.
47
47
48 """
48 """
49
49
50 def check(self, d):
50 def check(self, d):
51 """validate a dict against our traits"""
51 """validate a dict against our traits"""
52 for key in self.trait_names():
52 for key in self.trait_names():
53 nt.assert_in(key, d)
53 nt.assert_in(key, d)
54 # FIXME: always allow None, probably not a good idea
54 # FIXME: always allow None, probably not a good idea
55 if d[key] is None:
55 if d[key] is None:
56 continue
56 continue
57 try:
57 try:
58 setattr(self, key, d[key])
58 setattr(self, key, d[key])
59 except TraitError as e:
59 except TraitError as e:
60 assert False, str(e)
60 assert False, str(e)
61
61
62
62
63 class Version(Unicode):
63 class Version(Unicode):
64 def __init__(self, *args, **kwargs):
64 def __init__(self, *args, **kwargs):
65 self.min = kwargs.pop('min', None)
65 self.min = kwargs.pop('min', None)
66 self.max = kwargs.pop('max', None)
66 self.max = kwargs.pop('max', None)
67 kwargs['default_value'] = self.min
67 kwargs['default_value'] = self.min
68 super(Version, self).__init__(*args, **kwargs)
68 super(Version, self).__init__(*args, **kwargs)
69
69
70 def validate(self, obj, value):
70 def validate(self, obj, value):
71 if self.min and V(value) < V(self.min):
71 if self.min and V(value) < V(self.min):
72 raise TraitError("bad version: %s < %s" % (value, self.min))
72 raise TraitError("bad version: %s < %s" % (value, self.min))
73 if self.max and (V(value) > V(self.max)):
73 if self.max and (V(value) > V(self.max)):
74 raise TraitError("bad version: %s > %s" % (value, self.max))
74 raise TraitError("bad version: %s > %s" % (value, self.max))
75
75
76
76
77 class RMessage(Reference):
77 class RMessage(Reference):
78 msg_id = Unicode()
78 msg_id = Unicode()
79 msg_type = Unicode()
79 msg_type = Unicode()
80 header = Dict()
80 header = Dict()
81 parent_header = Dict()
81 parent_header = Dict()
82 content = Dict()
82 content = Dict()
83
83
84 def check(self, d):
84 def check(self, d):
85 super(RMessage, self).check(d)
85 super(RMessage, self).check(d)
86 RHeader().check(self.header)
86 RHeader().check(self.header)
87 if self.parent_header:
87 if self.parent_header:
88 RHeader().check(self.parent_header)
88 RHeader().check(self.parent_header)
89
89
90 class RHeader(Reference):
90 class RHeader(Reference):
91 msg_id = Unicode()
91 msg_id = Unicode()
92 msg_type = Unicode()
92 msg_type = Unicode()
93 session = Unicode()
93 session = Unicode()
94 username = Unicode()
94 username = Unicode()
95 version = Version(min='5.0')
95 version = Version(min='5.0')
96
96
97 mime_pat = re.compile(r'^[\w\-\+\.]+/[\w\-\+\.]+$')
97 mime_pat = re.compile(r'^[\w\-\+\.]+/[\w\-\+\.]+$')
98
98
99 class MimeBundle(Reference):
99 class MimeBundle(Reference):
100 metadata = Dict()
100 metadata = Dict()
101 data = Dict()
101 data = Dict()
102 def _data_changed(self, name, old, new):
102 def _data_changed(self, name, old, new):
103 for k,v in iteritems(new):
103 for k,v in iteritems(new):
104 assert mime_pat.match(k)
104 assert mime_pat.match(k)
105 nt.assert_is_instance(v, string_types)
105 nt.assert_is_instance(v, string_types)
106
106
107 # shell replies
107 # shell replies
108
108
109 class ExecuteReply(Reference):
109 class ExecuteReply(Reference):
110 execution_count = Integer()
110 execution_count = Integer()
111 status = Enum((u'ok', u'error'))
111 status = Enum((u'ok', u'error'))
112
112
113 def check(self, d):
113 def check(self, d):
114 Reference.check(self, d)
114 Reference.check(self, d)
115 if d['status'] == 'ok':
115 if d['status'] == 'ok':
116 ExecuteReplyOkay().check(d)
116 ExecuteReplyOkay().check(d)
117 elif d['status'] == 'error':
117 elif d['status'] == 'error':
118 ExecuteReplyError().check(d)
118 ExecuteReplyError().check(d)
119
119
120
120
121 class ExecuteReplyOkay(Reference):
121 class ExecuteReplyOkay(Reference):
122 payload = List(Dict)
122 payload = List(Dict)
123 user_expressions = Dict()
123 user_expressions = Dict()
124
124
125
125
126 class ExecuteReplyError(Reference):
126 class ExecuteReplyError(Reference):
127 ename = Unicode()
127 ename = Unicode()
128 evalue = Unicode()
128 evalue = Unicode()
129 traceback = List(Unicode)
129 traceback = List(Unicode)
130
130
131
131
132 class InspectReply(MimeBundle):
132 class InspectReply(MimeBundle):
133 found = Bool()
133 found = Bool()
134
134
135
135
136 class ArgSpec(Reference):
136 class ArgSpec(Reference):
137 args = List(Unicode)
137 args = List(Unicode)
138 varargs = Unicode()
138 varargs = Unicode()
139 varkw = Unicode()
139 varkw = Unicode()
140 defaults = List()
140 defaults = List()
141
141
142
142
143 class Status(Reference):
143 class Status(Reference):
144 execution_state = Enum((u'busy', u'idle', u'starting'))
144 execution_state = Enum((u'busy', u'idle', u'starting'))
145
145
146
146
147 class CompleteReply(Reference):
147 class CompleteReply(Reference):
148 matches = List(Unicode)
148 matches = List(Unicode)
149 cursor_start = Integer()
149 cursor_start = Integer()
150 cursor_end = Integer()
150 cursor_end = Integer()
151 status = Unicode()
151 status = Unicode()
152
152
153
153
154 class KernelInfoReply(Reference):
154 class KernelInfoReply(Reference):
155 protocol_version = Version(min='5.0')
155 protocol_version = Version(min='5.0')
156 implementation = Unicode('ipython')
156 implementation = Unicode('ipython')
157 implementation_version = Version(min='2.1')
157 implementation_version = Version(min='2.1')
158 language_version = Version(min='2.7')
158 language_version = Version(min='2.7')
159 language = Unicode('python')
159 language = Unicode('python')
160 language_info = Dict()
160 banner = Unicode()
161 banner = Unicode()
161
162
162
163
163 class IsCompleteReply(Reference):
164 class IsCompleteReply(Reference):
164 status = Enum((u'complete', u'incomplete', u'invalid', u'unknown'))
165 status = Enum((u'complete', u'incomplete', u'invalid', u'unknown'))
165
166
166 def check(self, d):
167 def check(self, d):
167 Reference.check(self, d)
168 Reference.check(self, d)
168 if d['status'] == 'incomplete':
169 if d['status'] == 'incomplete':
169 IsCompleteReplyIncomplete().check(d)
170 IsCompleteReplyIncomplete().check(d)
170
171
171 class IsCompleteReplyIncomplete(Reference):
172 class IsCompleteReplyIncomplete(Reference):
172 indent = Unicode()
173 indent = Unicode()
173
174
174
175
175 # IOPub messages
176 # IOPub messages
176
177
177 class ExecuteInput(Reference):
178 class ExecuteInput(Reference):
178 code = Unicode()
179 code = Unicode()
179 execution_count = Integer()
180 execution_count = Integer()
180
181
181
182
182 Error = ExecuteReplyError
183 Error = ExecuteReplyError
183
184
184
185
185 class Stream(Reference):
186 class Stream(Reference):
186 name = Enum((u'stdout', u'stderr'))
187 name = Enum((u'stdout', u'stderr'))
187 text = Unicode()
188 text = Unicode()
188
189
189
190
190 class DisplayData(MimeBundle):
191 class DisplayData(MimeBundle):
191 pass
192 pass
192
193
193
194
194 class ExecuteResult(MimeBundle):
195 class ExecuteResult(MimeBundle):
195 execution_count = Integer()
196 execution_count = Integer()
196
197
197
198
198 references = {
199 references = {
199 'execute_reply' : ExecuteReply(),
200 'execute_reply' : ExecuteReply(),
200 'inspect_reply' : InspectReply(),
201 'inspect_reply' : InspectReply(),
201 'status' : Status(),
202 'status' : Status(),
202 'complete_reply' : CompleteReply(),
203 'complete_reply' : CompleteReply(),
203 'kernel_info_reply': KernelInfoReply(),
204 'kernel_info_reply': KernelInfoReply(),
204 'is_complete_reply': IsCompleteReply(),
205 'is_complete_reply': IsCompleteReply(),
205 'execute_input' : ExecuteInput(),
206 'execute_input' : ExecuteInput(),
206 'execute_result' : ExecuteResult(),
207 'execute_result' : ExecuteResult(),
207 'error' : Error(),
208 'error' : Error(),
208 'stream' : Stream(),
209 'stream' : Stream(),
209 'display_data' : DisplayData(),
210 'display_data' : DisplayData(),
210 'header' : RHeader(),
211 'header' : RHeader(),
211 }
212 }
212 """
213 """
213 Specifications of `content` part of the reply messages.
214 Specifications of `content` part of the reply messages.
214 """
215 """
215
216
216
217
217 def validate_message(msg, msg_type=None, parent=None):
218 def validate_message(msg, msg_type=None, parent=None):
218 """validate a message
219 """validate a message
219
220
220 This is a generator, and must be iterated through to actually
221 This is a generator, and must be iterated through to actually
221 trigger each test.
222 trigger each test.
222
223
223 If msg_type and/or parent are given, the msg_type and/or parent msg_id
224 If msg_type and/or parent are given, the msg_type and/or parent msg_id
224 are compared with the given values.
225 are compared with the given values.
225 """
226 """
226 RMessage().check(msg)
227 RMessage().check(msg)
227 if msg_type:
228 if msg_type:
228 nt.assert_equal(msg['msg_type'], msg_type)
229 nt.assert_equal(msg['msg_type'], msg_type)
229 if parent:
230 if parent:
230 nt.assert_equal(msg['parent_header']['msg_id'], parent)
231 nt.assert_equal(msg['parent_header']['msg_id'], parent)
231 content = msg['content']
232 content = msg['content']
232 ref = references[msg['msg_type']]
233 ref = references[msg['msg_type']]
233 ref.check(content)
234 ref.check(content)
234
235
235
236
236 #-----------------------------------------------------------------------------
237 #-----------------------------------------------------------------------------
237 # Tests
238 # Tests
238 #-----------------------------------------------------------------------------
239 #-----------------------------------------------------------------------------
239
240
240 # Shell channel
241 # Shell channel
241
242
242 def test_execute():
243 def test_execute():
243 flush_channels()
244 flush_channels()
244
245
245 msg_id = KC.execute(code='x=1')
246 msg_id = KC.execute(code='x=1')
246 reply = KC.get_shell_msg(timeout=TIMEOUT)
247 reply = KC.get_shell_msg(timeout=TIMEOUT)
247 validate_message(reply, 'execute_reply', msg_id)
248 validate_message(reply, 'execute_reply', msg_id)
248
249
249
250
250 def test_execute_silent():
251 def test_execute_silent():
251 flush_channels()
252 flush_channels()
252 msg_id, reply = execute(code='x=1', silent=True)
253 msg_id, reply = execute(code='x=1', silent=True)
253
254
254 # flush status=idle
255 # flush status=idle
255 status = KC.iopub_channel.get_msg(timeout=TIMEOUT)
256 status = KC.iopub_channel.get_msg(timeout=TIMEOUT)
256 validate_message(status, 'status', msg_id)
257 validate_message(status, 'status', msg_id)
257 nt.assert_equal(status['content']['execution_state'], 'idle')
258 nt.assert_equal(status['content']['execution_state'], 'idle')
258
259
259 nt.assert_raises(Empty, KC.iopub_channel.get_msg, timeout=0.1)
260 nt.assert_raises(Empty, KC.iopub_channel.get_msg, timeout=0.1)
260 count = reply['execution_count']
261 count = reply['execution_count']
261
262
262 msg_id, reply = execute(code='x=2', silent=True)
263 msg_id, reply = execute(code='x=2', silent=True)
263
264
264 # flush status=idle
265 # flush status=idle
265 status = KC.iopub_channel.get_msg(timeout=TIMEOUT)
266 status = KC.iopub_channel.get_msg(timeout=TIMEOUT)
266 validate_message(status, 'status', msg_id)
267 validate_message(status, 'status', msg_id)
267 nt.assert_equal(status['content']['execution_state'], 'idle')
268 nt.assert_equal(status['content']['execution_state'], 'idle')
268
269
269 nt.assert_raises(Empty, KC.iopub_channel.get_msg, timeout=0.1)
270 nt.assert_raises(Empty, KC.iopub_channel.get_msg, timeout=0.1)
270 count_2 = reply['execution_count']
271 count_2 = reply['execution_count']
271 nt.assert_equal(count_2, count)
272 nt.assert_equal(count_2, count)
272
273
273
274
274 def test_execute_error():
275 def test_execute_error():
275 flush_channels()
276 flush_channels()
276
277
277 msg_id, reply = execute(code='1/0')
278 msg_id, reply = execute(code='1/0')
278 nt.assert_equal(reply['status'], 'error')
279 nt.assert_equal(reply['status'], 'error')
279 nt.assert_equal(reply['ename'], 'ZeroDivisionError')
280 nt.assert_equal(reply['ename'], 'ZeroDivisionError')
280
281
281 error = KC.iopub_channel.get_msg(timeout=TIMEOUT)
282 error = KC.iopub_channel.get_msg(timeout=TIMEOUT)
282 validate_message(error, 'error', msg_id)
283 validate_message(error, 'error', msg_id)
283
284
284
285
285 def test_execute_inc():
286 def test_execute_inc():
286 """execute request should increment execution_count"""
287 """execute request should increment execution_count"""
287 flush_channels()
288 flush_channels()
288
289
289 msg_id, reply = execute(code='x=1')
290 msg_id, reply = execute(code='x=1')
290 count = reply['execution_count']
291 count = reply['execution_count']
291
292
292 flush_channels()
293 flush_channels()
293
294
294 msg_id, reply = execute(code='x=2')
295 msg_id, reply = execute(code='x=2')
295 count_2 = reply['execution_count']
296 count_2 = reply['execution_count']
296 nt.assert_equal(count_2, count+1)
297 nt.assert_equal(count_2, count+1)
297
298
298
299
299 def test_user_expressions():
300 def test_user_expressions():
300 flush_channels()
301 flush_channels()
301
302
302 msg_id, reply = execute(code='x=1', user_expressions=dict(foo='x+1'))
303 msg_id, reply = execute(code='x=1', user_expressions=dict(foo='x+1'))
303 user_expressions = reply['user_expressions']
304 user_expressions = reply['user_expressions']
304 nt.assert_equal(user_expressions, {u'foo': {
305 nt.assert_equal(user_expressions, {u'foo': {
305 u'status': u'ok',
306 u'status': u'ok',
306 u'data': {u'text/plain': u'2'},
307 u'data': {u'text/plain': u'2'},
307 u'metadata': {},
308 u'metadata': {},
308 }})
309 }})
309
310
310
311
311 def test_user_expressions_fail():
312 def test_user_expressions_fail():
312 flush_channels()
313 flush_channels()
313
314
314 msg_id, reply = execute(code='x=0', user_expressions=dict(foo='nosuchname'))
315 msg_id, reply = execute(code='x=0', user_expressions=dict(foo='nosuchname'))
315 user_expressions = reply['user_expressions']
316 user_expressions = reply['user_expressions']
316 foo = user_expressions['foo']
317 foo = user_expressions['foo']
317 nt.assert_equal(foo['status'], 'error')
318 nt.assert_equal(foo['status'], 'error')
318 nt.assert_equal(foo['ename'], 'NameError')
319 nt.assert_equal(foo['ename'], 'NameError')
319
320
320
321
321 def test_oinfo():
322 def test_oinfo():
322 flush_channels()
323 flush_channels()
323
324
324 msg_id = KC.inspect('a')
325 msg_id = KC.inspect('a')
325 reply = KC.get_shell_msg(timeout=TIMEOUT)
326 reply = KC.get_shell_msg(timeout=TIMEOUT)
326 validate_message(reply, 'inspect_reply', msg_id)
327 validate_message(reply, 'inspect_reply', msg_id)
327
328
328
329
329 def test_oinfo_found():
330 def test_oinfo_found():
330 flush_channels()
331 flush_channels()
331
332
332 msg_id, reply = execute(code='a=5')
333 msg_id, reply = execute(code='a=5')
333
334
334 msg_id = KC.inspect('a')
335 msg_id = KC.inspect('a')
335 reply = KC.get_shell_msg(timeout=TIMEOUT)
336 reply = KC.get_shell_msg(timeout=TIMEOUT)
336 validate_message(reply, 'inspect_reply', msg_id)
337 validate_message(reply, 'inspect_reply', msg_id)
337 content = reply['content']
338 content = reply['content']
338 assert content['found']
339 assert content['found']
339 text = content['data']['text/plain']
340 text = content['data']['text/plain']
340 nt.assert_in('Type:', text)
341 nt.assert_in('Type:', text)
341 nt.assert_in('Docstring:', text)
342 nt.assert_in('Docstring:', text)
342
343
343
344
344 def test_oinfo_detail():
345 def test_oinfo_detail():
345 flush_channels()
346 flush_channels()
346
347
347 msg_id, reply = execute(code='ip=get_ipython()')
348 msg_id, reply = execute(code='ip=get_ipython()')
348
349
349 msg_id = KC.inspect('ip.object_inspect', cursor_pos=10, detail_level=1)
350 msg_id = KC.inspect('ip.object_inspect', cursor_pos=10, detail_level=1)
350 reply = KC.get_shell_msg(timeout=TIMEOUT)
351 reply = KC.get_shell_msg(timeout=TIMEOUT)
351 validate_message(reply, 'inspect_reply', msg_id)
352 validate_message(reply, 'inspect_reply', msg_id)
352 content = reply['content']
353 content = reply['content']
353 assert content['found']
354 assert content['found']
354 text = content['data']['text/plain']
355 text = content['data']['text/plain']
355 nt.assert_in('Definition:', text)
356 nt.assert_in('Definition:', text)
356 nt.assert_in('Source:', text)
357 nt.assert_in('Source:', text)
357
358
358
359
359 def test_oinfo_not_found():
360 def test_oinfo_not_found():
360 flush_channels()
361 flush_channels()
361
362
362 msg_id = KC.inspect('dne')
363 msg_id = KC.inspect('dne')
363 reply = KC.get_shell_msg(timeout=TIMEOUT)
364 reply = KC.get_shell_msg(timeout=TIMEOUT)
364 validate_message(reply, 'inspect_reply', msg_id)
365 validate_message(reply, 'inspect_reply', msg_id)
365 content = reply['content']
366 content = reply['content']
366 nt.assert_false(content['found'])
367 nt.assert_false(content['found'])
367
368
368
369
369 def test_complete():
370 def test_complete():
370 flush_channels()
371 flush_channels()
371
372
372 msg_id, reply = execute(code="alpha = albert = 5")
373 msg_id, reply = execute(code="alpha = albert = 5")
373
374
374 msg_id = KC.complete('al', 2)
375 msg_id = KC.complete('al', 2)
375 reply = KC.get_shell_msg(timeout=TIMEOUT)
376 reply = KC.get_shell_msg(timeout=TIMEOUT)
376 validate_message(reply, 'complete_reply', msg_id)
377 validate_message(reply, 'complete_reply', msg_id)
377 matches = reply['content']['matches']
378 matches = reply['content']['matches']
378 for name in ('alpha', 'albert'):
379 for name in ('alpha', 'albert'):
379 nt.assert_in(name, matches)
380 nt.assert_in(name, matches)
380
381
381
382
382 def test_kernel_info_request():
383 def test_kernel_info_request():
383 flush_channels()
384 flush_channels()
384
385
385 msg_id = KC.kernel_info()
386 msg_id = KC.kernel_info()
386 reply = KC.get_shell_msg(timeout=TIMEOUT)
387 reply = KC.get_shell_msg(timeout=TIMEOUT)
387 validate_message(reply, 'kernel_info_reply', msg_id)
388 validate_message(reply, 'kernel_info_reply', msg_id)
388
389
389
390
390 def test_single_payload():
391 def test_single_payload():
391 flush_channels()
392 flush_channels()
392 msg_id, reply = execute(code="for i in range(3):\n"+
393 msg_id, reply = execute(code="for i in range(3):\n"+
393 " x=range?\n")
394 " x=range?\n")
394 payload = reply['payload']
395 payload = reply['payload']
395 next_input_pls = [pl for pl in payload if pl["source"] == "set_next_input"]
396 next_input_pls = [pl for pl in payload if pl["source"] == "set_next_input"]
396 nt.assert_equal(len(next_input_pls), 1)
397 nt.assert_equal(len(next_input_pls), 1)
397
398
398 def test_is_complete():
399 def test_is_complete():
399 flush_channels()
400 flush_channels()
400
401
401 msg_id = KC.is_complete("a = 1")
402 msg_id = KC.is_complete("a = 1")
402 reply = KC.get_shell_msg(timeout=TIMEOUT)
403 reply = KC.get_shell_msg(timeout=TIMEOUT)
403 validate_message(reply, 'is_complete_reply', msg_id)
404 validate_message(reply, 'is_complete_reply', msg_id)
404
405
405 # IOPub channel
406 # IOPub channel
406
407
407
408
408 def test_stream():
409 def test_stream():
409 flush_channels()
410 flush_channels()
410
411
411 msg_id, reply = execute("print('hi')")
412 msg_id, reply = execute("print('hi')")
412
413
413 stdout = KC.iopub_channel.get_msg(timeout=TIMEOUT)
414 stdout = KC.iopub_channel.get_msg(timeout=TIMEOUT)
414 validate_message(stdout, 'stream', msg_id)
415 validate_message(stdout, 'stream', msg_id)
415 content = stdout['content']
416 content = stdout['content']
416 nt.assert_equal(content['text'], u'hi\n')
417 nt.assert_equal(content['text'], u'hi\n')
417
418
418
419
419 def test_display_data():
420 def test_display_data():
420 flush_channels()
421 flush_channels()
421
422
422 msg_id, reply = execute("from IPython.core.display import display; display(1)")
423 msg_id, reply = execute("from IPython.core.display import display; display(1)")
423
424
424 display = KC.iopub_channel.get_msg(timeout=TIMEOUT)
425 display = KC.iopub_channel.get_msg(timeout=TIMEOUT)
425 validate_message(display, 'display_data', parent=msg_id)
426 validate_message(display, 'display_data', parent=msg_id)
426 data = display['content']['data']
427 data = display['content']['data']
427 nt.assert_equal(data['text/plain'], u'1')
428 nt.assert_equal(data['text/plain'], u'1')
428
429
@@ -1,323 +1,328 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 = 'python'
73 language_version = sys.version.split()[0]
73 language_version = sys.version.split()[0]
74 language_info = {'mimetype': 'text/x-python',
75 'codemirror_mode': {'name': 'ipython',
76 'version': sys.version_info[0]},
77 'pygments_lexer': 'ipython%d' % (3 if PY3 else 2),
78 }
74 @property
79 @property
75 def banner(self):
80 def banner(self):
76 return self.shell.banner
81 return self.shell.banner
77
82
78 def start(self):
83 def start(self):
79 self.shell.exit_now = False
84 self.shell.exit_now = False
80 super(IPythonKernel, self).start()
85 super(IPythonKernel, self).start()
81
86
82 def set_parent(self, ident, parent):
87 def set_parent(self, ident, parent):
83 """Overridden from parent to tell the display hook and output streams
88 """Overridden from parent to tell the display hook and output streams
84 about the parent message.
89 about the parent message.
85 """
90 """
86 super(IPythonKernel, self).set_parent(ident, parent)
91 super(IPythonKernel, self).set_parent(ident, parent)
87 self.shell.set_parent(parent)
92 self.shell.set_parent(parent)
88
93
89 def _forward_input(self, allow_stdin=False):
94 def _forward_input(self, allow_stdin=False):
90 """Forward raw_input and getpass to the current frontend.
95 """Forward raw_input and getpass to the current frontend.
91
96
92 via input_request
97 via input_request
93 """
98 """
94 self._allow_stdin = allow_stdin
99 self._allow_stdin = allow_stdin
95
100
96 if PY3:
101 if PY3:
97 self._sys_raw_input = builtin_mod.input
102 self._sys_raw_input = builtin_mod.input
98 builtin_mod.input = self.raw_input
103 builtin_mod.input = self.raw_input
99 else:
104 else:
100 self._sys_raw_input = builtin_mod.raw_input
105 self._sys_raw_input = builtin_mod.raw_input
101 self._sys_eval_input = builtin_mod.input
106 self._sys_eval_input = builtin_mod.input
102 builtin_mod.raw_input = self.raw_input
107 builtin_mod.raw_input = self.raw_input
103 builtin_mod.input = lambda prompt='': eval(self.raw_input(prompt))
108 builtin_mod.input = lambda prompt='': eval(self.raw_input(prompt))
104 self._save_getpass = getpass.getpass
109 self._save_getpass = getpass.getpass
105 getpass.getpass = self.getpass
110 getpass.getpass = self.getpass
106
111
107 def _restore_input(self):
112 def _restore_input(self):
108 """Restore raw_input, getpass"""
113 """Restore raw_input, getpass"""
109 if PY3:
114 if PY3:
110 builtin_mod.input = self._sys_raw_input
115 builtin_mod.input = self._sys_raw_input
111 else:
116 else:
112 builtin_mod.raw_input = self._sys_raw_input
117 builtin_mod.raw_input = self._sys_raw_input
113 builtin_mod.input = self._sys_eval_input
118 builtin_mod.input = self._sys_eval_input
114
119
115 getpass.getpass = self._save_getpass
120 getpass.getpass = self._save_getpass
116
121
117 @property
122 @property
118 def execution_count(self):
123 def execution_count(self):
119 return self.shell.execution_count
124 return self.shell.execution_count
120
125
121 @execution_count.setter
126 @execution_count.setter
122 def execution_count(self, value):
127 def execution_count(self, value):
123 # Ignore the incrememnting done by KernelBase, in favour of our shell's
128 # Ignore the incrememnting done by KernelBase, in favour of our shell's
124 # execution counter.
129 # execution counter.
125 pass
130 pass
126
131
127 def do_execute(self, code, silent, store_history=True,
132 def do_execute(self, code, silent, store_history=True,
128 user_expressions=None, allow_stdin=False):
133 user_expressions=None, allow_stdin=False):
129 shell = self.shell # we'll need this a lot here
134 shell = self.shell # we'll need this a lot here
130
135
131 self._forward_input(allow_stdin)
136 self._forward_input(allow_stdin)
132
137
133 reply_content = {}
138 reply_content = {}
134 # FIXME: the shell calls the exception handler itself.
139 # FIXME: the shell calls the exception handler itself.
135 shell._reply_content = None
140 shell._reply_content = None
136 try:
141 try:
137 shell.run_cell(code, store_history=store_history, silent=silent)
142 shell.run_cell(code, store_history=store_history, silent=silent)
138 except:
143 except:
139 status = u'error'
144 status = u'error'
140 # FIXME: this code right now isn't being used yet by default,
145 # FIXME: this code right now isn't being used yet by default,
141 # because the run_cell() call above directly fires off exception
146 # because the run_cell() call above directly fires off exception
142 # reporting. This code, therefore, is only active in the scenario
147 # reporting. This code, therefore, is only active in the scenario
143 # where runlines itself has an unhandled exception. We need to
148 # where runlines itself has an unhandled exception. We need to
144 # uniformize this, for all exception construction to come from a
149 # uniformize this, for all exception construction to come from a
145 # single location in the codbase.
150 # single location in the codbase.
146 etype, evalue, tb = sys.exc_info()
151 etype, evalue, tb = sys.exc_info()
147 tb_list = traceback.format_exception(etype, evalue, tb)
152 tb_list = traceback.format_exception(etype, evalue, tb)
148 reply_content.update(shell._showtraceback(etype, evalue, tb_list))
153 reply_content.update(shell._showtraceback(etype, evalue, tb_list))
149 else:
154 else:
150 status = u'ok'
155 status = u'ok'
151 finally:
156 finally:
152 self._restore_input()
157 self._restore_input()
153
158
154 reply_content[u'status'] = status
159 reply_content[u'status'] = status
155
160
156 # Return the execution counter so clients can display prompts
161 # Return the execution counter so clients can display prompts
157 reply_content['execution_count'] = shell.execution_count - 1
162 reply_content['execution_count'] = shell.execution_count - 1
158
163
159 # FIXME - fish exception info out of shell, possibly left there by
164 # FIXME - fish exception info out of shell, possibly left there by
160 # runlines. We'll need to clean up this logic later.
165 # runlines. We'll need to clean up this logic later.
161 if shell._reply_content is not None:
166 if shell._reply_content is not None:
162 reply_content.update(shell._reply_content)
167 reply_content.update(shell._reply_content)
163 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='execute')
168 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='execute')
164 reply_content['engine_info'] = e_info
169 reply_content['engine_info'] = e_info
165 # reset after use
170 # reset after use
166 shell._reply_content = None
171 shell._reply_content = None
167
172
168 if 'traceback' in reply_content:
173 if 'traceback' in reply_content:
169 self.log.info("Exception in execute request:\n%s", '\n'.join(reply_content['traceback']))
174 self.log.info("Exception in execute request:\n%s", '\n'.join(reply_content['traceback']))
170
175
171
176
172 # At this point, we can tell whether the main code execution succeeded
177 # At this point, we can tell whether the main code execution succeeded
173 # or not. If it did, we proceed to evaluate user_expressions
178 # or not. If it did, we proceed to evaluate user_expressions
174 if reply_content['status'] == 'ok':
179 if reply_content['status'] == 'ok':
175 reply_content[u'user_expressions'] = \
180 reply_content[u'user_expressions'] = \
176 shell.user_expressions(user_expressions or {})
181 shell.user_expressions(user_expressions or {})
177 else:
182 else:
178 # If there was an error, don't even try to compute expressions
183 # If there was an error, don't even try to compute expressions
179 reply_content[u'user_expressions'] = {}
184 reply_content[u'user_expressions'] = {}
180
185
181 # Payloads should be retrieved regardless of outcome, so we can both
186 # Payloads should be retrieved regardless of outcome, so we can both
182 # recover partial output (that could have been generated early in a
187 # recover partial output (that could have been generated early in a
183 # block, before an error) and clear the payload system always.
188 # block, before an error) and clear the payload system always.
184 reply_content[u'payload'] = shell.payload_manager.read_payload()
189 reply_content[u'payload'] = shell.payload_manager.read_payload()
185 # Be agressive about clearing the payload because we don't want
190 # Be agressive about clearing the payload because we don't want
186 # it to sit in memory until the next execute_request comes in.
191 # it to sit in memory until the next execute_request comes in.
187 shell.payload_manager.clear_payload()
192 shell.payload_manager.clear_payload()
188
193
189 return reply_content
194 return reply_content
190
195
191 def do_complete(self, code, cursor_pos):
196 def do_complete(self, code, cursor_pos):
192 # FIXME: IPython completers currently assume single line,
197 # FIXME: IPython completers currently assume single line,
193 # but completion messages give multi-line context
198 # but completion messages give multi-line context
194 # For now, extract line from cell, based on cursor_pos:
199 # For now, extract line from cell, based on cursor_pos:
195 if cursor_pos is None:
200 if cursor_pos is None:
196 cursor_pos = len(code)
201 cursor_pos = len(code)
197 line, offset = line_at_cursor(code, cursor_pos)
202 line, offset = line_at_cursor(code, cursor_pos)
198 line_cursor = cursor_pos - offset
203 line_cursor = cursor_pos - offset
199
204
200 txt, matches = self.shell.complete('', line, line_cursor)
205 txt, matches = self.shell.complete('', line, line_cursor)
201 return {'matches' : matches,
206 return {'matches' : matches,
202 'cursor_end' : cursor_pos,
207 'cursor_end' : cursor_pos,
203 'cursor_start' : cursor_pos - len(txt),
208 'cursor_start' : cursor_pos - len(txt),
204 'metadata' : {},
209 'metadata' : {},
205 'status' : 'ok'}
210 'status' : 'ok'}
206
211
207 def do_inspect(self, code, cursor_pos, detail_level=0):
212 def do_inspect(self, code, cursor_pos, detail_level=0):
208 name = token_at_cursor(code, cursor_pos)
213 name = token_at_cursor(code, cursor_pos)
209 info = self.shell.object_inspect(name)
214 info = self.shell.object_inspect(name)
210
215
211 reply_content = {'status' : 'ok'}
216 reply_content = {'status' : 'ok'}
212 reply_content['data'] = data = {}
217 reply_content['data'] = data = {}
213 reply_content['metadata'] = {}
218 reply_content['metadata'] = {}
214 reply_content['found'] = info['found']
219 reply_content['found'] = info['found']
215 if info['found']:
220 if info['found']:
216 info_text = self.shell.object_inspect_text(
221 info_text = self.shell.object_inspect_text(
217 name,
222 name,
218 detail_level=detail_level,
223 detail_level=detail_level,
219 )
224 )
220 data['text/plain'] = info_text
225 data['text/plain'] = info_text
221
226
222 return reply_content
227 return reply_content
223
228
224 def do_history(self, hist_access_type, output, raw, session=None, start=None,
229 def do_history(self, hist_access_type, output, raw, session=None, start=None,
225 stop=None, n=None, pattern=None, unique=False):
230 stop=None, n=None, pattern=None, unique=False):
226 if hist_access_type == 'tail':
231 if hist_access_type == 'tail':
227 hist = self.shell.history_manager.get_tail(n, raw=raw, output=output,
232 hist = self.shell.history_manager.get_tail(n, raw=raw, output=output,
228 include_latest=True)
233 include_latest=True)
229
234
230 elif hist_access_type == 'range':
235 elif hist_access_type == 'range':
231 hist = self.shell.history_manager.get_range(session, start, stop,
236 hist = self.shell.history_manager.get_range(session, start, stop,
232 raw=raw, output=output)
237 raw=raw, output=output)
233
238
234 elif hist_access_type == 'search':
239 elif hist_access_type == 'search':
235 hist = self.shell.history_manager.search(
240 hist = self.shell.history_manager.search(
236 pattern, raw=raw, output=output, n=n, unique=unique)
241 pattern, raw=raw, output=output, n=n, unique=unique)
237 else:
242 else:
238 hist = []
243 hist = []
239
244
240 return {'history' : list(hist)}
245 return {'history' : list(hist)}
241
246
242 def do_shutdown(self, restart):
247 def do_shutdown(self, restart):
243 self.shell.exit_now = True
248 self.shell.exit_now = True
244 return dict(status='ok', restart=restart)
249 return dict(status='ok', restart=restart)
245
250
246 def do_is_complete(self, code):
251 def do_is_complete(self, code):
247 status, indent_spaces = self.shell.input_transformer_manager.check_complete(code)
252 status, indent_spaces = self.shell.input_transformer_manager.check_complete(code)
248 r = {'status': status}
253 r = {'status': status}
249 if status == 'incomplete':
254 if status == 'incomplete':
250 r['indent'] = ' ' * indent_spaces
255 r['indent'] = ' ' * indent_spaces
251 return r
256 return r
252
257
253 def do_apply(self, content, bufs, msg_id, reply_metadata):
258 def do_apply(self, content, bufs, msg_id, reply_metadata):
254 shell = self.shell
259 shell = self.shell
255 try:
260 try:
256 working = shell.user_ns
261 working = shell.user_ns
257
262
258 prefix = "_"+str(msg_id).replace("-","")+"_"
263 prefix = "_"+str(msg_id).replace("-","")+"_"
259
264
260 f,args,kwargs = unpack_apply_message(bufs, working, copy=False)
265 f,args,kwargs = unpack_apply_message(bufs, working, copy=False)
261
266
262 fname = getattr(f, '__name__', 'f')
267 fname = getattr(f, '__name__', 'f')
263
268
264 fname = prefix+"f"
269 fname = prefix+"f"
265 argname = prefix+"args"
270 argname = prefix+"args"
266 kwargname = prefix+"kwargs"
271 kwargname = prefix+"kwargs"
267 resultname = prefix+"result"
272 resultname = prefix+"result"
268
273
269 ns = { fname : f, argname : args, kwargname : kwargs , resultname : None }
274 ns = { fname : f, argname : args, kwargname : kwargs , resultname : None }
270 # print ns
275 # print ns
271 working.update(ns)
276 working.update(ns)
272 code = "%s = %s(*%s,**%s)" % (resultname, fname, argname, kwargname)
277 code = "%s = %s(*%s,**%s)" % (resultname, fname, argname, kwargname)
273 try:
278 try:
274 exec(code, shell.user_global_ns, shell.user_ns)
279 exec(code, shell.user_global_ns, shell.user_ns)
275 result = working.get(resultname)
280 result = working.get(resultname)
276 finally:
281 finally:
277 for key in ns:
282 for key in ns:
278 working.pop(key)
283 working.pop(key)
279
284
280 result_buf = serialize_object(result,
285 result_buf = serialize_object(result,
281 buffer_threshold=self.session.buffer_threshold,
286 buffer_threshold=self.session.buffer_threshold,
282 item_threshold=self.session.item_threshold,
287 item_threshold=self.session.item_threshold,
283 )
288 )
284
289
285 except:
290 except:
286 # invoke IPython traceback formatting
291 # invoke IPython traceback formatting
287 shell.showtraceback()
292 shell.showtraceback()
288 # FIXME - fish exception info out of shell, possibly left there by
293 # FIXME - fish exception info out of shell, possibly left there by
289 # run_code. We'll need to clean up this logic later.
294 # run_code. We'll need to clean up this logic later.
290 reply_content = {}
295 reply_content = {}
291 if shell._reply_content is not None:
296 if shell._reply_content is not None:
292 reply_content.update(shell._reply_content)
297 reply_content.update(shell._reply_content)
293 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='apply')
298 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='apply')
294 reply_content['engine_info'] = e_info
299 reply_content['engine_info'] = e_info
295 # reset after use
300 # reset after use
296 shell._reply_content = None
301 shell._reply_content = None
297
302
298 self.send_response(self.iopub_socket, u'error', reply_content,
303 self.send_response(self.iopub_socket, u'error', reply_content,
299 ident=self._topic('error'))
304 ident=self._topic('error'))
300 self.log.info("Exception in apply request:\n%s", '\n'.join(reply_content['traceback']))
305 self.log.info("Exception in apply request:\n%s", '\n'.join(reply_content['traceback']))
301 result_buf = []
306 result_buf = []
302
307
303 if reply_content['ename'] == 'UnmetDependency':
308 if reply_content['ename'] == 'UnmetDependency':
304 reply_metadata['dependencies_met'] = False
309 reply_metadata['dependencies_met'] = False
305 else:
310 else:
306 reply_content = {'status' : 'ok'}
311 reply_content = {'status' : 'ok'}
307
312
308 return reply_content, result_buf
313 return reply_content, result_buf
309
314
310 def do_clear(self):
315 def do_clear(self):
311 self.shell.reset(False)
316 self.shell.reset(False)
312 return dict(status='ok')
317 return dict(status='ok')
313
318
314
319
315 # This exists only for backwards compatibility - use IPythonKernel instead
320 # This exists only for backwards compatibility - use IPythonKernel instead
316
321
317 @undoc
322 @undoc
318 class Kernel(IPythonKernel):
323 class Kernel(IPythonKernel):
319 def __init__(self, *args, **kwargs):
324 def __init__(self, *args, **kwargs):
320 import warnings
325 import warnings
321 warnings.warn('Kernel is a deprecated alias of IPython.kernel.zmq.ipkernel.IPythonKernel',
326 warnings.warn('Kernel is a deprecated alias of IPython.kernel.zmq.ipkernel.IPythonKernel',
322 DeprecationWarning)
327 DeprecationWarning)
323 super(Kernel, self).__init__(*args, **kwargs) No newline at end of file
328 super(Kernel, self).__init__(*args, **kwargs)
@@ -1,692 +1,697 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
64 # language.
65 language_info = {}
66
63 # Private interface
67 # Private interface
64
68
65 _darwin_app_nap = Bool(True, config=True,
69 _darwin_app_nap = Bool(True, config=True,
66 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.
67
71
68 Only affects OS X >= 10.9.
72 Only affects OS X >= 10.9.
69 """
73 """
70 )
74 )
71
75
72 # track associations with current request
76 # track associations with current request
73 _allow_stdin = Bool(False)
77 _allow_stdin = Bool(False)
74 _parent_header = Dict()
78 _parent_header = Dict()
75 _parent_ident = Any(b'')
79 _parent_ident = Any(b'')
76 # 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
77 # 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
78 # execute cycle, it helps prevent output synchronization problems for
82 # execute cycle, it helps prevent output synchronization problems for
79 # clients.
83 # clients.
80 # 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
81 # ~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
82 # a little if it's not enough after more interactive testing.
86 # a little if it's not enough after more interactive testing.
83 _execute_sleep = Float(0.0005, config=True)
87 _execute_sleep = Float(0.0005, config=True)
84
88
85 # Frequency of the kernel's event loop.
89 # Frequency of the kernel's event loop.
86 # 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
87 # adapt to milliseconds.
91 # adapt to milliseconds.
88 _poll_interval = Float(0.05, config=True)
92 _poll_interval = Float(0.05, config=True)
89
93
90 # 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
91 # 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
92 # 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
93 # the end of our shutdown process (which happens after the underlying
97 # the end of our shutdown process (which happens after the underlying
94 # IPython shell's own shutdown).
98 # IPython shell's own shutdown).
95 _shutdown_message = None
99 _shutdown_message = None
96
100
97 # 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
98 # by record_ports and used by connect_request.
102 # by record_ports and used by connect_request.
99 _recorded_ports = Dict()
103 _recorded_ports = Dict()
100
104
101 # set of aborted msg_ids
105 # set of aborted msg_ids
102 aborted = Set()
106 aborted = Set()
103
107
104 # 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
105 # execution count we store in the shell.
109 # execution count we store in the shell.
106 execution_count = 0
110 execution_count = 0
107
111
108
112
109 def __init__(self, **kwargs):
113 def __init__(self, **kwargs):
110 super(Kernel, self).__init__(**kwargs)
114 super(Kernel, self).__init__(**kwargs)
111
115
112 # Build dict of handlers for message types
116 # Build dict of handlers for message types
113 msg_types = [ 'execute_request', 'complete_request',
117 msg_types = [ 'execute_request', 'complete_request',
114 'inspect_request', 'history_request',
118 'inspect_request', 'history_request',
115 'kernel_info_request',
119 'kernel_info_request',
116 'connect_request', 'shutdown_request',
120 'connect_request', 'shutdown_request',
117 'apply_request', 'is_complete_request',
121 'apply_request', 'is_complete_request',
118 ]
122 ]
119 self.shell_handlers = {}
123 self.shell_handlers = {}
120 for msg_type in msg_types:
124 for msg_type in msg_types:
121 self.shell_handlers[msg_type] = getattr(self, msg_type)
125 self.shell_handlers[msg_type] = getattr(self, msg_type)
122
126
123 control_msg_types = msg_types + [ 'clear_request', 'abort_request' ]
127 control_msg_types = msg_types + [ 'clear_request', 'abort_request' ]
124 self.control_handlers = {}
128 self.control_handlers = {}
125 for msg_type in control_msg_types:
129 for msg_type in control_msg_types:
126 self.control_handlers[msg_type] = getattr(self, msg_type)
130 self.control_handlers[msg_type] = getattr(self, msg_type)
127
131
128
132
129 def dispatch_control(self, msg):
133 def dispatch_control(self, msg):
130 """dispatch control requests"""
134 """dispatch control requests"""
131 idents,msg = self.session.feed_identities(msg, copy=False)
135 idents,msg = self.session.feed_identities(msg, copy=False)
132 try:
136 try:
133 msg = self.session.deserialize(msg, content=True, copy=False)
137 msg = self.session.deserialize(msg, content=True, copy=False)
134 except:
138 except:
135 self.log.error("Invalid Control Message", exc_info=True)
139 self.log.error("Invalid Control Message", exc_info=True)
136 return
140 return
137
141
138 self.log.debug("Control received: %s", msg)
142 self.log.debug("Control received: %s", msg)
139
143
140 # Set the parent message for side effects.
144 # Set the parent message for side effects.
141 self.set_parent(idents, msg)
145 self.set_parent(idents, msg)
142 self._publish_status(u'busy')
146 self._publish_status(u'busy')
143
147
144 header = msg['header']
148 header = msg['header']
145 msg_type = header['msg_type']
149 msg_type = header['msg_type']
146
150
147 handler = self.control_handlers.get(msg_type, None)
151 handler = self.control_handlers.get(msg_type, None)
148 if handler is None:
152 if handler is None:
149 self.log.error("UNKNOWN CONTROL MESSAGE TYPE: %r", msg_type)
153 self.log.error("UNKNOWN CONTROL MESSAGE TYPE: %r", msg_type)
150 else:
154 else:
151 try:
155 try:
152 handler(self.control_stream, idents, msg)
156 handler(self.control_stream, idents, msg)
153 except Exception:
157 except Exception:
154 self.log.error("Exception in control handler:", exc_info=True)
158 self.log.error("Exception in control handler:", exc_info=True)
155
159
156 sys.stdout.flush()
160 sys.stdout.flush()
157 sys.stderr.flush()
161 sys.stderr.flush()
158 self._publish_status(u'idle')
162 self._publish_status(u'idle')
159
163
160 def dispatch_shell(self, stream, msg):
164 def dispatch_shell(self, stream, msg):
161 """dispatch shell requests"""
165 """dispatch shell requests"""
162 # flush control requests first
166 # flush control requests first
163 if self.control_stream:
167 if self.control_stream:
164 self.control_stream.flush()
168 self.control_stream.flush()
165
169
166 idents,msg = self.session.feed_identities(msg, copy=False)
170 idents,msg = self.session.feed_identities(msg, copy=False)
167 try:
171 try:
168 msg = self.session.deserialize(msg, content=True, copy=False)
172 msg = self.session.deserialize(msg, content=True, copy=False)
169 except:
173 except:
170 self.log.error("Invalid Message", exc_info=True)
174 self.log.error("Invalid Message", exc_info=True)
171 return
175 return
172
176
173 # Set the parent message for side effects.
177 # Set the parent message for side effects.
174 self.set_parent(idents, msg)
178 self.set_parent(idents, msg)
175 self._publish_status(u'busy')
179 self._publish_status(u'busy')
176
180
177 header = msg['header']
181 header = msg['header']
178 msg_id = header['msg_id']
182 msg_id = header['msg_id']
179 msg_type = msg['header']['msg_type']
183 msg_type = msg['header']['msg_type']
180
184
181 # 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
182 # easier to trace visually the message chain when debugging. Each
186 # easier to trace visually the message chain when debugging. Each
183 # handler prints its message at the end.
187 # handler prints its message at the end.
184 self.log.debug('\n*** MESSAGE TYPE:%s***', msg_type)
188 self.log.debug('\n*** MESSAGE TYPE:%s***', msg_type)
185 self.log.debug(' Content: %s\n --->\n ', msg['content'])
189 self.log.debug(' Content: %s\n --->\n ', msg['content'])
186
190
187 if msg_id in self.aborted:
191 if msg_id in self.aborted:
188 self.aborted.remove(msg_id)
192 self.aborted.remove(msg_id)
189 # 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?
190 reply_type = msg_type.split('_')[0] + '_reply'
194 reply_type = msg_type.split('_')[0] + '_reply'
191 status = {'status' : 'aborted'}
195 status = {'status' : 'aborted'}
192 md = {'engine' : self.ident}
196 md = {'engine' : self.ident}
193 md.update(status)
197 md.update(status)
194 self.session.send(stream, reply_type, metadata=md,
198 self.session.send(stream, reply_type, metadata=md,
195 content=status, parent=msg, ident=idents)
199 content=status, parent=msg, ident=idents)
196 return
200 return
197
201
198 handler = self.shell_handlers.get(msg_type, None)
202 handler = self.shell_handlers.get(msg_type, None)
199 if handler is None:
203 if handler is None:
200 self.log.error("UNKNOWN MESSAGE TYPE: %r", msg_type)
204 self.log.error("UNKNOWN MESSAGE TYPE: %r", msg_type)
201 else:
205 else:
202 # ensure default_int_handler during handler call
206 # ensure default_int_handler during handler call
203 sig = signal(SIGINT, default_int_handler)
207 sig = signal(SIGINT, default_int_handler)
204 self.log.debug("%s: %s", msg_type, msg)
208 self.log.debug("%s: %s", msg_type, msg)
205 try:
209 try:
206 handler(stream, idents, msg)
210 handler(stream, idents, msg)
207 except Exception:
211 except Exception:
208 self.log.error("Exception in message handler:", exc_info=True)
212 self.log.error("Exception in message handler:", exc_info=True)
209 finally:
213 finally:
210 signal(SIGINT, sig)
214 signal(SIGINT, sig)
211
215
212 sys.stdout.flush()
216 sys.stdout.flush()
213 sys.stderr.flush()
217 sys.stderr.flush()
214 self._publish_status(u'idle')
218 self._publish_status(u'idle')
215
219
216 def enter_eventloop(self):
220 def enter_eventloop(self):
217 """enter eventloop"""
221 """enter eventloop"""
218 self.log.info("entering eventloop %s", self.eventloop)
222 self.log.info("entering eventloop %s", self.eventloop)
219 for stream in self.shell_streams:
223 for stream in self.shell_streams:
220 # flush any pending replies,
224 # flush any pending replies,
221 # which may be skipped by entering the eventloop
225 # which may be skipped by entering the eventloop
222 stream.flush(zmq.POLLOUT)
226 stream.flush(zmq.POLLOUT)
223 # restore default_int_handler
227 # restore default_int_handler
224 signal(SIGINT, default_int_handler)
228 signal(SIGINT, default_int_handler)
225 while self.eventloop is not None:
229 while self.eventloop is not None:
226 try:
230 try:
227 self.eventloop(self)
231 self.eventloop(self)
228 except KeyboardInterrupt:
232 except KeyboardInterrupt:
229 # Ctrl-C shouldn't crash the kernel
233 # Ctrl-C shouldn't crash the kernel
230 self.log.error("KeyboardInterrupt caught in kernel")
234 self.log.error("KeyboardInterrupt caught in kernel")
231 continue
235 continue
232 else:
236 else:
233 # eventloop exited cleanly, this means we should stop (right?)
237 # eventloop exited cleanly, this means we should stop (right?)
234 self.eventloop = None
238 self.eventloop = None
235 break
239 break
236 self.log.info("exiting eventloop")
240 self.log.info("exiting eventloop")
237
241
238 def start(self):
242 def start(self):
239 """register dispatchers for streams"""
243 """register dispatchers for streams"""
240 if self.control_stream:
244 if self.control_stream:
241 self.control_stream.on_recv(self.dispatch_control, copy=False)
245 self.control_stream.on_recv(self.dispatch_control, copy=False)
242
246
243 def make_dispatcher(stream):
247 def make_dispatcher(stream):
244 def dispatcher(msg):
248 def dispatcher(msg):
245 return self.dispatch_shell(stream, msg)
249 return self.dispatch_shell(stream, msg)
246 return dispatcher
250 return dispatcher
247
251
248 for s in self.shell_streams:
252 for s in self.shell_streams:
249 s.on_recv(make_dispatcher(s), copy=False)
253 s.on_recv(make_dispatcher(s), copy=False)
250
254
251 # publish idle status
255 # publish idle status
252 self._publish_status('starting')
256 self._publish_status('starting')
253
257
254 def do_one_iteration(self):
258 def do_one_iteration(self):
255 """step eventloop just once"""
259 """step eventloop just once"""
256 if self.control_stream:
260 if self.control_stream:
257 self.control_stream.flush()
261 self.control_stream.flush()
258 for stream in self.shell_streams:
262 for stream in self.shell_streams:
259 # handle at most one request per iteration
263 # handle at most one request per iteration
260 stream.flush(zmq.POLLIN, 1)
264 stream.flush(zmq.POLLIN, 1)
261 stream.flush(zmq.POLLOUT)
265 stream.flush(zmq.POLLOUT)
262
266
263
267
264 def record_ports(self, ports):
268 def record_ports(self, ports):
265 """Record the ports that this kernel is using.
269 """Record the ports that this kernel is using.
266
270
267 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
268 want the :meth:`connect_request` method to return the port numbers.
272 want the :meth:`connect_request` method to return the port numbers.
269 """
273 """
270 self._recorded_ports = ports
274 self._recorded_ports = ports
271
275
272 #---------------------------------------------------------------------------
276 #---------------------------------------------------------------------------
273 # Kernel request handlers
277 # Kernel request handlers
274 #---------------------------------------------------------------------------
278 #---------------------------------------------------------------------------
275
279
276 def _make_metadata(self, other=None):
280 def _make_metadata(self, other=None):
277 """init metadata dict, for execute/apply_reply"""
281 """init metadata dict, for execute/apply_reply"""
278 new_md = {
282 new_md = {
279 'dependencies_met' : True,
283 'dependencies_met' : True,
280 'engine' : self.ident,
284 'engine' : self.ident,
281 'started': datetime.now(),
285 'started': datetime.now(),
282 }
286 }
283 if other:
287 if other:
284 new_md.update(other)
288 new_md.update(other)
285 return new_md
289 return new_md
286
290
287 def _publish_execute_input(self, code, parent, execution_count):
291 def _publish_execute_input(self, code, parent, execution_count):
288 """Publish the code request on the iopub stream."""
292 """Publish the code request on the iopub stream."""
289
293
290 self.session.send(self.iopub_socket, u'execute_input',
294 self.session.send(self.iopub_socket, u'execute_input',
291 {u'code':code, u'execution_count': execution_count},
295 {u'code':code, u'execution_count': execution_count},
292 parent=parent, ident=self._topic('execute_input')
296 parent=parent, ident=self._topic('execute_input')
293 )
297 )
294
298
295 def _publish_status(self, status, parent=None):
299 def _publish_status(self, status, parent=None):
296 """send status (busy/idle) on IOPub"""
300 """send status (busy/idle) on IOPub"""
297 self.session.send(self.iopub_socket,
301 self.session.send(self.iopub_socket,
298 u'status',
302 u'status',
299 {u'execution_state': status},
303 {u'execution_state': status},
300 parent=parent or self._parent_header,
304 parent=parent or self._parent_header,
301 ident=self._topic('status'),
305 ident=self._topic('status'),
302 )
306 )
303
307
304 def set_parent(self, ident, parent):
308 def set_parent(self, ident, parent):
305 """Set the current parent_header
309 """Set the current parent_header
306
310
307 Side effects (IOPub messages) and replies are associated with
311 Side effects (IOPub messages) and replies are associated with
308 the request that caused them via the parent_header.
312 the request that caused them via the parent_header.
309
313
310 The parent identity is used to route input_request messages
314 The parent identity is used to route input_request messages
311 on the stdin channel.
315 on the stdin channel.
312 """
316 """
313 self._parent_ident = ident
317 self._parent_ident = ident
314 self._parent_header = parent
318 self._parent_header = parent
315
319
316 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,
317 buffers=None, track=False, header=None, metadata=None):
321 buffers=None, track=False, header=None, metadata=None):
318 """Send a response to the message we're currently processing.
322 """Send a response to the message we're currently processing.
319
323
320 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`
321 except ``parent``.
325 except ``parent``.
322
326
323 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
324 message.
328 message.
325 """
329 """
326 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,
327 ident, buffers, track, header, metadata)
331 ident, buffers, track, header, metadata)
328
332
329 def execute_request(self, stream, ident, parent):
333 def execute_request(self, stream, ident, parent):
330 """handle an execute_request"""
334 """handle an execute_request"""
331
335
332 try:
336 try:
333 content = parent[u'content']
337 content = parent[u'content']
334 code = py3compat.cast_unicode_py2(content[u'code'])
338 code = py3compat.cast_unicode_py2(content[u'code'])
335 silent = content[u'silent']
339 silent = content[u'silent']
336 store_history = content.get(u'store_history', not silent)
340 store_history = content.get(u'store_history', not silent)
337 user_expressions = content.get('user_expressions', {})
341 user_expressions = content.get('user_expressions', {})
338 allow_stdin = content.get('allow_stdin', False)
342 allow_stdin = content.get('allow_stdin', False)
339 except:
343 except:
340 self.log.error("Got bad msg: ")
344 self.log.error("Got bad msg: ")
341 self.log.error("%s", parent)
345 self.log.error("%s", parent)
342 return
346 return
343
347
344 md = self._make_metadata(parent['metadata'])
348 md = self._make_metadata(parent['metadata'])
345
349
346 # Re-broadcast our input for the benefit of listening clients, and
350 # Re-broadcast our input for the benefit of listening clients, and
347 # start computing output
351 # start computing output
348 if not silent:
352 if not silent:
349 self.execution_count += 1
353 self.execution_count += 1
350 self._publish_execute_input(code, parent, self.execution_count)
354 self._publish_execute_input(code, parent, self.execution_count)
351
355
352 reply_content = self.do_execute(code, silent, store_history,
356 reply_content = self.do_execute(code, silent, store_history,
353 user_expressions, allow_stdin)
357 user_expressions, allow_stdin)
354
358
355 # Flush output before sending the reply.
359 # Flush output before sending the reply.
356 sys.stdout.flush()
360 sys.stdout.flush()
357 sys.stderr.flush()
361 sys.stderr.flush()
358 # 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
359 # clients... This seems to mitigate the problem, but we definitely need
363 # clients... This seems to mitigate the problem, but we definitely need
360 # to better understand what's going on.
364 # to better understand what's going on.
361 if self._execute_sleep:
365 if self._execute_sleep:
362 time.sleep(self._execute_sleep)
366 time.sleep(self._execute_sleep)
363
367
364 # Send the reply.
368 # Send the reply.
365 reply_content = json_clean(reply_content)
369 reply_content = json_clean(reply_content)
366
370
367 md['status'] = reply_content['status']
371 md['status'] = reply_content['status']
368 if reply_content['status'] == 'error' and \
372 if reply_content['status'] == 'error' and \
369 reply_content['ename'] == 'UnmetDependency':
373 reply_content['ename'] == 'UnmetDependency':
370 md['dependencies_met'] = False
374 md['dependencies_met'] = False
371
375
372 reply_msg = self.session.send(stream, u'execute_reply',
376 reply_msg = self.session.send(stream, u'execute_reply',
373 reply_content, parent, metadata=md,
377 reply_content, parent, metadata=md,
374 ident=ident)
378 ident=ident)
375
379
376 self.log.debug("%s", reply_msg)
380 self.log.debug("%s", reply_msg)
377
381
378 if not silent and reply_msg['content']['status'] == u'error':
382 if not silent and reply_msg['content']['status'] == u'error':
379 self._abort_queues()
383 self._abort_queues()
380
384
381 def do_execute(self, code, silent, store_history=True,
385 def do_execute(self, code, silent, store_history=True,
382 user_experssions=None, allow_stdin=False):
386 user_experssions=None, allow_stdin=False):
383 """Execute user code. Must be overridden by subclasses.
387 """Execute user code. Must be overridden by subclasses.
384 """
388 """
385 raise NotImplementedError
389 raise NotImplementedError
386
390
387 def complete_request(self, stream, ident, parent):
391 def complete_request(self, stream, ident, parent):
388 content = parent['content']
392 content = parent['content']
389 code = content['code']
393 code = content['code']
390 cursor_pos = content['cursor_pos']
394 cursor_pos = content['cursor_pos']
391
395
392 matches = self.do_complete(code, cursor_pos)
396 matches = self.do_complete(code, cursor_pos)
393 matches = json_clean(matches)
397 matches = json_clean(matches)
394 completion_msg = self.session.send(stream, 'complete_reply',
398 completion_msg = self.session.send(stream, 'complete_reply',
395 matches, parent, ident)
399 matches, parent, ident)
396 self.log.debug("%s", completion_msg)
400 self.log.debug("%s", completion_msg)
397
401
398 def do_complete(self, code, cursor_pos):
402 def do_complete(self, code, cursor_pos):
399 """Override in subclasses to find completions.
403 """Override in subclasses to find completions.
400 """
404 """
401 return {'matches' : [],
405 return {'matches' : [],
402 'cursor_end' : cursor_pos,
406 'cursor_end' : cursor_pos,
403 'cursor_start' : cursor_pos,
407 'cursor_start' : cursor_pos,
404 'metadata' : {},
408 'metadata' : {},
405 'status' : 'ok'}
409 'status' : 'ok'}
406
410
407 def inspect_request(self, stream, ident, parent):
411 def inspect_request(self, stream, ident, parent):
408 content = parent['content']
412 content = parent['content']
409
413
410 reply_content = self.do_inspect(content['code'], content['cursor_pos'],
414 reply_content = self.do_inspect(content['code'], content['cursor_pos'],
411 content.get('detail_level', 0))
415 content.get('detail_level', 0))
412 # 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
413 reply_content = json_clean(reply_content)
417 reply_content = json_clean(reply_content)
414 msg = self.session.send(stream, 'inspect_reply',
418 msg = self.session.send(stream, 'inspect_reply',
415 reply_content, parent, ident)
419 reply_content, parent, ident)
416 self.log.debug("%s", msg)
420 self.log.debug("%s", msg)
417
421
418 def do_inspect(self, code, cursor_pos, detail_level=0):
422 def do_inspect(self, code, cursor_pos, detail_level=0):
419 """Override in subclasses to allow introspection.
423 """Override in subclasses to allow introspection.
420 """
424 """
421 return {'status': 'ok', 'data':{}, 'metadata':{}, 'found':False}
425 return {'status': 'ok', 'data':{}, 'metadata':{}, 'found':False}
422
426
423 def history_request(self, stream, ident, parent):
427 def history_request(self, stream, ident, parent):
424 content = parent['content']
428 content = parent['content']
425
429
426 reply_content = self.do_history(**content)
430 reply_content = self.do_history(**content)
427
431
428 reply_content = json_clean(reply_content)
432 reply_content = json_clean(reply_content)
429 msg = self.session.send(stream, 'history_reply',
433 msg = self.session.send(stream, 'history_reply',
430 reply_content, parent, ident)
434 reply_content, parent, ident)
431 self.log.debug("%s", msg)
435 self.log.debug("%s", msg)
432
436
433 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,
434 stop=None, n=None, pattern=None, unique=False):
438 stop=None, n=None, pattern=None, unique=False):
435 """Override in subclasses to access history.
439 """Override in subclasses to access history.
436 """
440 """
437 return {'history': []}
441 return {'history': []}
438
442
439 def connect_request(self, stream, ident, parent):
443 def connect_request(self, stream, ident, parent):
440 if self._recorded_ports is not None:
444 if self._recorded_ports is not None:
441 content = self._recorded_ports.copy()
445 content = self._recorded_ports.copy()
442 else:
446 else:
443 content = {}
447 content = {}
444 msg = self.session.send(stream, 'connect_reply',
448 msg = self.session.send(stream, 'connect_reply',
445 content, parent, ident)
449 content, parent, ident)
446 self.log.debug("%s", msg)
450 self.log.debug("%s", msg)
447
451
448 @property
452 @property
449 def kernel_info(self):
453 def kernel_info(self):
450 return {
454 return {
451 'protocol_version': release.kernel_protocol_version,
455 'protocol_version': release.kernel_protocol_version,
452 'implementation': self.implementation,
456 'implementation': self.implementation,
453 'implementation_version': self.implementation_version,
457 'implementation_version': self.implementation_version,
454 'language': self.language,
458 'language': self.language,
455 'language_version': self.language_version,
459 'language_version': self.language_version,
460 'language_info': self.language_info,
456 'banner': self.banner,
461 'banner': self.banner,
457 }
462 }
458
463
459 def kernel_info_request(self, stream, ident, parent):
464 def kernel_info_request(self, stream, ident, parent):
460 msg = self.session.send(stream, 'kernel_info_reply',
465 msg = self.session.send(stream, 'kernel_info_reply',
461 self.kernel_info, parent, ident)
466 self.kernel_info, parent, ident)
462 self.log.debug("%s", msg)
467 self.log.debug("%s", msg)
463
468
464 def shutdown_request(self, stream, ident, parent):
469 def shutdown_request(self, stream, ident, parent):
465 content = self.do_shutdown(parent['content']['restart'])
470 content = self.do_shutdown(parent['content']['restart'])
466 self.session.send(stream, u'shutdown_reply', content, parent, ident=ident)
471 self.session.send(stream, u'shutdown_reply', content, parent, ident=ident)
467 # same content, but different msg_id for broadcasting on IOPub
472 # same content, but different msg_id for broadcasting on IOPub
468 self._shutdown_message = self.session.msg(u'shutdown_reply',
473 self._shutdown_message = self.session.msg(u'shutdown_reply',
469 content, parent
474 content, parent
470 )
475 )
471
476
472 self._at_shutdown()
477 self._at_shutdown()
473 # call sys.exit after a short delay
478 # call sys.exit after a short delay
474 loop = ioloop.IOLoop.instance()
479 loop = ioloop.IOLoop.instance()
475 loop.add_timeout(time.time()+0.1, loop.stop)
480 loop.add_timeout(time.time()+0.1, loop.stop)
476
481
477 def do_shutdown(self, restart):
482 def do_shutdown(self, restart):
478 """Override in subclasses to do things when the frontend shuts down the
483 """Override in subclasses to do things when the frontend shuts down the
479 kernel.
484 kernel.
480 """
485 """
481 return {'status': 'ok', 'restart': restart}
486 return {'status': 'ok', 'restart': restart}
482
487
483 def is_complete_request(self, stream, ident, parent):
488 def is_complete_request(self, stream, ident, parent):
484 content = parent['content']
489 content = parent['content']
485 code = content['code']
490 code = content['code']
486
491
487 reply_content = self.do_is_complete(code)
492 reply_content = self.do_is_complete(code)
488 reply_content = json_clean(reply_content)
493 reply_content = json_clean(reply_content)
489 reply_msg = self.session.send(stream, 'is_complete_reply',
494 reply_msg = self.session.send(stream, 'is_complete_reply',
490 reply_content, parent, ident)
495 reply_content, parent, ident)
491 self.log.debug("%s", reply_msg)
496 self.log.debug("%s", reply_msg)
492
497
493 def do_is_complete(self, code):
498 def do_is_complete(self, code):
494 """Override in subclasses to find completions.
499 """Override in subclasses to find completions.
495 """
500 """
496 return {'status' : 'unknown',
501 return {'status' : 'unknown',
497 }
502 }
498
503
499 #---------------------------------------------------------------------------
504 #---------------------------------------------------------------------------
500 # Engine methods
505 # Engine methods
501 #---------------------------------------------------------------------------
506 #---------------------------------------------------------------------------
502
507
503 def apply_request(self, stream, ident, parent):
508 def apply_request(self, stream, ident, parent):
504 try:
509 try:
505 content = parent[u'content']
510 content = parent[u'content']
506 bufs = parent[u'buffers']
511 bufs = parent[u'buffers']
507 msg_id = parent['header']['msg_id']
512 msg_id = parent['header']['msg_id']
508 except:
513 except:
509 self.log.error("Got bad msg: %s", parent, exc_info=True)
514 self.log.error("Got bad msg: %s", parent, exc_info=True)
510 return
515 return
511
516
512 md = self._make_metadata(parent['metadata'])
517 md = self._make_metadata(parent['metadata'])
513
518
514 reply_content, result_buf = self.do_apply(content, bufs, msg_id, md)
519 reply_content, result_buf = self.do_apply(content, bufs, msg_id, md)
515
520
516 # put 'ok'/'error' status in header, for scheduler introspection:
521 # put 'ok'/'error' status in header, for scheduler introspection:
517 md['status'] = reply_content['status']
522 md['status'] = reply_content['status']
518
523
519 # flush i/o
524 # flush i/o
520 sys.stdout.flush()
525 sys.stdout.flush()
521 sys.stderr.flush()
526 sys.stderr.flush()
522
527
523 self.session.send(stream, u'apply_reply', reply_content,
528 self.session.send(stream, u'apply_reply', reply_content,
524 parent=parent, ident=ident,buffers=result_buf, metadata=md)
529 parent=parent, ident=ident,buffers=result_buf, metadata=md)
525
530
526 def do_apply(self, content, bufs, msg_id, reply_metadata):
531 def do_apply(self, content, bufs, msg_id, reply_metadata):
527 """Override in subclasses to support the IPython parallel framework.
532 """Override in subclasses to support the IPython parallel framework.
528 """
533 """
529 raise NotImplementedError
534 raise NotImplementedError
530
535
531 #---------------------------------------------------------------------------
536 #---------------------------------------------------------------------------
532 # Control messages
537 # Control messages
533 #---------------------------------------------------------------------------
538 #---------------------------------------------------------------------------
534
539
535 def abort_request(self, stream, ident, parent):
540 def abort_request(self, stream, ident, parent):
536 """abort a specific msg by id"""
541 """abort a specific msg by id"""
537 msg_ids = parent['content'].get('msg_ids', None)
542 msg_ids = parent['content'].get('msg_ids', None)
538 if isinstance(msg_ids, string_types):
543 if isinstance(msg_ids, string_types):
539 msg_ids = [msg_ids]
544 msg_ids = [msg_ids]
540 if not msg_ids:
545 if not msg_ids:
541 self._abort_queues()
546 self._abort_queues()
542 for mid in msg_ids:
547 for mid in msg_ids:
543 self.aborted.add(str(mid))
548 self.aborted.add(str(mid))
544
549
545 content = dict(status='ok')
550 content = dict(status='ok')
546 reply_msg = self.session.send(stream, 'abort_reply', content=content,
551 reply_msg = self.session.send(stream, 'abort_reply', content=content,
547 parent=parent, ident=ident)
552 parent=parent, ident=ident)
548 self.log.debug("%s", reply_msg)
553 self.log.debug("%s", reply_msg)
549
554
550 def clear_request(self, stream, idents, parent):
555 def clear_request(self, stream, idents, parent):
551 """Clear our namespace."""
556 """Clear our namespace."""
552 content = self.do_clear()
557 content = self.do_clear()
553 self.session.send(stream, 'clear_reply', ident=idents, parent=parent,
558 self.session.send(stream, 'clear_reply', ident=idents, parent=parent,
554 content = content)
559 content = content)
555
560
556 def do_clear(self):
561 def do_clear(self):
557 """Override in subclasses to clear the namespace
562 """Override in subclasses to clear the namespace
558
563
559 This is only required for IPython.parallel.
564 This is only required for IPython.parallel.
560 """
565 """
561 raise NotImplementedError
566 raise NotImplementedError
562
567
563 #---------------------------------------------------------------------------
568 #---------------------------------------------------------------------------
564 # Protected interface
569 # Protected interface
565 #---------------------------------------------------------------------------
570 #---------------------------------------------------------------------------
566
571
567 def _topic(self, topic):
572 def _topic(self, topic):
568 """prefixed topic for IOPub messages"""
573 """prefixed topic for IOPub messages"""
569 if self.int_id >= 0:
574 if self.int_id >= 0:
570 base = "engine.%i" % self.int_id
575 base = "engine.%i" % self.int_id
571 else:
576 else:
572 base = "kernel.%s" % self.ident
577 base = "kernel.%s" % self.ident
573
578
574 return py3compat.cast_bytes("%s.%s" % (base, topic))
579 return py3compat.cast_bytes("%s.%s" % (base, topic))
575
580
576 def _abort_queues(self):
581 def _abort_queues(self):
577 for stream in self.shell_streams:
582 for stream in self.shell_streams:
578 if stream:
583 if stream:
579 self._abort_queue(stream)
584 self._abort_queue(stream)
580
585
581 def _abort_queue(self, stream):
586 def _abort_queue(self, stream):
582 poller = zmq.Poller()
587 poller = zmq.Poller()
583 poller.register(stream.socket, zmq.POLLIN)
588 poller.register(stream.socket, zmq.POLLIN)
584 while True:
589 while True:
585 idents,msg = self.session.recv(stream, zmq.NOBLOCK, content=True)
590 idents,msg = self.session.recv(stream, zmq.NOBLOCK, content=True)
586 if msg is None:
591 if msg is None:
587 return
592 return
588
593
589 self.log.info("Aborting:")
594 self.log.info("Aborting:")
590 self.log.info("%s", msg)
595 self.log.info("%s", msg)
591 msg_type = msg['header']['msg_type']
596 msg_type = msg['header']['msg_type']
592 reply_type = msg_type.split('_')[0] + '_reply'
597 reply_type = msg_type.split('_')[0] + '_reply'
593
598
594 status = {'status' : 'aborted'}
599 status = {'status' : 'aborted'}
595 md = {'engine' : self.ident}
600 md = {'engine' : self.ident}
596 md.update(status)
601 md.update(status)
597 reply_msg = self.session.send(stream, reply_type, metadata=md,
602 reply_msg = self.session.send(stream, reply_type, metadata=md,
598 content=status, parent=msg, ident=idents)
603 content=status, parent=msg, ident=idents)
599 self.log.debug("%s", reply_msg)
604 self.log.debug("%s", reply_msg)
600 # We need to wait a bit for requests to come in. This can probably
605 # We need to wait a bit for requests to come in. This can probably
601 # be set shorter for true asynchronous clients.
606 # be set shorter for true asynchronous clients.
602 poller.poll(50)
607 poller.poll(50)
603
608
604
609
605 def _no_raw_input(self):
610 def _no_raw_input(self):
606 """Raise StdinNotImplentedError if active frontend doesn't support
611 """Raise StdinNotImplentedError if active frontend doesn't support
607 stdin."""
612 stdin."""
608 raise StdinNotImplementedError("raw_input was called, but this "
613 raise StdinNotImplementedError("raw_input was called, but this "
609 "frontend does not support stdin.")
614 "frontend does not support stdin.")
610
615
611 def getpass(self, prompt=''):
616 def getpass(self, prompt=''):
612 """Forward getpass to frontends
617 """Forward getpass to frontends
613
618
614 Raises
619 Raises
615 ------
620 ------
616 StdinNotImplentedError if active frontend doesn't support stdin.
621 StdinNotImplentedError if active frontend doesn't support stdin.
617 """
622 """
618 if not self._allow_stdin:
623 if not self._allow_stdin:
619 raise StdinNotImplementedError(
624 raise StdinNotImplementedError(
620 "getpass was called, but this frontend does not support input requests."
625 "getpass was called, but this frontend does not support input requests."
621 )
626 )
622 return self._input_request(prompt,
627 return self._input_request(prompt,
623 self._parent_ident,
628 self._parent_ident,
624 self._parent_header,
629 self._parent_header,
625 password=True,
630 password=True,
626 )
631 )
627
632
628 def raw_input(self, prompt=''):
633 def raw_input(self, prompt=''):
629 """Forward raw_input to frontends
634 """Forward raw_input to frontends
630
635
631 Raises
636 Raises
632 ------
637 ------
633 StdinNotImplentedError if active frontend doesn't support stdin.
638 StdinNotImplentedError if active frontend doesn't support stdin.
634 """
639 """
635 if not self._allow_stdin:
640 if not self._allow_stdin:
636 raise StdinNotImplementedError(
641 raise StdinNotImplementedError(
637 "raw_input was called, but this frontend does not support input requests."
642 "raw_input was called, but this frontend does not support input requests."
638 )
643 )
639 return self._input_request(prompt,
644 return self._input_request(prompt,
640 self._parent_ident,
645 self._parent_ident,
641 self._parent_header,
646 self._parent_header,
642 password=False,
647 password=False,
643 )
648 )
644
649
645 def _input_request(self, prompt, ident, parent, password=False):
650 def _input_request(self, prompt, ident, parent, password=False):
646 # Flush output before making the request.
651 # Flush output before making the request.
647 sys.stderr.flush()
652 sys.stderr.flush()
648 sys.stdout.flush()
653 sys.stdout.flush()
649 # flush the stdin socket, to purge stale replies
654 # flush the stdin socket, to purge stale replies
650 while True:
655 while True:
651 try:
656 try:
652 self.stdin_socket.recv_multipart(zmq.NOBLOCK)
657 self.stdin_socket.recv_multipart(zmq.NOBLOCK)
653 except zmq.ZMQError as e:
658 except zmq.ZMQError as e:
654 if e.errno == zmq.EAGAIN:
659 if e.errno == zmq.EAGAIN:
655 break
660 break
656 else:
661 else:
657 raise
662 raise
658
663
659 # Send the input request.
664 # Send the input request.
660 content = json_clean(dict(prompt=prompt, password=password))
665 content = json_clean(dict(prompt=prompt, password=password))
661 self.session.send(self.stdin_socket, u'input_request', content, parent,
666 self.session.send(self.stdin_socket, u'input_request', content, parent,
662 ident=ident)
667 ident=ident)
663
668
664 # Await a response.
669 # Await a response.
665 while True:
670 while True:
666 try:
671 try:
667 ident, reply = self.session.recv(self.stdin_socket, 0)
672 ident, reply = self.session.recv(self.stdin_socket, 0)
668 except Exception:
673 except Exception:
669 self.log.warn("Invalid Message:", exc_info=True)
674 self.log.warn("Invalid Message:", exc_info=True)
670 except KeyboardInterrupt:
675 except KeyboardInterrupt:
671 # re-raise KeyboardInterrupt, to truncate traceback
676 # re-raise KeyboardInterrupt, to truncate traceback
672 raise KeyboardInterrupt
677 raise KeyboardInterrupt
673 else:
678 else:
674 break
679 break
675 try:
680 try:
676 value = py3compat.unicode_to_str(reply['content']['value'])
681 value = py3compat.unicode_to_str(reply['content']['value'])
677 except:
682 except:
678 self.log.error("Bad input_reply: %s", parent)
683 self.log.error("Bad input_reply: %s", parent)
679 value = ''
684 value = ''
680 if value == '\x04':
685 if value == '\x04':
681 # EOF
686 # EOF
682 raise EOFError
687 raise EOFError
683 return value
688 return value
684
689
685 def _at_shutdown(self):
690 def _at_shutdown(self):
686 """Actions taken at shutdown by the kernel, called by python's atexit.
691 """Actions taken at shutdown by the kernel, called by python's atexit.
687 """
692 """
688 # io.rprint("Kernel at_shutdown") # dbg
693 # io.rprint("Kernel at_shutdown") # dbg
689 if self._shutdown_message is not None:
694 if self._shutdown_message is not None:
690 self.session.send(self.iopub_socket, self._shutdown_message, ident=self._topic('shutdown'))
695 self.session.send(self.iopub_socket, self._shutdown_message, ident=self._topic('shutdown'))
691 self.log.debug("%s", self._shutdown_message)
696 self.log.debug("%s", self._shutdown_message)
692 [ s.flush(zmq.POLLOUT) for s in self.shell_streams ]
697 [ s.flush(zmq.POLLOUT) for s in self.shell_streams ]
@@ -1,66 +1,66 b''
1 """HTML Exporter class"""
1 """HTML Exporter class"""
2
2
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (c) 2013, the IPython Development Team.
4 # Copyright (c) 2013, the IPython Development Team.
5 #
5 #
6 # Distributed under the terms of the Modified BSD License.
6 # Distributed under the terms of the Modified BSD License.
7 #
7 #
8 # The full license is in the file COPYING.txt, distributed with this software.
8 # The full license is in the file COPYING.txt, distributed with this software.
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Imports
12 # Imports
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 import os
15 import os
16
16
17 from IPython.nbconvert.filters.highlight import Highlight2HTML
17 from IPython.nbconvert.filters.highlight import Highlight2HTML
18 from IPython.config import Config
18 from IPython.config import Config
19
19
20 from .templateexporter import TemplateExporter
20 from .templateexporter import TemplateExporter
21
21
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23 # Classes
23 # Classes
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25
25
26 class HTMLExporter(TemplateExporter):
26 class HTMLExporter(TemplateExporter):
27 """
27 """
28 Exports a basic HTML document. This exporter assists with the export of
28 Exports a basic HTML document. This exporter assists with the export of
29 HTML. Inherit from it if you are writing your own HTML template and need
29 HTML. Inherit from it if you are writing your own HTML template and need
30 custom preprocessors/filters. If you don't need custom preprocessors/
30 custom preprocessors/filters. If you don't need custom preprocessors/
31 filters, just change the 'template_file' config option.
31 filters, just change the 'template_file' config option.
32 """
32 """
33
33
34 def _file_extension_default(self):
34 def _file_extension_default(self):
35 return 'html'
35 return 'html'
36
36
37 def _default_template_path_default(self):
37 def _default_template_path_default(self):
38 return os.path.join("..", "templates", "html")
38 return os.path.join("..", "templates", "html")
39
39
40 def _template_file_default(self):
40 def _template_file_default(self):
41 return 'full'
41 return 'full'
42
42
43 output_mimetype = 'text/html'
43 output_mimetype = 'text/html'
44
44
45 @property
45 @property
46 def default_config(self):
46 def default_config(self):
47 c = Config({
47 c = Config({
48 'NbConvertBase': {
48 'NbConvertBase': {
49 'display_data_priority' : ['javascript', 'html', 'application/pdf', 'svg', 'latex', 'png', 'jpg', 'jpeg' , 'text']
49 'display_data_priority' : ['javascript', 'html', 'application/pdf', 'svg', 'latex', 'png', 'jpg', 'jpeg' , 'text']
50 },
50 },
51 'CSSHTMLHeaderPreprocessor':{
51 'CSSHTMLHeaderPreprocessor':{
52 'enabled':True
52 'enabled':True
53 },
53 },
54 'HighlightMagicsPreprocessor': {
54 'HighlightMagicsPreprocessor': {
55 'enabled':True
55 'enabled':True
56 }
56 }
57 })
57 })
58 c.merge(super(HTMLExporter,self).default_config)
58 c.merge(super(HTMLExporter,self).default_config)
59 return c
59 return c
60
60
61 def from_notebook_node(self, nb, resources=None, **kw):
61 def from_notebook_node(self, nb, resources=None, **kw):
62 kernelspec = nb.metadata.get('kernelspec', {})
62 langinfo = nb.metadata.get('language_info', {})
63 lexer = kernelspec.get('pygments_lexer', kernelspec.get('language', None))
63 lexer = langinfo.get('pygments_lexer', langinfo.get('name', None))
64 self.register_filter('highlight_code',
64 self.register_filter('highlight_code',
65 Highlight2HTML(pygments_lexer=lexer, parent=self))
65 Highlight2HTML(pygments_lexer=lexer, parent=self))
66 return super(HTMLExporter, self).from_notebook_node(nb, resources, **kw)
66 return super(HTMLExporter, self).from_notebook_node(nb, resources, **kw)
@@ -1,96 +1,96 b''
1 """LaTeX Exporter class"""
1 """LaTeX Exporter class"""
2
2
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (c) 2013, the IPython Development Team.
4 # Copyright (c) 2013, the IPython Development Team.
5 #
5 #
6 # Distributed under the terms of the Modified BSD License.
6 # Distributed under the terms of the Modified BSD License.
7 #
7 #
8 # The full license is in the file COPYING.txt, distributed with this software.
8 # The full license is in the file COPYING.txt, distributed with this software.
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Imports
12 # Imports
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 # Stdlib imports
15 # Stdlib imports
16 import os
16 import os
17
17
18 # IPython imports
18 # IPython imports
19 from IPython.utils.traitlets import Unicode
19 from IPython.utils.traitlets import Unicode
20 from IPython.config import Config
20 from IPython.config import Config
21
21
22 from IPython.nbconvert.filters.highlight import Highlight2Latex
22 from IPython.nbconvert.filters.highlight import Highlight2Latex
23 from .templateexporter import TemplateExporter
23 from .templateexporter import TemplateExporter
24
24
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26 # Classes and functions
26 # Classes and functions
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28
28
29 class LatexExporter(TemplateExporter):
29 class LatexExporter(TemplateExporter):
30 """
30 """
31 Exports to a Latex template. Inherit from this class if your template is
31 Exports to a Latex template. Inherit from this class if your template is
32 LaTeX based and you need custom tranformers/filters. Inherit from it if
32 LaTeX based and you need custom tranformers/filters. Inherit from it if
33 you are writing your own HTML template and need custom tranformers/filters.
33 you are writing your own HTML template and need custom tranformers/filters.
34 If you don't need custom tranformers/filters, just change the
34 If you don't need custom tranformers/filters, just change the
35 'template_file' config option. Place your template in the special "/latex"
35 'template_file' config option. Place your template in the special "/latex"
36 subfolder of the "../templates" folder.
36 subfolder of the "../templates" folder.
37 """
37 """
38
38
39 def _file_extension_default(self):
39 def _file_extension_default(self):
40 return 'tex'
40 return 'tex'
41
41
42 def _template_file_default(self):
42 def _template_file_default(self):
43 return 'article'
43 return 'article'
44
44
45 #Latex constants
45 #Latex constants
46 def _default_template_path_default(self):
46 def _default_template_path_default(self):
47 return os.path.join("..", "templates", "latex")
47 return os.path.join("..", "templates", "latex")
48
48
49 def _template_skeleton_path_default(self):
49 def _template_skeleton_path_default(self):
50 return os.path.join("..", "templates", "latex", "skeleton")
50 return os.path.join("..", "templates", "latex", "skeleton")
51
51
52 #Special Jinja2 syntax that will not conflict when exporting latex.
52 #Special Jinja2 syntax that will not conflict when exporting latex.
53 jinja_comment_block_start = Unicode("((=", config=True)
53 jinja_comment_block_start = Unicode("((=", config=True)
54 jinja_comment_block_end = Unicode("=))", config=True)
54 jinja_comment_block_end = Unicode("=))", config=True)
55 jinja_variable_block_start = Unicode("(((", config=True)
55 jinja_variable_block_start = Unicode("(((", config=True)
56 jinja_variable_block_end = Unicode(")))", config=True)
56 jinja_variable_block_end = Unicode(")))", config=True)
57 jinja_logic_block_start = Unicode("((*", config=True)
57 jinja_logic_block_start = Unicode("((*", config=True)
58 jinja_logic_block_end = Unicode("*))", config=True)
58 jinja_logic_block_end = Unicode("*))", config=True)
59
59
60 #Extension that the template files use.
60 #Extension that the template files use.
61 template_extension = Unicode(".tplx", config=True)
61 template_extension = Unicode(".tplx", config=True)
62
62
63 output_mimetype = 'text/latex'
63 output_mimetype = 'text/latex'
64
64
65
65
66 @property
66 @property
67 def default_config(self):
67 def default_config(self):
68 c = Config({
68 c = Config({
69 'NbConvertBase': {
69 'NbConvertBase': {
70 'display_data_priority' : ['latex', 'application/pdf', 'png', 'jpg', 'svg', 'jpeg', 'text']
70 'display_data_priority' : ['latex', 'application/pdf', 'png', 'jpg', 'svg', 'jpeg', 'text']
71 },
71 },
72 'ExtractOutputPreprocessor': {
72 'ExtractOutputPreprocessor': {
73 'enabled':True
73 'enabled':True
74 },
74 },
75 'SVG2PDFPreprocessor': {
75 'SVG2PDFPreprocessor': {
76 'enabled':True
76 'enabled':True
77 },
77 },
78 'LatexPreprocessor': {
78 'LatexPreprocessor': {
79 'enabled':True
79 'enabled':True
80 },
80 },
81 'SphinxPreprocessor': {
81 'SphinxPreprocessor': {
82 'enabled':True
82 'enabled':True
83 },
83 },
84 'HighlightMagicsPreprocessor': {
84 'HighlightMagicsPreprocessor': {
85 'enabled':True
85 'enabled':True
86 }
86 }
87 })
87 })
88 c.merge(super(LatexExporter,self).default_config)
88 c.merge(super(LatexExporter,self).default_config)
89 return c
89 return c
90
90
91 def from_notebook_node(self, nb, resources=None, **kw):
91 def from_notebook_node(self, nb, resources=None, **kw):
92 kernelspec = nb.metadata.get('kernelspec', {})
92 langinfo = nb.metadata.get('language_info', {})
93 lexer = kernelspec.get('pygments_lexer', kernelspec.get('language', None))
93 lexer = langinfo.get('pygments_lexer', langinfo.get('name', None))
94 self.register_filter('highlight_code',
94 self.register_filter('highlight_code',
95 Highlight2Latex(pygments_lexer=lexer, parent=self))
95 Highlight2Latex(pygments_lexer=lexer, parent=self))
96 return super(LatexExporter, self).from_notebook_node(nb, resources, **kw)
96 return super(LatexExporter, self).from_notebook_node(nb, resources, **kw)
@@ -1,135 +1,135 b''
1 """
1 """
2 Module containing filter functions that allow code to be highlighted
2 Module containing filter functions that allow code to be highlighted
3 from within Jinja templates.
3 from within Jinja templates.
4 """
4 """
5
5
6 # Copyright (c) IPython Development Team.
6 # Copyright (c) IPython Development Team.
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8
8
9 # pygments must not be imported at the module level
9 # pygments must not be imported at the module level
10 # because errors should be raised at runtime if it's actually needed,
10 # because errors should be raised at runtime if it's actually needed,
11 # not import time, when it may not be needed.
11 # not import time, when it may not be needed.
12
12
13 from IPython.nbconvert.utils.base import NbConvertBase
13 from IPython.nbconvert.utils.base import NbConvertBase
14 from warnings import warn
14 from warnings import warn
15
15
16 MULTILINE_OUTPUTS = ['text', 'html', 'svg', 'latex', 'javascript', 'json']
16 MULTILINE_OUTPUTS = ['text', 'html', 'svg', 'latex', 'javascript', 'json']
17
17
18 __all__ = [
18 __all__ = [
19 'Highlight2HTML',
19 'Highlight2HTML',
20 'Highlight2Latex'
20 'Highlight2Latex'
21 ]
21 ]
22
22
23 class Highlight2HTML(NbConvertBase):
23 class Highlight2HTML(NbConvertBase):
24 def __init__(self, pygments_lexer=None, **kwargs):
24 def __init__(self, pygments_lexer=None, **kwargs):
25 self.pygments_lexer = pygments_lexer or 'ipython3'
25 self.pygments_lexer = pygments_lexer or 'ipython3'
26 super(Highlight2HTML, self).__init__(**kwargs)
26 super(Highlight2HTML, self).__init__(**kwargs)
27
27
28 def _default_language_changed(self, name, old, new):
28 def _default_language_changed(self, name, old, new):
29 warn('Setting default_language in config is deprecated, '
29 warn('Setting default_language in config is deprecated, '
30 'please use kernelspecs instead.')
30 'please use language_info metadata instead.')
31 self.pygments_lexer = new
31 self.pygments_lexer = new
32
32
33 def __call__(self, source, language=None, metadata=None):
33 def __call__(self, source, language=None, metadata=None):
34 """
34 """
35 Return a syntax-highlighted version of the input source as html output.
35 Return a syntax-highlighted version of the input source as html output.
36
36
37 Parameters
37 Parameters
38 ----------
38 ----------
39 source : str
39 source : str
40 source of the cell to highlight
40 source of the cell to highlight
41 language : str
41 language : str
42 language to highlight the syntax of
42 language to highlight the syntax of
43 metadata : NotebookNode cell metadata
43 metadata : NotebookNode cell metadata
44 metadata of the cell to highlight
44 metadata of the cell to highlight
45 """
45 """
46 from pygments.formatters import HtmlFormatter
46 from pygments.formatters import HtmlFormatter
47
47
48 if not language:
48 if not language:
49 language=self.pygments_lexer
49 language=self.pygments_lexer
50
50
51 return _pygments_highlight(source if len(source) > 0 else ' ',
51 return _pygments_highlight(source if len(source) > 0 else ' ',
52 # needed to help post processors:
52 # needed to help post processors:
53 HtmlFormatter(cssclass=" highlight hl-"+language),
53 HtmlFormatter(cssclass=" highlight hl-"+language),
54 language, metadata)
54 language, metadata)
55
55
56
56
57 class Highlight2Latex(NbConvertBase):
57 class Highlight2Latex(NbConvertBase):
58 def __init__(self, pygments_lexer=None, **kwargs):
58 def __init__(self, pygments_lexer=None, **kwargs):
59 self.pygments_lexer = pygments_lexer or 'ipython3'
59 self.pygments_lexer = pygments_lexer or 'ipython3'
60 super(Highlight2Latex, self).__init__(**kwargs)
60 super(Highlight2Latex, self).__init__(**kwargs)
61
61
62 def _default_language_changed(self, name, old, new):
62 def _default_language_changed(self, name, old, new):
63 warn('Setting default_language in config is deprecated, '
63 warn('Setting default_language in config is deprecated, '
64 'please use kernelspecs instead.')
64 'please use language_info metadata instead.')
65 self.pygments_lexer = new
65 self.pygments_lexer = new
66
66
67 def __call__(self, source, language=None, metadata=None, strip_verbatim=False):
67 def __call__(self, source, language=None, metadata=None, strip_verbatim=False):
68 """
68 """
69 Return a syntax-highlighted version of the input source as latex output.
69 Return a syntax-highlighted version of the input source as latex output.
70
70
71 Parameters
71 Parameters
72 ----------
72 ----------
73 source : str
73 source : str
74 source of the cell to highlight
74 source of the cell to highlight
75 language : str
75 language : str
76 language to highlight the syntax of
76 language to highlight the syntax of
77 metadata : NotebookNode cell metadata
77 metadata : NotebookNode cell metadata
78 metadata of the cell to highlight
78 metadata of the cell to highlight
79 strip_verbatim : bool
79 strip_verbatim : bool
80 remove the Verbatim environment that pygments provides by default
80 remove the Verbatim environment that pygments provides by default
81 """
81 """
82 from pygments.formatters import LatexFormatter
82 from pygments.formatters import LatexFormatter
83 if not language:
83 if not language:
84 language=self.pygments_lexer
84 language=self.pygments_lexer
85
85
86 latex = _pygments_highlight(source, LatexFormatter(), language, metadata)
86 latex = _pygments_highlight(source, LatexFormatter(), language, metadata)
87 if strip_verbatim:
87 if strip_verbatim:
88 latex = latex.replace(r'\begin{Verbatim}[commandchars=\\\{\}]' + '\n', '')
88 latex = latex.replace(r'\begin{Verbatim}[commandchars=\\\{\}]' + '\n', '')
89 return latex.replace('\n\\end{Verbatim}\n', '')
89 return latex.replace('\n\\end{Verbatim}\n', '')
90 else:
90 else:
91 return latex
91 return latex
92
92
93
93
94
94
95 def _pygments_highlight(source, output_formatter, language='ipython', metadata=None):
95 def _pygments_highlight(source, output_formatter, language='ipython', metadata=None):
96 """
96 """
97 Return a syntax-highlighted version of the input source
97 Return a syntax-highlighted version of the input source
98
98
99 Parameters
99 Parameters
100 ----------
100 ----------
101 source : str
101 source : str
102 source of the cell to highlight
102 source of the cell to highlight
103 output_formatter : Pygments formatter
103 output_formatter : Pygments formatter
104 language : str
104 language : str
105 language to highlight the syntax of
105 language to highlight the syntax of
106 metadata : NotebookNode cell metadata
106 metadata : NotebookNode cell metadata
107 metadata of the cell to highlight
107 metadata of the cell to highlight
108 """
108 """
109 from pygments import highlight
109 from pygments import highlight
110 from pygments.lexers import get_lexer_by_name
110 from pygments.lexers import get_lexer_by_name
111 from pygments.util import ClassNotFound
111 from pygments.util import ClassNotFound
112 from IPython.nbconvert.utils.lexers import IPythonLexer, IPython3Lexer
112 from IPython.nbconvert.utils.lexers import IPythonLexer, IPython3Lexer
113
113
114 # If the cell uses a magic extension language,
114 # If the cell uses a magic extension language,
115 # use the magic language instead.
115 # use the magic language instead.
116 if language == 'ipython' \
116 if language == 'ipython' \
117 and metadata \
117 and metadata \
118 and 'magics_language' in metadata:
118 and 'magics_language' in metadata:
119
119
120 language = metadata['magics_language']
120 language = metadata['magics_language']
121
121
122 if language == 'ipython2':
122 if language == 'ipython2':
123 lexer = IPythonLexer()
123 lexer = IPythonLexer()
124 elif language == 'ipython3':
124 elif language == 'ipython3':
125 lexer = IPython3Lexer()
125 lexer = IPython3Lexer()
126 else:
126 else:
127 try:
127 try:
128 lexer = get_lexer_by_name(language, stripall=True)
128 lexer = get_lexer_by_name(language, stripall=True)
129 except ClassNotFound:
129 except ClassNotFound:
130 warn("No lexer found for language %r. Treating as plain text." % language)
130 warn("No lexer found for language %r. Treating as plain text." % language)
131 from pygments.lexers.special import TextLexer
131 from pygments.lexers.special import TextLexer
132 lexer = TextLexer()
132 lexer = TextLexer()
133
133
134
134
135 return highlight(source, lexer, output_formatter)
135 return highlight(source, lexer, output_formatter)
@@ -1,41 +1,41 b''
1 """Global configuration class."""
1 """Global configuration class."""
2 #-----------------------------------------------------------------------------
2 #-----------------------------------------------------------------------------
3 # Copyright (c) 2013, the IPython Development Team.
3 # Copyright (c) 2013, the IPython Development Team.
4 #
4 #
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6 #
6 #
7 # The full license is in the file COPYING.txt, distributed with this software.
7 # The full license is in the file COPYING.txt, distributed with this software.
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Imports
11 # Imports
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 from IPython.utils.traitlets import List
14 from IPython.utils.traitlets import List
15 from IPython.config.configurable import LoggingConfigurable
15 from IPython.config.configurable import LoggingConfigurable
16 from IPython.utils.traitlets import Unicode
16 from IPython.utils.traitlets import Unicode
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Classes and functions
19 # Classes and functions
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 class NbConvertBase(LoggingConfigurable):
22 class NbConvertBase(LoggingConfigurable):
23 """Global configurable class for shared config
23 """Global configurable class for shared config
24
24
25 Useful for display data priority that might be use by many transformers
25 Useful for display data priority that might be use by many transformers
26 """
26 """
27
27
28 display_data_priority = List(['html', 'application/pdf', 'svg', 'latex', 'png', 'jpg', 'jpeg' , 'text'],
28 display_data_priority = List(['html', 'application/pdf', 'svg', 'latex', 'png', 'jpg', 'jpeg' , 'text'],
29 config=True,
29 config=True,
30 help= """
30 help= """
31 An ordered list of preferred output type, the first
31 An ordered list of preferred output type, the first
32 encountered will usually be used when converting discarding
32 encountered will usually be used when converting discarding
33 the others.
33 the others.
34 """
34 """
35 )
35 )
36
36
37 default_language = Unicode('ipython', config=True,
37 default_language = Unicode('ipython', config=True,
38 help='DEPRECATED default highlight language, please use kernelspecs instead')
38 help='DEPRECATED default highlight language, please use language_info metadata instead')
39
39
40 def __init__(self, **kw):
40 def __init__(self, **kw):
41 super(NbConvertBase, self).__init__(**kw)
41 super(NbConvertBase, self).__init__(**kw)
@@ -1,155 +1,137 b''
1 ==========================
1 ==========================
2 Making kernels for IPython
2 Making kernels for IPython
3 ==========================
3 ==========================
4
4
5 A 'kernel' is a program that runs and introspects the user's code. IPython
5 A 'kernel' is a program that runs and introspects the user's code. IPython
6 includes a kernel for Python code, and people have written kernels for
6 includes a kernel for Python code, and people have written kernels for
7 `several other languages <https://github.com/ipython/ipython/wiki/Projects-using-IPython#list-of-some-ipython-compatible-kernels>`_.
7 `several other languages <https://github.com/ipython/ipython/wiki/Projects-using-IPython#list-of-some-ipython-compatible-kernels>`_.
8
8
9 When IPython starts a kernel, it passes it a connection file. This specifies
9 When IPython starts a kernel, it passes it a connection file. This specifies
10 how to set up communications with the frontend.
10 how to set up communications with the frontend.
11
11
12 There are two options for writing a kernel:
12 There are two options for writing a kernel:
13
13
14 1. You can reuse the IPython kernel machinery to handle the communications, and
14 1. You can reuse the IPython kernel machinery to handle the communications, and
15 just describe how to execute your code. This is much simpler if the target
15 just describe how to execute your code. This is much simpler if the target
16 language can be driven from Python. See :doc:`wrapperkernels` for details.
16 language can be driven from Python. See :doc:`wrapperkernels` for details.
17 2. You can implement the kernel machinery in your target language. This is more
17 2. You can implement the kernel machinery in your target language. This is more
18 work initially, but the people using your kernel might be more likely to
18 work initially, but the people using your kernel might be more likely to
19 contribute to it if it's in the language they know.
19 contribute to it if it's in the language they know.
20
20
21 Connection files
21 Connection files
22 ================
22 ================
23
23
24 Your kernel will be given the path to a connection file when it starts (see
24 Your kernel will be given the path to a connection file when it starts (see
25 :ref:`kernelspecs` for how to specify the command line arguments for your kernel).
25 :ref:`kernelspecs` for how to specify the command line arguments for your kernel).
26 This file, which is accessible only to the current user, will contain a JSON
26 This file, which is accessible only to the current user, will contain a JSON
27 dictionary looking something like this::
27 dictionary looking something like this::
28
28
29 {
29 {
30 "control_port": 50160,
30 "control_port": 50160,
31 "shell_port": 57503,
31 "shell_port": 57503,
32 "transport": "tcp",
32 "transport": "tcp",
33 "signature_scheme": "hmac-sha256",
33 "signature_scheme": "hmac-sha256",
34 "stdin_port": 52597,
34 "stdin_port": 52597,
35 "hb_port": 42540,
35 "hb_port": 42540,
36 "ip": "127.0.0.1",
36 "ip": "127.0.0.1",
37 "iopub_port": 40885,
37 "iopub_port": 40885,
38 "key": "a0436f6c-1916-498b-8eb9-e81ab9368e84"
38 "key": "a0436f6c-1916-498b-8eb9-e81ab9368e84"
39 }
39 }
40
40
41 The ``transport``, ``ip`` and five ``_port`` fields specify five ports which the
41 The ``transport``, ``ip`` and five ``_port`` fields specify five ports which the
42 kernel should bind to using `ZeroMQ <http://zeromq.org/>`_. For instance, the
42 kernel should bind to using `ZeroMQ <http://zeromq.org/>`_. For instance, the
43 address of the shell socket in the example above would be::
43 address of the shell socket in the example above would be::
44
44
45 tcp://127.0.0.1:57503
45 tcp://127.0.0.1:57503
46
46
47 New ports are chosen at random for each kernel started.
47 New ports are chosen at random for each kernel started.
48
48
49 ``signature_scheme`` and ``key`` are used to cryptographically sign messages, so
49 ``signature_scheme`` and ``key`` are used to cryptographically sign messages, so
50 that other users on the system can't send code to run in this kernel. See
50 that other users on the system can't send code to run in this kernel. See
51 :ref:`wire_protocol` for the details of how this signature is calculated.
51 :ref:`wire_protocol` for the details of how this signature is calculated.
52
52
53 Handling messages
53 Handling messages
54 =================
54 =================
55
55
56 After reading the connection file and binding to the necessary sockets, the
56 After reading the connection file and binding to the necessary sockets, the
57 kernel should go into an event loop, listening on the hb (heartbeat), control
57 kernel should go into an event loop, listening on the hb (heartbeat), control
58 and shell sockets.
58 and shell sockets.
59
59
60 :ref:`Heartbeat <kernel_heartbeat>` messages should be echoed back immediately
60 :ref:`Heartbeat <kernel_heartbeat>` messages should be echoed back immediately
61 on the same socket - the frontend uses this to check that the kernel is still
61 on the same socket - the frontend uses this to check that the kernel is still
62 alive.
62 alive.
63
63
64 Messages on the control and shell sockets should be parsed, and their signature
64 Messages on the control and shell sockets should be parsed, and their signature
65 validated. See :ref:`wire_protocol` for how to do this.
65 validated. See :ref:`wire_protocol` for how to do this.
66
66
67 The kernel will send messages on the iopub socket to display output, and on the
67 The kernel will send messages on the iopub socket to display output, and on the
68 stdin socket to prompt the user for textual input.
68 stdin socket to prompt the user for textual input.
69
69
70 .. seealso::
70 .. seealso::
71
71
72 :doc:`messaging`
72 :doc:`messaging`
73 Details of the different sockets and the messages that come over them
73 Details of the different sockets and the messages that come over them
74
74
75 `Creating Language Kernels for IPython <http://andrew.gibiansky.com/blog/ipython/ipython-kernels/>`_
75 `Creating Language Kernels for IPython <http://andrew.gibiansky.com/blog/ipython/ipython-kernels/>`_
76 A blog post by the author of `IHaskell <https://github.com/gibiansky/IHaskell>`_,
76 A blog post by the author of `IHaskell <https://github.com/gibiansky/IHaskell>`_,
77 a Haskell kernel
77 a Haskell kernel
78
78
79 `simple_kernel <https://github.com/dsblank/simple_kernel>`_
79 `simple_kernel <https://github.com/dsblank/simple_kernel>`_
80 A simple example implementation of the kernel machinery in Python
80 A simple example implementation of the kernel machinery in Python
81
81
82
82
83 .. _kernelspecs:
83 .. _kernelspecs:
84
84
85 Kernel specs
85 Kernel specs
86 ============
86 ============
87
87
88 A kernel identifies itself to IPython by creating a directory, the name of which
88 A kernel identifies itself to IPython by creating a directory, the name of which
89 is used as an identifier for the kernel. These may be created in a number of
89 is used as an identifier for the kernel. These may be created in a number of
90 locations:
90 locations:
91
91
92 +--------+--------------------------------------+-----------------------------------+
92 +--------+--------------------------------------+-----------------------------------+
93 | | Unix | Windows |
93 | | Unix | Windows |
94 +========+======================================+===================================+
94 +========+======================================+===================================+
95 | System | ``/usr/share/ipython/kernels`` | ``%PROGRAMDATA%\ipython\kernels`` |
95 | System | ``/usr/share/ipython/kernels`` | ``%PROGRAMDATA%\ipython\kernels`` |
96 | | | |
96 | | | |
97 | | ``/usr/local/share/ipython/kernels`` | |
97 | | ``/usr/local/share/ipython/kernels`` | |
98 +--------+--------------------------------------+-----------------------------------+
98 +--------+--------------------------------------+-----------------------------------+
99 | User | ``~/.ipython/kernels`` |
99 | User | ``~/.ipython/kernels`` |
100 +--------+--------------------------------------+-----------------------------------+
100 +--------+--------------------------------------+-----------------------------------+
101
101
102 The user location takes priority over the system locations, and the case of the
102 The user location takes priority over the system locations, and the case of the
103 names is ignored, so selecting kernels works the same way whether or not the
103 names is ignored, so selecting kernels works the same way whether or not the
104 filesystem is case sensitive.
104 filesystem is case sensitive.
105
105
106 Inside the directory, the most important file is *kernel.json*. This should be a
106 Inside the directory, the most important file is *kernel.json*. This should be a
107 JSON serialised dictionary containing the following keys and values:
107 JSON serialised dictionary containing the following keys and values:
108
108
109 - **argv**: A list of command line arguments used to start the kernel. The text
109 - **argv**: A list of command line arguments used to start the kernel. The text
110 ``{connection_file}`` in any argument will be replaced with the path to the
110 ``{connection_file}`` in any argument will be replaced with the path to the
111 connection file.
111 connection file.
112 - **display_name**: The kernel's name as it should be displayed in the UI.
112 - **display_name**: The kernel's name as it should be displayed in the UI.
113 Unlike the kernel name used in the API, this can contain arbitrary unicode
113 Unlike the kernel name used in the API, this can contain arbitrary unicode
114 characters.
114 characters.
115 - **language**: The programming language which this kernel runs. This will be
116 stored in notebook metadata. This may be used by syntax highlighters to guess
117 how to parse code in a notebook, and frontends may eventually use it to
118 identify alternative kernels that can run some code.
119 - **codemirror_mode** (optional): The `codemirror mode <http://codemirror.net/mode/index.html>`_
120 to use for code in this language. This can be a string or a dictionary, as
121 passed to codemirror config. This only needs to be specified if it does not
122 match the value in *language*.
123 - **pygments_lexer** (optional): The name of a `Pygments lexer <http://pygments.org/docs/lexers/>`_
124 to use for code in this language, as a string. This only needs to be specified
125 if it does not match the value in *language*.
126 - **env** (optional): A dictionary of environment variables to set for the kernel.
115 - **env** (optional): A dictionary of environment variables to set for the kernel.
127 These will be added to the current environment variables before the kernel is
116 These will be added to the current environment variables before the kernel is
128 started.
117 started.
129 - **help_links** (optional): A list of dictionaries, each with keys 'text' and
130 'url'. These will be displayed in the help menu in the notebook UI.
131
118
132 For example, the kernel.json file for IPython looks like this::
119 For example, the kernel.json file for IPython looks like this::
133
120
134 {
121 {
135 "argv": ["python3", "-c", "from IPython.kernel.zmq.kernelapp import main; main()",
122 "argv": ["python3", "-c", "from IPython.kernel.zmq.kernelapp import main; main()",
136 "-f", "{connection_file}"],
123 "-f", "{connection_file}"],
137 "codemirror_mode": {
138 "version": 3,
139 "name": "ipython"
140 },
141 "display_name": "IPython (Python 3)",
124 "display_name": "IPython (Python 3)",
142 "language": "python"
143 }
125 }
144
126
145 To see the available kernel specs, run::
127 To see the available kernel specs, run::
146
128
147 ipython kernelspec list
129 ipython kernelspec list
148
130
149 To start the terminal console or the Qt console with a specific kernel::
131 To start the terminal console or the Qt console with a specific kernel::
150
132
151 ipython console --kernel bash
133 ipython console --kernel bash
152 ipython qtconsole --kernel bash
134 ipython qtconsole --kernel bash
153
135
154 To use different kernels in the notebook, select a different kernel from the
136 To use different kernels in the notebook, select a different kernel from the
155 dropdown menu in the top-right of the UI.
137 dropdown menu in the top-right of the UI.
@@ -1,1115 +1,1138 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.
358 # 'payload' will be a list of payload dicts.
359 # Each execution payload is a dict with string keys that may have been
359 # Each execution payload is a dict with string keys that may have been
360 # produced by the code being executed. It is retrieved by the kernel at
360 # produced by the code being executed. It is retrieved by the kernel at
361 # the end of the execution and sent back to the front end, which can take
361 # the end of the execution and sent back to the front end, which can take
362 # action on it as needed.
362 # action on it as needed.
363 # The only requirement of each payload dict is that it have a 'source' key,
363 # The only requirement of each payload dict is that it have a 'source' key,
364 # which is a string classifying the payload (e.g. 'pager').
364 # which is a string classifying the payload (e.g. 'pager').
365 'payload' : list(dict),
365 'payload' : list(dict),
366
366
367 # Results for the user_expressions.
367 # Results for the user_expressions.
368 'user_expressions' : dict,
368 'user_expressions' : dict,
369 }
369 }
370
370
371 .. versionchanged:: 5.0
371 .. versionchanged:: 5.0
372
372
373 ``user_variables`` is removed, use user_expressions instead.
373 ``user_variables`` is removed, use user_expressions instead.
374
374
375 .. admonition:: Execution payloads
375 .. admonition:: Execution payloads
376
376
377 The notion of an 'execution payload' is different from a return value of a
377 The notion of an 'execution payload' is different from a return value of a
378 given set of code, which normally is just displayed on the execute_result stream
378 given set of code, which normally is just displayed on the execute_result stream
379 through the PUB socket. The idea of a payload is to allow special types of
379 through the PUB socket. The idea of a payload is to allow special types of
380 code, typically magics, to populate a data container in the IPython kernel
380 code, typically magics, to populate a data container in the IPython kernel
381 that will be shipped back to the caller via this channel. The kernel
381 that will be shipped back to the caller via this channel. The kernel
382 has an API for this in the PayloadManager::
382 has an API for this in the PayloadManager::
383
383
384 ip.payload_manager.write_payload(payload_dict)
384 ip.payload_manager.write_payload(payload_dict)
385
385
386 which appends a dictionary to the list of payloads.
386 which appends a dictionary to the list of payloads.
387
387
388 The payload API is not yet stabilized,
388 The payload API is not yet stabilized,
389 and should probably not be supported by non-Python kernels at this time.
389 and should probably not be supported by non-Python kernels at this time.
390 In such cases, the payload list should always be empty.
390 In such cases, the payload list should always be empty.
391
391
392
392
393 When status is 'error', the following extra fields are present::
393 When status is 'error', the following extra fields are present::
394
394
395 {
395 {
396 'ename' : str, # Exception name, as a string
396 'ename' : str, # Exception name, as a string
397 'evalue' : str, # Exception value, as a string
397 'evalue' : str, # Exception value, as a string
398
398
399 # The traceback will contain a list of frames, represented each as a
399 # The traceback will contain a list of frames, represented each as a
400 # string. For now we'll stick to the existing design of ultraTB, which
400 # string. For now we'll stick to the existing design of ultraTB, which
401 # controls exception level of detail statefully. But eventually we'll
401 # controls exception level of detail statefully. But eventually we'll
402 # want to grow into a model where more information is collected and
402 # want to grow into a model where more information is collected and
403 # packed into the traceback object, with clients deciding how little or
403 # packed into the traceback object, with clients deciding how little or
404 # how much of it to unpack. But for now, let's start with a simple list
404 # how much of it to unpack. But for now, let's start with a simple list
405 # of strings, since that requires only minimal changes to ultratb as
405 # of strings, since that requires only minimal changes to ultratb as
406 # written.
406 # written.
407 'traceback' : list,
407 'traceback' : list,
408 }
408 }
409
409
410
410
411 When status is 'abort', there are for now no additional data fields. This
411 When status is 'abort', there are for now no additional data fields. This
412 happens when the kernel was interrupted by a signal.
412 happens when the kernel was interrupted by a signal.
413
413
414 .. _msging_inspection:
414 .. _msging_inspection:
415
415
416 Introspection
416 Introspection
417 -------------
417 -------------
418
418
419 Code can be inspected to show useful information to the user.
419 Code can be inspected to show useful information to the user.
420 It is up to the Kernel to decide what information should be displayed, and its formatting.
420 It is up to the Kernel to decide what information should be displayed, and its formatting.
421
421
422 Message type: ``inspect_request``::
422 Message type: ``inspect_request``::
423
423
424 content = {
424 content = {
425 # The code context in which introspection is requested
425 # The code context in which introspection is requested
426 # this may be up to an entire multiline cell.
426 # this may be up to an entire multiline cell.
427 'code' : str,
427 'code' : str,
428
428
429 # The cursor position within 'code' (in unicode characters) where inspection is requested
429 # The cursor position within 'code' (in unicode characters) where inspection is requested
430 'cursor_pos' : int,
430 'cursor_pos' : int,
431
431
432 # The level of detail desired. In IPython, the default (0) is equivalent to typing
432 # The level of detail desired. In IPython, the default (0) is equivalent to typing
433 # 'x?' at the prompt, 1 is equivalent to 'x??'.
433 # 'x?' at the prompt, 1 is equivalent to 'x??'.
434 # The difference is up to kernels, but in IPython level 1 includes the source code
434 # The difference is up to kernels, but in IPython level 1 includes the source code
435 # if available.
435 # if available.
436 'detail_level' : 0 or 1,
436 'detail_level' : 0 or 1,
437 }
437 }
438
438
439 .. versionchanged:: 5.0
439 .. versionchanged:: 5.0
440
440
441 ``object_info_request`` renamed to ``inspect_request``.
441 ``object_info_request`` renamed to ``inspect_request``.
442
442
443 .. versionchanged:: 5.0
443 .. versionchanged:: 5.0
444
444
445 ``name`` key replaced with ``code`` and ``cursor_pos``,
445 ``name`` key replaced with ``code`` and ``cursor_pos``,
446 moving the lexing responsibility to the kernel.
446 moving the lexing responsibility to the kernel.
447
447
448 The reply is a mime-bundle, like a `display_data`_ message,
448 The reply is a mime-bundle, like a `display_data`_ message,
449 which should be a formatted representation of information about the context.
449 which should be a formatted representation of information about the context.
450 In the notebook, this is used to show tooltips over function calls, etc.
450 In the notebook, this is used to show tooltips over function calls, etc.
451
451
452 Message type: ``inspect_reply``::
452 Message type: ``inspect_reply``::
453
453
454 content = {
454 content = {
455 # 'ok' if the request succeeded or 'error', with error information as in all other replies.
455 # 'ok' if the request succeeded or 'error', with error information as in all other replies.
456 'status' : 'ok',
456 'status' : 'ok',
457
457
458 # data can be empty if nothing is found
458 # data can be empty if nothing is found
459 'data' : dict,
459 'data' : dict,
460 'metadata' : dict,
460 'metadata' : dict,
461 }
461 }
462
462
463 .. versionchanged:: 5.0
463 .. versionchanged:: 5.0
464
464
465 ``object_info_reply`` renamed to ``inspect_reply``.
465 ``object_info_reply`` renamed to ``inspect_reply``.
466
466
467 .. versionchanged:: 5.0
467 .. versionchanged:: 5.0
468
468
469 Reply is changed from structured data to a mime bundle, allowing formatting decisions to be made by the kernel.
469 Reply is changed from structured data to a mime bundle, allowing formatting decisions to be made by the kernel.
470
470
471 .. _msging_completion:
471 .. _msging_completion:
472
472
473 Completion
473 Completion
474 ----------
474 ----------
475
475
476 Message type: ``complete_request``::
476 Message type: ``complete_request``::
477
477
478 content = {
478 content = {
479 # The code context in which completion is requested
479 # The code context in which completion is requested
480 # this may be up to an entire multiline cell, such as
480 # this may be up to an entire multiline cell, such as
481 # 'foo = a.isal'
481 # 'foo = a.isal'
482 'code' : str,
482 'code' : str,
483
483
484 # The cursor position within 'code' (in unicode characters) where completion is requested
484 # The cursor position within 'code' (in unicode characters) where completion is requested
485 'cursor_pos' : int,
485 'cursor_pos' : int,
486 }
486 }
487
487
488 .. versionchanged:: 5.0
488 .. versionchanged:: 5.0
489
489
490 ``line``, ``block``, and ``text`` keys are removed in favor of a single ``code`` for context.
490 ``line``, ``block``, and ``text`` keys are removed in favor of a single ``code`` for context.
491 Lexing is up to the kernel.
491 Lexing is up to the kernel.
492
492
493
493
494 Message type: ``complete_reply``::
494 Message type: ``complete_reply``::
495
495
496 content = {
496 content = {
497 # The list of all matches to the completion request, such as
497 # The list of all matches to the completion request, such as
498 # ['a.isalnum', 'a.isalpha'] for the above example.
498 # ['a.isalnum', 'a.isalpha'] for the above example.
499 'matches' : list,
499 'matches' : list,
500
500
501 # The range of text that should be replaced by the above matches when a completion is accepted.
501 # The range of text that should be replaced by the above matches when a completion is accepted.
502 # typically cursor_end is the same as cursor_pos in the request.
502 # typically cursor_end is the same as cursor_pos in the request.
503 'cursor_start' : int,
503 'cursor_start' : int,
504 'cursor_end' : int,
504 'cursor_end' : int,
505
505
506 # Information that frontend plugins might use for extra display information about completions.
506 # Information that frontend plugins might use for extra display information about completions.
507 'metadata' : dict,
507 'metadata' : dict,
508
508
509 # status should be 'ok' unless an exception was raised during the request,
509 # status should be 'ok' unless an exception was raised during the request,
510 # in which case it should be 'error', along with the usual error message content
510 # in which case it should be 'error', along with the usual error message content
511 # in other messages.
511 # in other messages.
512 'status' : 'ok'
512 'status' : 'ok'
513 }
513 }
514
514
515 .. versionchanged:: 5.0
515 .. versionchanged:: 5.0
516
516
517 - ``matched_text`` is removed in favor of ``cursor_start`` and ``cursor_end``.
517 - ``matched_text`` is removed in favor of ``cursor_start`` and ``cursor_end``.
518 - ``metadata`` is added for extended information.
518 - ``metadata`` is added for extended information.
519
519
520 .. _msging_history:
520 .. _msging_history:
521
521
522 History
522 History
523 -------
523 -------
524
524
525 For clients to explicitly request history from a kernel. The kernel has all
525 For clients to explicitly request history from a kernel. The kernel has all
526 the actual execution history stored in a single location, so clients can
526 the actual execution history stored in a single location, so clients can
527 request it from the kernel when needed.
527 request it from the kernel when needed.
528
528
529 Message type: ``history_request``::
529 Message type: ``history_request``::
530
530
531 content = {
531 content = {
532
532
533 # If True, also return output history in the resulting dict.
533 # If True, also return output history in the resulting dict.
534 'output' : bool,
534 'output' : bool,
535
535
536 # If True, return the raw input history, else the transformed input.
536 # If True, return the raw input history, else the transformed input.
537 'raw' : bool,
537 'raw' : bool,
538
538
539 # So far, this can be 'range', 'tail' or 'search'.
539 # So far, this can be 'range', 'tail' or 'search'.
540 'hist_access_type' : str,
540 'hist_access_type' : str,
541
541
542 # If hist_access_type is 'range', get a range of input cells. session can
542 # If hist_access_type is 'range', get a range of input cells. session can
543 # be a positive session number, or a negative number to count back from
543 # be a positive session number, or a negative number to count back from
544 # the current session.
544 # the current session.
545 'session' : int,
545 'session' : int,
546 # start and stop are line numbers within that session.
546 # start and stop are line numbers within that session.
547 'start' : int,
547 'start' : int,
548 'stop' : int,
548 'stop' : int,
549
549
550 # If hist_access_type is 'tail' or 'search', get the last n cells.
550 # If hist_access_type is 'tail' or 'search', get the last n cells.
551 'n' : int,
551 'n' : int,
552
552
553 # If hist_access_type is 'search', get cells matching the specified glob
553 # If hist_access_type is 'search', get cells matching the specified glob
554 # pattern (with * and ? as wildcards).
554 # pattern (with * and ? as wildcards).
555 'pattern' : str,
555 'pattern' : str,
556
556
557 # If hist_access_type is 'search' and unique is true, do not
557 # If hist_access_type is 'search' and unique is true, do not
558 # include duplicated history. Default is false.
558 # include duplicated history. Default is false.
559 'unique' : bool,
559 'unique' : bool,
560
560
561 }
561 }
562
562
563 .. versionadded:: 4.0
563 .. versionadded:: 4.0
564 The key ``unique`` for ``history_request``.
564 The key ``unique`` for ``history_request``.
565
565
566 Message type: ``history_reply``::
566 Message type: ``history_reply``::
567
567
568 content = {
568 content = {
569 # A list of 3 tuples, either:
569 # A list of 3 tuples, either:
570 # (session, line_number, input) or
570 # (session, line_number, input) or
571 # (session, line_number, (input, output)),
571 # (session, line_number, (input, output)),
572 # depending on whether output was False or True, respectively.
572 # depending on whether output was False or True, respectively.
573 'history' : list,
573 'history' : list,
574 }
574 }
575
575
576 .. _msging_is_complete:
576 .. _msging_is_complete:
577
577
578 Code completeness
578 Code completeness
579 -----------------
579 -----------------
580
580
581 .. versionadded:: 5.0
581 .. versionadded:: 5.0
582
582
583 When the user enters a line in a console style interface, the console must
583 When the user enters a line in a console style interface, the console must
584 decide whether to immediately execute the current code, or whether to show a
584 decide whether to immediately execute the current code, or whether to show a
585 continuation prompt for further input. For instance, in Python ``a = 5`` would
585 continuation prompt for further input. For instance, in Python ``a = 5`` would
586 be executed immediately, while ``for i in range(5):`` would expect further input.
586 be executed immediately, while ``for i in range(5):`` would expect further input.
587
587
588 There are four possible replies:
588 There are four possible replies:
589
589
590 - *complete* code is ready to be executed
590 - *complete* code is ready to be executed
591 - *incomplete* code should prompt for another line
591 - *incomplete* code should prompt for another line
592 - *invalid* code will typically be sent for execution, so that the user sees the
592 - *invalid* code will typically be sent for execution, so that the user sees the
593 error soonest.
593 error soonest.
594 - *unknown* - if the kernel is not able to determine this. The frontend should
594 - *unknown* - if the kernel is not able to determine this. The frontend should
595 also handle the kernel not replying promptly. It may default to sending the
595 also handle the kernel not replying promptly. It may default to sending the
596 code for execution, or it may implement simple fallback heuristics for whether
596 code for execution, or it may implement simple fallback heuristics for whether
597 to execute the code (e.g. execute after a blank line).
597 to execute the code (e.g. execute after a blank line).
598
598
599 Frontends may have ways to override this, forcing the code to be sent for
599 Frontends may have ways to override this, forcing the code to be sent for
600 execution or forcing a continuation prompt.
600 execution or forcing a continuation prompt.
601
601
602 Message type: ``is_complete_request``::
602 Message type: ``is_complete_request``::
603
603
604 content = {
604 content = {
605 # The code entered so far as a multiline string
605 # The code entered so far as a multiline string
606 'code' : str,
606 'code' : str,
607 }
607 }
608
608
609 Message type: ``is_complete_reply``::
609 Message type: ``is_complete_reply``::
610
610
611 content = {
611 content = {
612 # One of 'complete', 'incomplete', 'invalid', 'unknown'
612 # One of 'complete', 'incomplete', 'invalid', 'unknown'
613 'status' : str,
613 'status' : str,
614
614
615 # If status is 'incomplete', indent should contain the characters to use
615 # If status is 'incomplete', indent should contain the characters to use
616 # to indent the next line. This is only a hint: frontends may ignore it
616 # to indent the next line. This is only a hint: frontends may ignore it
617 # and use their own autoindentation rules. For other statuses, this
617 # and use their own autoindentation rules. For other statuses, this
618 # field does not exist.
618 # field does not exist.
619 'indent': str,
619 'indent': str,
620 }
620 }
621
621
622 Connect
622 Connect
623 -------
623 -------
624
624
625 When a client connects to the request/reply socket of the kernel, it can issue
625 When a client connects to the request/reply socket of the kernel, it can issue
626 a connect request to get basic information about the kernel, such as the ports
626 a connect request to get basic information about the kernel, such as the ports
627 the other ZeroMQ sockets are listening on. This allows clients to only have
627 the other ZeroMQ sockets are listening on. This allows clients to only have
628 to know about a single port (the shell channel) to connect to a kernel.
628 to know about a single port (the shell channel) to connect to a kernel.
629
629
630 Message type: ``connect_request``::
630 Message type: ``connect_request``::
631
631
632 content = {
632 content = {
633 }
633 }
634
634
635 Message type: ``connect_reply``::
635 Message type: ``connect_reply``::
636
636
637 content = {
637 content = {
638 'shell_port' : int, # The port the shell ROUTER socket is listening on.
638 'shell_port' : int, # The port the shell ROUTER socket is listening on.
639 'iopub_port' : int, # The port the PUB socket is listening on.
639 'iopub_port' : int, # The port the PUB socket is listening on.
640 'stdin_port' : int, # The port the stdin ROUTER socket is listening on.
640 'stdin_port' : int, # The port the stdin ROUTER socket is listening on.
641 'hb_port' : int, # The port the heartbeat socket is listening on.
641 'hb_port' : int, # The port the heartbeat socket is listening on.
642 }
642 }
643
643
644 .. _msging_kernel_info:
644 .. _msging_kernel_info:
645
645
646 Kernel info
646 Kernel info
647 -----------
647 -----------
648
648
649 If a client needs to know information about the kernel, it can
649 If a client needs to know information about the kernel, it can
650 make a request of the kernel's information.
650 make a request of the kernel's information.
651 This message can be used to fetch core information of the
651 This message can be used to fetch core information of the
652 kernel, including language (e.g., Python), language version number and
652 kernel, including language (e.g., Python), language version number and
653 IPython version number, and the IPython message spec version number.
653 IPython version number, and the IPython message spec version number.
654
654
655 Message type: ``kernel_info_request``::
655 Message type: ``kernel_info_request``::
656
656
657 content = {
657 content = {
658 }
658 }
659
659
660 Message type: ``kernel_info_reply``::
660 Message type: ``kernel_info_reply``::
661
661
662 content = {
662 content = {
663 # Version of messaging protocol.
663 # Version of messaging protocol.
664 # The first integer indicates major version. It is incremented when
664 # The first integer indicates major version. It is incremented when
665 # there is any backward incompatible change.
665 # there is any backward incompatible change.
666 # The second integer indicates minor version. It is incremented when
666 # The second integer indicates minor version. It is incremented when
667 # there is any backward compatible change.
667 # there is any backward compatible change.
668 'protocol_version': 'X.Y.Z',
668 'protocol_version': 'X.Y.Z',
669
669
670 # The kernel implementation name
670 # The kernel implementation name
671 # (e.g. 'ipython' for the IPython kernel)
671 # (e.g. 'ipython' for the IPython kernel)
672 'implementation': str,
672 'implementation': str,
673
673
674 # Implementation version number.
674 # Implementation version number.
675 # The version number of the kernel's implementation
675 # The version number of the kernel's implementation
676 # (e.g. IPython.__version__ for the IPython kernel)
676 # (e.g. IPython.__version__ for the IPython kernel)
677 'implementation_version': 'X.Y.Z',
677 'implementation_version': 'X.Y.Z',
678
678
679 # Programming language in which kernel is implemented.
679 # Programming language in which kernel is implemented.
680 # Kernel included in IPython returns 'python'.
680 # Kernel included in IPython returns 'python'.
681 'language': str,
681 'language': str,
682
682
683 # Language version number.
683 # Language version number.
684 # It is Python version number (e.g., '2.7.3') for the kernel
684 # It is Python version number (e.g., '2.7.3') for the kernel
685 # included in IPython.
685 # included in IPython.
686 'language_version': 'X.Y.Z',
686 'language_version': 'X.Y.Z',
687
687
688 # Information about the language of code for the kernel
689 'language_info': {
690 'mimetype': str,
691
692 # Pygments lexer, for highlighting
693 # Only needed if it differs from the top level 'language' field.
694 'pygments_lexer': str,
695
696 # Codemirror mode, for for highlighting in the notebook.
697 # Only needed if it differs from the top level 'language' field.
698 'codemirror_mode': str or dict,
699 },
700
688 # A banner of information about the kernel,
701 # A banner of information about the kernel,
689 # which may be desplayed in console environments.
702 # which may be desplayed in console environments.
690 'banner' : str,
703 'banner' : str,
704
705 # Optional: A list of dictionaries, each with keys 'text' and 'url'.
706 # These will be displayed in the help menu in the notebook UI.
707 'help_links': [
708 {'text': str, 'url': str}
709 ],
691 }
710 }
692
711
712 Refer to the lists of available `Pygments lexers <http://pygments.org/docs/lexers/>`_
713 and `codemirror modes <http://codemirror.net/mode/index.html>`_ for those fields.
714
693 .. versionchanged:: 5.0
715 .. versionchanged:: 5.0
694
716
695 Versions changed from lists of integers to strings.
717 Versions changed from lists of integers to strings.
696
718
697 .. versionchanged:: 5.0
719 .. versionchanged:: 5.0
698
720
699 ``ipython_version`` is removed.
721 ``ipython_version`` is removed.
700
722
701 .. versionchanged:: 5.0
723 .. versionchanged:: 5.0
702
724
703 ``implementation``, ``implementation_version``, and ``banner`` keys are added.
725 ``language_info``, ``implementation``, ``implementation_version``, ``banner``
726 and ``help_links`` keys are added.
704
727
705 .. _msging_shutdown:
728 .. _msging_shutdown:
706
729
707 Kernel shutdown
730 Kernel shutdown
708 ---------------
731 ---------------
709
732
710 The clients can request the kernel to shut itself down; this is used in
733 The clients can request the kernel to shut itself down; this is used in
711 multiple cases:
734 multiple cases:
712
735
713 - when the user chooses to close the client application via a menu or window
736 - when the user chooses to close the client application via a menu or window
714 control.
737 control.
715 - when the user types 'exit' or 'quit' (or their uppercase magic equivalents).
738 - when the user types 'exit' or 'quit' (or their uppercase magic equivalents).
716 - when the user chooses a GUI method (like the 'Ctrl-C' shortcut in the
739 - when the user chooses a GUI method (like the 'Ctrl-C' shortcut in the
717 IPythonQt client) to force a kernel restart to get a clean kernel without
740 IPythonQt client) to force a kernel restart to get a clean kernel without
718 losing client-side state like history or inlined figures.
741 losing client-side state like history or inlined figures.
719
742
720 The client sends a shutdown request to the kernel, and once it receives the
743 The client sends a shutdown request to the kernel, and once it receives the
721 reply message (which is otherwise empty), it can assume that the kernel has
744 reply message (which is otherwise empty), it can assume that the kernel has
722 completed shutdown safely.
745 completed shutdown safely.
723
746
724 Upon their own shutdown, client applications will typically execute a last
747 Upon their own shutdown, client applications will typically execute a last
725 minute sanity check and forcefully terminate any kernel that is still alive, to
748 minute sanity check and forcefully terminate any kernel that is still alive, to
726 avoid leaving stray processes in the user's machine.
749 avoid leaving stray processes in the user's machine.
727
750
728 Message type: ``shutdown_request``::
751 Message type: ``shutdown_request``::
729
752
730 content = {
753 content = {
731 'restart' : bool # whether the shutdown is final, or precedes a restart
754 'restart' : bool # whether the shutdown is final, or precedes a restart
732 }
755 }
733
756
734 Message type: ``shutdown_reply``::
757 Message type: ``shutdown_reply``::
735
758
736 content = {
759 content = {
737 'restart' : bool # whether the shutdown is final, or precedes a restart
760 'restart' : bool # whether the shutdown is final, or precedes a restart
738 }
761 }
739
762
740 .. Note::
763 .. Note::
741
764
742 When the clients detect a dead kernel thanks to inactivity on the heartbeat
765 When the clients detect a dead kernel thanks to inactivity on the heartbeat
743 socket, they simply send a forceful process termination signal, since a dead
766 socket, they simply send a forceful process termination signal, since a dead
744 process is unlikely to respond in any useful way to messages.
767 process is unlikely to respond in any useful way to messages.
745
768
746
769
747 Messages on the PUB/SUB socket
770 Messages on the PUB/SUB socket
748 ==============================
771 ==============================
749
772
750 Streams (stdout, stderr, etc)
773 Streams (stdout, stderr, etc)
751 ------------------------------
774 ------------------------------
752
775
753 Message type: ``stream``::
776 Message type: ``stream``::
754
777
755 content = {
778 content = {
756 # The name of the stream is one of 'stdout', 'stderr'
779 # The name of the stream is one of 'stdout', 'stderr'
757 'name' : str,
780 'name' : str,
758
781
759 # The text is an arbitrary string to be written to that stream
782 # The text is an arbitrary string to be written to that stream
760 'text' : str,
783 'text' : str,
761 }
784 }
762
785
763 .. versionchanged:: 5.0
786 .. versionchanged:: 5.0
764
787
765 'data' key renamed to 'text' for conistency with the notebook format.
788 'data' key renamed to 'text' for conistency with the notebook format.
766
789
767 Display Data
790 Display Data
768 ------------
791 ------------
769
792
770 This type of message is used to bring back data that should be displayed (text,
793 This type of message is used to bring back data that should be displayed (text,
771 html, svg, etc.) in the frontends. This data is published to all frontends.
794 html, svg, etc.) in the frontends. This data is published to all frontends.
772 Each message can have multiple representations of the data; it is up to the
795 Each message can have multiple representations of the data; it is up to the
773 frontend to decide which to use and how. A single message should contain all
796 frontend to decide which to use and how. A single message should contain all
774 possible representations of the same information. Each representation should
797 possible representations of the same information. Each representation should
775 be a JSON'able data structure, and should be a valid MIME type.
798 be a JSON'able data structure, and should be a valid MIME type.
776
799
777 Some questions remain about this design:
800 Some questions remain about this design:
778
801
779 * Do we use this message type for execute_result/displayhook? Probably not, because
802 * Do we use this message type for execute_result/displayhook? Probably not, because
780 the displayhook also has to handle the Out prompt display. On the other hand
803 the displayhook also has to handle the Out prompt display. On the other hand
781 we could put that information into the metadata section.
804 we could put that information into the metadata section.
782
805
783 .. _display_data:
806 .. _display_data:
784
807
785 Message type: ``display_data``::
808 Message type: ``display_data``::
786
809
787 content = {
810 content = {
788
811
789 # Who create the data
812 # Who create the data
790 'source' : str,
813 'source' : str,
791
814
792 # The data dict contains key/value pairs, where the keys are MIME
815 # The data dict contains key/value pairs, where the keys are MIME
793 # types and the values are the raw data of the representation in that
816 # types and the values are the raw data of the representation in that
794 # format.
817 # format.
795 'data' : dict,
818 'data' : dict,
796
819
797 # Any metadata that describes the data
820 # Any metadata that describes the data
798 'metadata' : dict
821 'metadata' : dict
799 }
822 }
800
823
801
824
802 The ``metadata`` contains any metadata that describes the output.
825 The ``metadata`` contains any metadata that describes the output.
803 Global keys are assumed to apply to the output as a whole.
826 Global keys are assumed to apply to the output as a whole.
804 The ``metadata`` dict can also contain mime-type keys, which will be sub-dictionaries,
827 The ``metadata`` dict can also contain mime-type keys, which will be sub-dictionaries,
805 which are interpreted as applying only to output of that type.
828 which are interpreted as applying only to output of that type.
806 Third parties should put any data they write into a single dict
829 Third parties should put any data they write into a single dict
807 with a reasonably unique name to avoid conflicts.
830 with a reasonably unique name to avoid conflicts.
808
831
809 The only metadata keys currently defined in IPython are the width and height
832 The only metadata keys currently defined in IPython are the width and height
810 of images::
833 of images::
811
834
812 metadata = {
835 metadata = {
813 'image/png' : {
836 'image/png' : {
814 'width': 640,
837 'width': 640,
815 'height': 480
838 'height': 480
816 }
839 }
817 }
840 }
818
841
819
842
820 .. versionchanged:: 5.0
843 .. versionchanged:: 5.0
821
844
822 `application/json` data should be unpacked JSON data,
845 `application/json` data should be unpacked JSON data,
823 not double-serialized as a JSON string.
846 not double-serialized as a JSON string.
824
847
825
848
826 Raw Data Publication
849 Raw Data Publication
827 --------------------
850 --------------------
828
851
829 ``display_data`` lets you publish *representations* of data, such as images and html.
852 ``display_data`` lets you publish *representations* of data, such as images and html.
830 This ``data_pub`` message lets you publish *actual raw data*, sent via message buffers.
853 This ``data_pub`` message lets you publish *actual raw data*, sent via message buffers.
831
854
832 data_pub messages are constructed via the :func:`IPython.lib.datapub.publish_data` function:
855 data_pub messages are constructed via the :func:`IPython.lib.datapub.publish_data` function:
833
856
834 .. sourcecode:: python
857 .. sourcecode:: python
835
858
836 from IPython.kernel.zmq.datapub import publish_data
859 from IPython.kernel.zmq.datapub import publish_data
837 ns = dict(x=my_array)
860 ns = dict(x=my_array)
838 publish_data(ns)
861 publish_data(ns)
839
862
840
863
841 Message type: ``data_pub``::
864 Message type: ``data_pub``::
842
865
843 content = {
866 content = {
844 # the keys of the data dict, after it has been unserialized
867 # the keys of the data dict, after it has been unserialized
845 'keys' : ['a', 'b']
868 'keys' : ['a', 'b']
846 }
869 }
847 # the namespace dict will be serialized in the message buffers,
870 # the namespace dict will be serialized in the message buffers,
848 # which will have a length of at least one
871 # which will have a length of at least one
849 buffers = [b'pdict', ...]
872 buffers = [b'pdict', ...]
850
873
851
874
852 The interpretation of a sequence of data_pub messages for a given parent request should be
875 The interpretation of a sequence of data_pub messages for a given parent request should be
853 to update a single namespace with subsequent results.
876 to update a single namespace with subsequent results.
854
877
855 .. note::
878 .. note::
856
879
857 No frontends directly handle data_pub messages at this time.
880 No frontends directly handle data_pub messages at this time.
858 It is currently only used by the client/engines in :mod:`IPython.parallel`,
881 It is currently only used by the client/engines in :mod:`IPython.parallel`,
859 where engines may publish *data* to the Client,
882 where engines may publish *data* to the Client,
860 of which the Client can then publish *representations* via ``display_data``
883 of which the Client can then publish *representations* via ``display_data``
861 to various frontends.
884 to various frontends.
862
885
863 Code inputs
886 Code inputs
864 -----------
887 -----------
865
888
866 To let all frontends know what code is being executed at any given time, these
889 To let all frontends know what code is being executed at any given time, these
867 messages contain a re-broadcast of the ``code`` portion of an
890 messages contain a re-broadcast of the ``code`` portion of an
868 :ref:`execute_request <execute>`, along with the :ref:`execution_count
891 :ref:`execute_request <execute>`, along with the :ref:`execution_count
869 <execution_counter>`.
892 <execution_counter>`.
870
893
871 Message type: ``execute_input``::
894 Message type: ``execute_input``::
872
895
873 content = {
896 content = {
874 'code' : str, # Source code to be executed, one or more lines
897 'code' : str, # Source code to be executed, one or more lines
875
898
876 # The counter for this execution is also provided so that clients can
899 # The counter for this execution is also provided so that clients can
877 # display it, since IPython automatically creates variables called _iN
900 # display it, since IPython automatically creates variables called _iN
878 # (for input prompt In[N]).
901 # (for input prompt In[N]).
879 'execution_count' : int
902 'execution_count' : int
880 }
903 }
881
904
882 .. versionchanged:: 5.0
905 .. versionchanged:: 5.0
883
906
884 ``pyin`` is renamed to ``execute_input``.
907 ``pyin`` is renamed to ``execute_input``.
885
908
886
909
887 Execution results
910 Execution results
888 -----------------
911 -----------------
889
912
890 Results of an execution are published as an ``execute_result``.
913 Results of an execution are published as an ``execute_result``.
891 These are identical to `display_data`_ messages, with the addition of an ``execution_count`` key.
914 These are identical to `display_data`_ messages, with the addition of an ``execution_count`` key.
892
915
893 Results can have multiple simultaneous formats depending on its
916 Results can have multiple simultaneous formats depending on its
894 configuration. A plain text representation should always be provided
917 configuration. A plain text representation should always be provided
895 in the ``text/plain`` mime-type. Frontends are free to display any or all of these
918 in the ``text/plain`` mime-type. Frontends are free to display any or all of these
896 according to its capabilities.
919 according to its capabilities.
897 Frontends should ignore mime-types they do not understand. The data itself is
920 Frontends should ignore mime-types they do not understand. The data itself is
898 any JSON object and depends on the format. It is often, but not always a string.
921 any JSON object and depends on the format. It is often, but not always a string.
899
922
900 Message type: ``execute_result``::
923 Message type: ``execute_result``::
901
924
902 content = {
925 content = {
903
926
904 # The counter for this execution is also provided so that clients can
927 # The counter for this execution is also provided so that clients can
905 # display it, since IPython automatically creates variables called _N
928 # display it, since IPython automatically creates variables called _N
906 # (for prompt N).
929 # (for prompt N).
907 'execution_count' : int,
930 'execution_count' : int,
908
931
909 # data and metadata are identical to a display_data message.
932 # data and metadata are identical to a display_data message.
910 # the object being displayed is that passed to the display hook,
933 # the object being displayed is that passed to the display hook,
911 # i.e. the *result* of the execution.
934 # i.e. the *result* of the execution.
912 'data' : dict,
935 'data' : dict,
913 'metadata' : dict,
936 'metadata' : dict,
914 }
937 }
915
938
916 Execution errors
939 Execution errors
917 ----------------
940 ----------------
918
941
919 When an error occurs during code execution
942 When an error occurs during code execution
920
943
921 Message type: ``error``::
944 Message type: ``error``::
922
945
923 content = {
946 content = {
924 # Similar content to the execute_reply messages for the 'error' case,
947 # Similar content to the execute_reply messages for the 'error' case,
925 # except the 'status' field is omitted.
948 # except the 'status' field is omitted.
926 }
949 }
927
950
928 .. versionchanged:: 5.0
951 .. versionchanged:: 5.0
929
952
930 ``pyerr`` renamed to ``error``
953 ``pyerr`` renamed to ``error``
931
954
932 Kernel status
955 Kernel status
933 -------------
956 -------------
934
957
935 This message type is used by frontends to monitor the status of the kernel.
958 This message type is used by frontends to monitor the status of the kernel.
936
959
937 Message type: ``status``::
960 Message type: ``status``::
938
961
939 content = {
962 content = {
940 # When the kernel starts to handle a message, it will enter the 'busy'
963 # When the kernel starts to handle a message, it will enter the 'busy'
941 # state and when it finishes, it will enter the 'idle' state.
964 # state and when it finishes, it will enter the 'idle' state.
942 # The kernel will publish state 'starting' exactly once at process startup.
965 # The kernel will publish state 'starting' exactly once at process startup.
943 execution_state : ('busy', 'idle', 'starting')
966 execution_state : ('busy', 'idle', 'starting')
944 }
967 }
945
968
946 .. versionchanged:: 5.0
969 .. versionchanged:: 5.0
947
970
948 Busy and idle messages should be sent before/after handling every message,
971 Busy and idle messages should be sent before/after handling every message,
949 not just execution.
972 not just execution.
950
973
951 Clear output
974 Clear output
952 ------------
975 ------------
953
976
954 This message type is used to clear the output that is visible on the frontend.
977 This message type is used to clear the output that is visible on the frontend.
955
978
956 Message type: ``clear_output``::
979 Message type: ``clear_output``::
957
980
958 content = {
981 content = {
959
982
960 # Wait to clear the output until new output is available. Clears the
983 # Wait to clear the output until new output is available. Clears the
961 # existing output immediately before the new output is displayed.
984 # existing output immediately before the new output is displayed.
962 # Useful for creating simple animations with minimal flickering.
985 # Useful for creating simple animations with minimal flickering.
963 'wait' : bool,
986 'wait' : bool,
964 }
987 }
965
988
966 .. versionchanged:: 4.1
989 .. versionchanged:: 4.1
967
990
968 ``stdout``, ``stderr``, and ``display`` boolean keys for selective clearing are removed,
991 ``stdout``, ``stderr``, and ``display`` boolean keys for selective clearing are removed,
969 and ``wait`` is added.
992 and ``wait`` is added.
970 The selective clearing keys are ignored in v4 and the default behavior remains the same,
993 The selective clearing keys are ignored in v4 and the default behavior remains the same,
971 so v4 clear_output messages will be safely handled by a v4.1 frontend.
994 so v4 clear_output messages will be safely handled by a v4.1 frontend.
972
995
973
996
974 Messages on the stdin ROUTER/DEALER sockets
997 Messages on the stdin ROUTER/DEALER sockets
975 ===========================================
998 ===========================================
976
999
977 This is a socket where the request/reply pattern goes in the opposite direction:
1000 This is a socket where the request/reply pattern goes in the opposite direction:
978 from the kernel to a *single* frontend, and its purpose is to allow
1001 from the kernel to a *single* frontend, and its purpose is to allow
979 ``raw_input`` and similar operations that read from ``sys.stdin`` on the kernel
1002 ``raw_input`` and similar operations that read from ``sys.stdin`` on the kernel
980 to be fulfilled by the client. The request should be made to the frontend that
1003 to be fulfilled by the client. The request should be made to the frontend that
981 made the execution request that prompted ``raw_input`` to be called. For now we
1004 made the execution request that prompted ``raw_input`` to be called. For now we
982 will keep these messages as simple as possible, since they only mean to convey
1005 will keep these messages as simple as possible, since they only mean to convey
983 the ``raw_input(prompt)`` call.
1006 the ``raw_input(prompt)`` call.
984
1007
985 Message type: ``input_request``::
1008 Message type: ``input_request``::
986
1009
987 content = {
1010 content = {
988 # the text to show at the prompt
1011 # the text to show at the prompt
989 'prompt' : str,
1012 'prompt' : str,
990 # Is the request for a password?
1013 # Is the request for a password?
991 # If so, the frontend shouldn't echo input.
1014 # If so, the frontend shouldn't echo input.
992 'password' : bool
1015 'password' : bool
993 }
1016 }
994
1017
995 Message type: ``input_reply``::
1018 Message type: ``input_reply``::
996
1019
997 content = { 'value' : str }
1020 content = { 'value' : str }
998
1021
999
1022
1000 When ``password`` is True, the frontend should not echo the input as it is entered.
1023 When ``password`` is True, the frontend should not echo the input as it is entered.
1001
1024
1002 .. versionchanged:: 5.0
1025 .. versionchanged:: 5.0
1003
1026
1004 ``password`` key added.
1027 ``password`` key added.
1005
1028
1006 .. note::
1029 .. note::
1007
1030
1008 The stdin socket of the client is required to have the same zmq IDENTITY
1031 The stdin socket of the client is required to have the same zmq IDENTITY
1009 as the client's shell socket.
1032 as the client's shell socket.
1010 Because of this, the ``input_request`` must be sent with the same IDENTITY
1033 Because of this, the ``input_request`` must be sent with the same IDENTITY
1011 routing prefix as the ``execute_reply`` in order for the frontend to receive
1034 routing prefix as the ``execute_reply`` in order for the frontend to receive
1012 the message.
1035 the message.
1013
1036
1014 .. note::
1037 .. note::
1015
1038
1016 We do not explicitly try to forward the raw ``sys.stdin`` object, because in
1039 We do not explicitly try to forward the raw ``sys.stdin`` object, because in
1017 practice the kernel should behave like an interactive program. When a
1040 practice the kernel should behave like an interactive program. When a
1018 program is opened on the console, the keyboard effectively takes over the
1041 program is opened on the console, the keyboard effectively takes over the
1019 ``stdin`` file descriptor, and it can't be used for raw reading anymore.
1042 ``stdin`` file descriptor, and it can't be used for raw reading anymore.
1020 Since the IPython kernel effectively behaves like a console program (albeit
1043 Since the IPython kernel effectively behaves like a console program (albeit
1021 one whose "keyboard" is actually living in a separate process and
1044 one whose "keyboard" is actually living in a separate process and
1022 transported over the zmq connection), raw ``stdin`` isn't expected to be
1045 transported over the zmq connection), raw ``stdin`` isn't expected to be
1023 available.
1046 available.
1024
1047
1025 .. _kernel_heartbeat:
1048 .. _kernel_heartbeat:
1026
1049
1027 Heartbeat for kernels
1050 Heartbeat for kernels
1028 =====================
1051 =====================
1029
1052
1030 Clients send ping messages on a REQ socket, which are echoed right back
1053 Clients send ping messages on a REQ socket, which are echoed right back
1031 from the Kernel's REP socket. These are simple bytestrings, not full JSON messages described above.
1054 from the Kernel's REP socket. These are simple bytestrings, not full JSON messages described above.
1032
1055
1033
1056
1034 Custom Messages
1057 Custom Messages
1035 ===============
1058 ===============
1036
1059
1037 .. versionadded:: 4.1
1060 .. versionadded:: 4.1
1038
1061
1039 IPython 2.0 (msgspec v4.1) adds a messaging system for developers to add their own objects with Frontend
1062 IPython 2.0 (msgspec v4.1) adds a messaging system for developers to add their own objects with Frontend
1040 and Kernel-side components, and allow them to communicate with each other.
1063 and Kernel-side components, and allow them to communicate with each other.
1041 To do this, IPython adds a notion of a ``Comm``, which exists on both sides,
1064 To do this, IPython adds a notion of a ``Comm``, which exists on both sides,
1042 and can communicate in either direction.
1065 and can communicate in either direction.
1043
1066
1044 These messages are fully symmetrical - both the Kernel and the Frontend can send each message,
1067 These messages are fully symmetrical - both the Kernel and the Frontend can send each message,
1045 and no messages expect a reply.
1068 and no messages expect a reply.
1046 The Kernel listens for these messages on the Shell channel,
1069 The Kernel listens for these messages on the Shell channel,
1047 and the Frontend listens for them on the IOPub channel.
1070 and the Frontend listens for them on the IOPub channel.
1048
1071
1049 Opening a Comm
1072 Opening a Comm
1050 --------------
1073 --------------
1051
1074
1052 Opening a Comm produces a ``comm_open`` message, to be sent to the other side::
1075 Opening a Comm produces a ``comm_open`` message, to be sent to the other side::
1053
1076
1054 {
1077 {
1055 'comm_id' : 'u-u-i-d',
1078 'comm_id' : 'u-u-i-d',
1056 'target_name' : 'my_comm',
1079 'target_name' : 'my_comm',
1057 'data' : {}
1080 'data' : {}
1058 }
1081 }
1059
1082
1060 Every Comm has an ID and a target name.
1083 Every Comm has an ID and a target name.
1061 The code handling the message on the receiving side is responsible for maintaining a mapping
1084 The code handling the message on the receiving side is responsible for maintaining a mapping
1062 of target_name keys to constructors.
1085 of target_name keys to constructors.
1063 After a ``comm_open`` message has been sent,
1086 After a ``comm_open`` message has been sent,
1064 there should be a corresponding Comm instance on both sides.
1087 there should be a corresponding Comm instance on both sides.
1065 The ``data`` key is always a dict and can be any extra JSON information used in initialization of the comm.
1088 The ``data`` key is always a dict and can be any extra JSON information used in initialization of the comm.
1066
1089
1067 If the ``target_name`` key is not found on the receiving side,
1090 If the ``target_name`` key is not found on the receiving side,
1068 then it should immediately reply with a ``comm_close`` message to avoid an inconsistent state.
1091 then it should immediately reply with a ``comm_close`` message to avoid an inconsistent state.
1069
1092
1070 Comm Messages
1093 Comm Messages
1071 -------------
1094 -------------
1072
1095
1073 Comm messages are one-way communications to update comm state,
1096 Comm messages are one-way communications to update comm state,
1074 used for synchronizing widget state, or simply requesting actions of a comm's counterpart.
1097 used for synchronizing widget state, or simply requesting actions of a comm's counterpart.
1075
1098
1076 Essentially, each comm pair defines their own message specification implemented inside the ``data`` dict.
1099 Essentially, each comm pair defines their own message specification implemented inside the ``data`` dict.
1077
1100
1078 There are no expected replies (of course, one side can send another ``comm_msg`` in reply).
1101 There are no expected replies (of course, one side can send another ``comm_msg`` in reply).
1079
1102
1080 Message type: ``comm_msg``::
1103 Message type: ``comm_msg``::
1081
1104
1082 {
1105 {
1083 'comm_id' : 'u-u-i-d',
1106 'comm_id' : 'u-u-i-d',
1084 'data' : {}
1107 'data' : {}
1085 }
1108 }
1086
1109
1087 Tearing Down Comms
1110 Tearing Down Comms
1088 ------------------
1111 ------------------
1089
1112
1090 Since comms live on both sides, when a comm is destroyed the other side must be notified.
1113 Since comms live on both sides, when a comm is destroyed the other side must be notified.
1091 This is done with a ``comm_close`` message.
1114 This is done with a ``comm_close`` message.
1092
1115
1093 Message type: ``comm_close``::
1116 Message type: ``comm_close``::
1094
1117
1095 {
1118 {
1096 'comm_id' : 'u-u-i-d',
1119 'comm_id' : 'u-u-i-d',
1097 'data' : {}
1120 'data' : {}
1098 }
1121 }
1099
1122
1100 Output Side Effects
1123 Output Side Effects
1101 -------------------
1124 -------------------
1102
1125
1103 Since comm messages can execute arbitrary user code,
1126 Since comm messages can execute arbitrary user code,
1104 handlers should set the parent header and publish status busy / idle,
1127 handlers should set the parent header and publish status busy / idle,
1105 just like an execute request.
1128 just like an execute request.
1106
1129
1107
1130
1108 To Do
1131 To Do
1109 =====
1132 =====
1110
1133
1111 Missing things include:
1134 Missing things include:
1112
1135
1113 * Important: finish thinking through the payload concept and API.
1136 * Important: finish thinking through the payload concept and API.
1114
1137
1115 .. include:: ../links.txt
1138 .. include:: ../links.txt
@@ -1,164 +1,174 b''
1 Making simple Python wrapper kernels
1 Making simple Python wrapper kernels
2 ====================================
2 ====================================
3
3
4 .. versionadded:: 3.0
4 .. versionadded:: 3.0
5
5
6 You can now re-use the kernel machinery in IPython to easily make new kernels.
6 You can now re-use the kernel machinery in IPython to easily make new kernels.
7 This is useful for languages that have Python bindings, such as `Octave
7 This is useful for languages that have Python bindings, such as `Octave
8 <http://www.gnu.org/software/octave/>`_ (via
8 <http://www.gnu.org/software/octave/>`_ (via
9 `Oct2Py <http://blink1073.github.io/oct2py/docs/index.html>`_), or languages
9 `Oct2Py <http://blink1073.github.io/oct2py/docs/index.html>`_), or languages
10 where the REPL can be controlled in a tty using `pexpect <http://pexpect.readthedocs.org/en/latest/>`_,
10 where the REPL can be controlled in a tty using `pexpect <http://pexpect.readthedocs.org/en/latest/>`_,
11 such as bash.
11 such as bash.
12
12
13 .. seealso::
13 .. seealso::
14
14
15 `bash_kernel <https://github.com/takluyver/bash_kernel>`_
15 `bash_kernel <https://github.com/takluyver/bash_kernel>`_
16 A simple kernel for bash, written using this machinery
16 A simple kernel for bash, written using this machinery
17
17
18 Required steps
18 Required steps
19 --------------
19 --------------
20
20
21 Subclass :class:`IPython.kernel.zmq.kernelbase.Kernel`, and implement the
21 Subclass :class:`IPython.kernel.zmq.kernelbase.Kernel`, and implement the
22 following methods and attributes:
22 following methods and attributes:
23
23
24 .. class:: MyKernel
24 .. class:: MyKernel
25
25
26 .. attribute:: implementation
26 .. attribute:: implementation
27 implementation_version
27 implementation_version
28 language
28 language
29 language_version
29 language_version
30 banner
30 banner
31
31
32 Information for :ref:`msging_kernel_info` replies. 'Implementation' refers
32 Information for :ref:`msging_kernel_info` replies. 'Implementation' refers
33 to the kernel (e.g. IPython), and 'language' refers to the language it
33 to the kernel (e.g. IPython), and 'language' refers to the language it
34 interprets (e.g. Python). The 'banner' is displayed to the user in console
34 interprets (e.g. Python). The 'banner' is displayed to the user in console
35 UIs before the first prompt. All of these values are strings.
35 UIs before the first prompt. All of these values are strings.
36
36
37 .. attribute:: language_info
38
39 Language information for :ref:`msging_kernel_info` replies, in a dictionary.
40 This should contain the key ``mimetype`` with the mimetype of code in the
41 target language (e.g. ``'text/x-python'``). It may also contain keys
42 ``codemirror_mode`` and ``pygments_lexer`` if they need to differ from
43 :attr:`language`.
44
45 Other keys may be added to this later.
46
37 .. method:: do_execute(code, silent, store_history=True, user_expressions=None, allow_stdin=False)
47 .. method:: do_execute(code, silent, store_history=True, user_expressions=None, allow_stdin=False)
38
48
39 Execute user code.
49 Execute user code.
40
50
41 :param str code: The code to be executed.
51 :param str code: The code to be executed.
42 :param bool silent: Whether to display output.
52 :param bool silent: Whether to display output.
43 :param bool store_history: Whether to record this code in history and
53 :param bool store_history: Whether to record this code in history and
44 increase the execution count. If silent is True, this is implicitly
54 increase the execution count. If silent is True, this is implicitly
45 False.
55 False.
46 :param dict user_expressions: Mapping of names to expressions to evaluate
56 :param dict user_expressions: Mapping of names to expressions to evaluate
47 after the code has run. You can ignore this if you need to.
57 after the code has run. You can ignore this if you need to.
48 :param bool allow_stdin: Whether the frontend can provide input on request
58 :param bool allow_stdin: Whether the frontend can provide input on request
49 (e.g. for Python's :func:`raw_input`).
59 (e.g. for Python's :func:`raw_input`).
50
60
51 Your method should return a dict containing the fields described in
61 Your method should return a dict containing the fields described in
52 :ref:`execution_results`. To display output, it can send messages
62 :ref:`execution_results`. To display output, it can send messages
53 using :meth:`~IPython.kernel.zmq.kernelbase.Kernel.send_response`.
63 using :meth:`~IPython.kernel.zmq.kernelbase.Kernel.send_response`.
54 See :doc:`messaging` for details of the different message types.
64 See :doc:`messaging` for details of the different message types.
55
65
56 To launch your kernel, add this at the end of your module::
66 To launch your kernel, add this at the end of your module::
57
67
58 if __name__ == '__main__':
68 if __name__ == '__main__':
59 from IPython.kernel.zmq.kernelapp import IPKernelApp
69 from IPython.kernel.zmq.kernelapp import IPKernelApp
60 IPKernelApp.launch_instance(kernel_class=MyKernel)
70 IPKernelApp.launch_instance(kernel_class=MyKernel)
61
71
62 Example
72 Example
63 -------
73 -------
64
74
65 ``echokernel.py`` will simply echo any input it's given to stdout::
75 ``echokernel.py`` will simply echo any input it's given to stdout::
66
76
67 from IPython.kernel.zmq.kernelbase import Kernel
77 from IPython.kernel.zmq.kernelbase import Kernel
68
78
69 class EchoKernel(Kernel):
79 class EchoKernel(Kernel):
70 implementation = 'Echo'
80 implementation = 'Echo'
71 implementation_version = '1.0'
81 implementation_version = '1.0'
72 language = 'no-op'
82 language = 'no-op'
73 language_version = '0.1'
83 language_version = '0.1'
84 language_info = {'mimetype': 'text/plain'}
74 banner = "Echo kernel - as useful as a parrot"
85 banner = "Echo kernel - as useful as a parrot"
75
86
76 def do_execute(self, code, silent, store_history=True, user_expressions=None,
87 def do_execute(self, code, silent, store_history=True, user_expressions=None,
77 allow_stdin=False):
88 allow_stdin=False):
78 if not silent:
89 if not silent:
79 stream_content = {'name': 'stdout', 'text': code}
90 stream_content = {'name': 'stdout', 'text': code}
80 self.send_response(self.iopub_socket, 'stream', stream_content)
91 self.send_response(self.iopub_socket, 'stream', stream_content)
81
92
82 return {'status': 'ok',
93 return {'status': 'ok',
83 # The base class increments the execution count
94 # The base class increments the execution count
84 'execution_count': self.execution_count,
95 'execution_count': self.execution_count,
85 'payload': [],
96 'payload': [],
86 'user_expressions': {},
97 'user_expressions': {},
87 }
98 }
88
99
89 if __name__ == '__main__':
100 if __name__ == '__main__':
90 from IPython.kernel.zmq.kernelapp import IPKernelApp
101 from IPython.kernel.zmq.kernelapp import IPKernelApp
91 IPKernelApp.launch_instance(kernel_class=EchoKernel)
102 IPKernelApp.launch_instance(kernel_class=EchoKernel)
92
103
93 Here's the Kernel spec ``kernel.json`` file for this::
104 Here's the Kernel spec ``kernel.json`` file for this::
94
105
95 {"argv":["python","-m","echokernel", "-f", "{connection_file}"],
106 {"argv":["python","-m","echokernel", "-f", "{connection_file}"],
96 "display_name":"Echo",
107 "display_name":"Echo",
97 "language":"no-op"
98 }
108 }
99
109
100
110
101 Optional steps
111 Optional steps
102 --------------
112 --------------
103
113
104 You can override a number of other methods to improve the functionality of your
114 You can override a number of other methods to improve the functionality of your
105 kernel. All of these methods should return a dictionary as described in the
115 kernel. All of these methods should return a dictionary as described in the
106 relevant section of the :doc:`messaging spec <messaging>`.
116 relevant section of the :doc:`messaging spec <messaging>`.
107
117
108 .. class:: MyKernel
118 .. class:: MyKernel
109
119
110 .. method:: do_complete(code, cusor_pos)
120 .. method:: do_complete(code, cusor_pos)
111
121
112 Code completion
122 Code completion
113
123
114 :param str code: The code already present
124 :param str code: The code already present
115 :param int cursor_pos: The position in the code where completion is requested
125 :param int cursor_pos: The position in the code where completion is requested
116
126
117 .. seealso::
127 .. seealso::
118
128
119 :ref:`msging_completion` messages
129 :ref:`msging_completion` messages
120
130
121 .. method:: do_inspect(code, cusor_pos, detail_level=0)
131 .. method:: do_inspect(code, cusor_pos, detail_level=0)
122
132
123 Object introspection
133 Object introspection
124
134
125 :param str code: The code
135 :param str code: The code
126 :param int cursor_pos: The position in the code where introspection is requested
136 :param int cursor_pos: The position in the code where introspection is requested
127 :param int detail_level: 0 or 1 for more or less detail. In IPython, 1 gets
137 :param int detail_level: 0 or 1 for more or less detail. In IPython, 1 gets
128 the source code.
138 the source code.
129
139
130 .. seealso::
140 .. seealso::
131
141
132 :ref:`msging_inspection` messages
142 :ref:`msging_inspection` messages
133
143
134 .. method:: do_history(hist_access_type, output, raw, session=None, start=None, stop=None, n=None, pattern=None, unique=False)
144 .. method:: do_history(hist_access_type, output, raw, session=None, start=None, stop=None, n=None, pattern=None, unique=False)
135
145
136 History access. Only the relevant parameters for the type of history
146 History access. Only the relevant parameters for the type of history
137 request concerned will be passed, so your method definition must have defaults
147 request concerned will be passed, so your method definition must have defaults
138 for all the arguments shown with defaults here.
148 for all the arguments shown with defaults here.
139
149
140 .. seealso::
150 .. seealso::
141
151
142 :ref:`msging_history` messages
152 :ref:`msging_history` messages
143
153
144 .. method:: do_is_complete(code)
154 .. method:: do_is_complete(code)
145
155
146 Is code entered in a console-like interface complete and ready to execute,
156 Is code entered in a console-like interface complete and ready to execute,
147 or should a continuation prompt be shown?
157 or should a continuation prompt be shown?
148
158
149 :param str code: The code entered so far - possibly multiple lines
159 :param str code: The code entered so far - possibly multiple lines
150
160
151 .. seealso::
161 .. seealso::
152
162
153 :ref:`msging_is_complete` messages
163 :ref:`msging_is_complete` messages
154
164
155 .. method:: do_shutdown(restart)
165 .. method:: do_shutdown(restart)
156
166
157 Shutdown the kernel. You only need to handle your own clean up - the kernel
167 Shutdown the kernel. You only need to handle your own clean up - the kernel
158 machinery will take care of cleaning up its own things before stopping.
168 machinery will take care of cleaning up its own things before stopping.
159
169
160 :param bool restart: Whether the kernel will be started again afterwards
170 :param bool restart: Whether the kernel will be started again afterwards
161
171
162 .. seealso::
172 .. seealso::
163
173
164 :ref:`msging_shutdown` messages
174 :ref:`msging_shutdown` messages
General Comments 0
You need to be logged in to leave comments. Login now