##// END OF EJS Templates
Update JS for kernels and sessions APIs
Thomas Kluyver -
Show More
@@ -1,2527 +1,2532
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/js/session',
11 'services/sessions/js/session',
12 'notebook/js/celltoolbar',
12 'notebook/js/celltoolbar',
13 'components/marked/lib/marked',
13 'components/marked/lib/marked',
14 'notebook/js/mathjaxutils',
14 'notebook/js/mathjaxutils',
15 'base/js/keyboard',
15 'base/js/keyboard',
16 'notebook/js/tooltip',
16 'notebook/js/tooltip',
17 'notebook/js/celltoolbarpresets/default',
17 'notebook/js/celltoolbarpresets/default',
18 'notebook/js/celltoolbarpresets/rawcell',
18 'notebook/js/celltoolbarpresets/rawcell',
19 'notebook/js/celltoolbarpresets/slideshow',
19 'notebook/js/celltoolbarpresets/slideshow',
20 ], function (
20 ], function (
21 IPython,
21 IPython,
22 $,
22 $,
23 utils,
23 utils,
24 dialog,
24 dialog,
25 textcell,
25 textcell,
26 codecell,
26 codecell,
27 session,
27 session,
28 celltoolbar,
28 celltoolbar,
29 marked,
29 marked,
30 mathjaxutils,
30 mathjaxutils,
31 keyboard,
31 keyboard,
32 tooltip,
32 tooltip,
33 default_celltoolbar,
33 default_celltoolbar,
34 rawcell_celltoolbar,
34 rawcell_celltoolbar,
35 slideshow_celltoolbar
35 slideshow_celltoolbar
36 ) {
36 ) {
37
37
38 var Notebook = function (selector, options) {
38 var Notebook = function (selector, options) {
39 // Constructor
39 // Constructor
40 //
40 //
41 // A notebook contains and manages cells.
41 // A notebook contains and manages cells.
42 //
42 //
43 // Parameters:
43 // Parameters:
44 // selector: string
44 // selector: string
45 // options: dictionary
45 // options: dictionary
46 // Dictionary of keyword arguments.
46 // Dictionary of keyword arguments.
47 // events: $(Events) instance
47 // events: $(Events) instance
48 // keyboard_manager: KeyboardManager instance
48 // keyboard_manager: KeyboardManager instance
49 // save_widget: SaveWidget instance
49 // save_widget: SaveWidget instance
50 // config: dictionary
50 // config: dictionary
51 // base_url : string
51 // base_url : string
52 // notebook_path : string
52 // notebook_path : string
53 // notebook_name : string
53 // notebook_name : string
54 this.config = options.config || {};
54 this.config = options.config || {};
55 this.base_url = options.base_url;
55 this.base_url = options.base_url;
56 this.notebook_path = options.notebook_path;
56 this.notebook_path = options.notebook_path;
57 this.notebook_name = options.notebook_name;
57 this.notebook_name = options.notebook_name;
58 this.events = options.events;
58 this.events = options.events;
59 this.keyboard_manager = options.keyboard_manager;
59 this.keyboard_manager = options.keyboard_manager;
60 this.save_widget = options.save_widget;
60 this.save_widget = options.save_widget;
61 this.tooltip = new tooltip.Tooltip(this.events);
61 this.tooltip = new tooltip.Tooltip(this.events);
62 // TODO: This code smells (and the other `= this` line a couple lines down)
62 // TODO: This code smells (and the other `= this` line a couple lines down)
63 // We need a better way to deal with circular instance references.
63 // We need a better way to deal with circular instance references.
64 this.keyboard_manager.notebook = this;
64 this.keyboard_manager.notebook = this;
65 this.save_widget.notebook = this;
65 this.save_widget.notebook = this;
66
66
67 mathjaxutils.init();
67 mathjaxutils.init();
68
68
69 if (marked) {
69 if (marked) {
70 marked.setOptions({
70 marked.setOptions({
71 gfm : true,
71 gfm : true,
72 tables: true,
72 tables: true,
73 langPrefix: "language-",
73 langPrefix: "language-",
74 highlight: function(code, lang) {
74 highlight: function(code, lang) {
75 if (!lang) {
75 if (!lang) {
76 // no language, no highlight
76 // no language, no highlight
77 return code;
77 return code;
78 }
78 }
79 var highlighted;
79 var highlighted;
80 try {
80 try {
81 highlighted = hljs.highlight(lang, code, false);
81 highlighted = hljs.highlight(lang, code, false);
82 } catch(err) {
82 } catch(err) {
83 highlighted = hljs.highlightAuto(code);
83 highlighted = hljs.highlightAuto(code);
84 }
84 }
85 return highlighted.value;
85 return highlighted.value;
86 }
86 }
87 });
87 });
88 }
88 }
89
89
90 this.element = $(selector);
90 this.element = $(selector);
91 this.element.scroll();
91 this.element.scroll();
92 this.element.data("notebook", this);
92 this.element.data("notebook", this);
93 this.next_prompt_number = 1;
93 this.next_prompt_number = 1;
94 this.session = null;
94 this.session = null;
95 this.kernel = null;
95 this.kernel = null;
96 this.clipboard = null;
96 this.clipboard = null;
97 this.undelete_backup = null;
97 this.undelete_backup = null;
98 this.undelete_index = null;
98 this.undelete_index = null;
99 this.undelete_below = false;
99 this.undelete_below = false;
100 this.paste_enabled = false;
100 this.paste_enabled = false;
101 // It is important to start out in command mode to match the intial mode
101 // It is important to start out in command mode to match the intial mode
102 // of the KeyboardManager.
102 // of the KeyboardManager.
103 this.mode = 'command';
103 this.mode = 'command';
104 this.set_dirty(false);
104 this.set_dirty(false);
105 this.metadata = {};
105 this.metadata = {};
106 this._checkpoint_after_save = false;
106 this._checkpoint_after_save = false;
107 this.last_checkpoint = null;
107 this.last_checkpoint = null;
108 this.checkpoints = [];
108 this.checkpoints = [];
109 this.autosave_interval = 0;
109 this.autosave_interval = 0;
110 this.autosave_timer = null;
110 this.autosave_timer = null;
111 // autosave *at most* every two minutes
111 // autosave *at most* every two minutes
112 this.minimum_autosave_interval = 120000;
112 this.minimum_autosave_interval = 120000;
113 // single worksheet for now
113 // single worksheet for now
114 this.worksheet_metadata = {};
114 this.worksheet_metadata = {};
115 this.notebook_name_blacklist_re = /[\/\\:]/;
115 this.notebook_name_blacklist_re = /[\/\\:]/;
116 this.nbformat = 3; // Increment this when changing the nbformat
116 this.nbformat = 3; // Increment this when changing the nbformat
117 this.nbformat_minor = 0; // Increment this when changing the nbformat
117 this.nbformat_minor = 0; // Increment this when changing the nbformat
118 this.style();
118 this.style();
119 this.create_elements();
119 this.create_elements();
120 this.bind_events();
120 this.bind_events();
121 this.save_notebook = function() { // don't allow save until notebook_loaded
121 this.save_notebook = function() { // don't allow save until notebook_loaded
122 this.save_notebook_error(null, null, "Load failed, save is disabled");
122 this.save_notebook_error(null, null, "Load failed, save is disabled");
123 };
123 };
124
124
125 // Trigger cell toolbar registration.
125 // Trigger cell toolbar registration.
126 default_celltoolbar.register(this, options.events);
126 default_celltoolbar.register(this, options.events);
127 rawcell_celltoolbar.register(this, options.events);
127 rawcell_celltoolbar.register(this, options.events);
128 slideshow_celltoolbar.register(this, options.events);
128 slideshow_celltoolbar.register(this, options.events);
129 };
129 };
130
130
131 /**
131 /**
132 * Tweak the notebook's CSS style.
132 * Tweak the notebook's CSS style.
133 *
133 *
134 * @method style
134 * @method style
135 */
135 */
136 Notebook.prototype.style = function () {
136 Notebook.prototype.style = function () {
137 $('div#notebook').addClass('border-box-sizing');
137 $('div#notebook').addClass('border-box-sizing');
138 };
138 };
139
139
140 /**
140 /**
141 * Create an HTML and CSS representation of the notebook.
141 * Create an HTML and CSS representation of the notebook.
142 *
142 *
143 * @method create_elements
143 * @method create_elements
144 */
144 */
145 Notebook.prototype.create_elements = function () {
145 Notebook.prototype.create_elements = function () {
146 var that = this;
146 var that = this;
147 this.element.attr('tabindex','-1');
147 this.element.attr('tabindex','-1');
148 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
148 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
149 // We add this end_space div to the end of the notebook div to:
149 // We add this end_space div to the end of the notebook div to:
150 // i) provide a margin between the last cell and the end of the notebook
150 // i) provide a margin between the last cell and the end of the notebook
151 // ii) to prevent the div from scrolling up when the last cell is being
151 // ii) to prevent the div from scrolling up when the last cell is being
152 // edited, but is too low on the page, which browsers will do automatically.
152 // edited, but is too low on the page, which browsers will do automatically.
153 var end_space = $('<div/>').addClass('end_space');
153 var end_space = $('<div/>').addClass('end_space');
154 end_space.dblclick(function (e) {
154 end_space.dblclick(function (e) {
155 var ncells = that.ncells();
155 var ncells = that.ncells();
156 that.insert_cell_below('code',ncells-1);
156 that.insert_cell_below('code',ncells-1);
157 });
157 });
158 this.element.append(this.container);
158 this.element.append(this.container);
159 this.container.append(end_space);
159 this.container.append(end_space);
160 };
160 };
161
161
162 /**
162 /**
163 * Bind JavaScript events: key presses and custom IPython events.
163 * Bind JavaScript events: key presses and custom IPython events.
164 *
164 *
165 * @method bind_events
165 * @method bind_events
166 */
166 */
167 Notebook.prototype.bind_events = function () {
167 Notebook.prototype.bind_events = function () {
168 var that = this;
168 var that = this;
169
169
170 this.events.on('set_next_input.Notebook', function (event, data) {
170 this.events.on('set_next_input.Notebook', function (event, data) {
171 var index = that.find_cell_index(data.cell);
171 var index = that.find_cell_index(data.cell);
172 var new_cell = that.insert_cell_below('code',index);
172 var new_cell = that.insert_cell_below('code',index);
173 new_cell.set_text(data.text);
173 new_cell.set_text(data.text);
174 that.dirty = true;
174 that.dirty = true;
175 });
175 });
176
176
177 this.events.on('set_dirty.Notebook', function (event, data) {
177 this.events.on('set_dirty.Notebook', function (event, data) {
178 that.dirty = data.value;
178 that.dirty = data.value;
179 });
179 });
180
180
181 this.events.on('trust_changed.Notebook', function (event, data) {
181 this.events.on('trust_changed.Notebook', function (event, data) {
182 that.trusted = data.value;
182 that.trusted = data.value;
183 });
183 });
184
184
185 this.events.on('select.Cell', function (event, data) {
185 this.events.on('select.Cell', function (event, data) {
186 var index = that.find_cell_index(data.cell);
186 var index = that.find_cell_index(data.cell);
187 that.select(index);
187 that.select(index);
188 });
188 });
189
189
190 this.events.on('edit_mode.Cell', function (event, data) {
190 this.events.on('edit_mode.Cell', function (event, data) {
191 that.handle_edit_mode(data.cell);
191 that.handle_edit_mode(data.cell);
192 });
192 });
193
193
194 this.events.on('command_mode.Cell', function (event, data) {
194 this.events.on('command_mode.Cell', function (event, data) {
195 that.handle_command_mode(data.cell);
195 that.handle_command_mode(data.cell);
196 });
196 });
197
197
198 this.events.on('status_autorestarting.Kernel', function () {
198 this.events.on('status_autorestarting.Kernel', function () {
199 dialog.modal({
199 dialog.modal({
200 notebook: that,
200 notebook: that,
201 keyboard_manager: that.keyboard_manager,
201 keyboard_manager: that.keyboard_manager,
202 title: "Kernel Restarting",
202 title: "Kernel Restarting",
203 body: "The kernel appears to have died. It will restart automatically.",
203 body: "The kernel appears to have died. It will restart automatically.",
204 buttons: {
204 buttons: {
205 OK : {
205 OK : {
206 class : "btn-primary"
206 class : "btn-primary"
207 }
207 }
208 }
208 }
209 });
209 });
210 });
210 });
211
211
212 var collapse_time = function (time) {
212 var collapse_time = function (time) {
213 var app_height = $('#ipython-main-app').height(); // content height
213 var app_height = $('#ipython-main-app').height(); // content height
214 var splitter_height = $('div#pager_splitter').outerHeight(true);
214 var splitter_height = $('div#pager_splitter').outerHeight(true);
215 var new_height = app_height - splitter_height;
215 var new_height = app_height - splitter_height;
216 that.element.animate({height : new_height + 'px'}, time);
216 that.element.animate({height : new_height + 'px'}, time);
217 };
217 };
218
218
219 this.element.bind('collapse_pager', function (event, extrap) {
219 this.element.bind('collapse_pager', function (event, extrap) {
220 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
220 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
221 collapse_time(time);
221 collapse_time(time);
222 });
222 });
223
223
224 var expand_time = function (time) {
224 var expand_time = function (time) {
225 var app_height = $('#ipython-main-app').height(); // content height
225 var app_height = $('#ipython-main-app').height(); // content height
226 var splitter_height = $('div#pager_splitter').outerHeight(true);
226 var splitter_height = $('div#pager_splitter').outerHeight(true);
227 var pager_height = $('div#pager').outerHeight(true);
227 var pager_height = $('div#pager').outerHeight(true);
228 var new_height = app_height - pager_height - splitter_height;
228 var new_height = app_height - pager_height - splitter_height;
229 that.element.animate({height : new_height + 'px'}, time);
229 that.element.animate({height : new_height + 'px'}, time);
230 };
230 };
231
231
232 this.element.bind('expand_pager', function (event, extrap) {
232 this.element.bind('expand_pager', function (event, extrap) {
233 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
233 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
234 expand_time(time);
234 expand_time(time);
235 });
235 });
236
236
237 // Firefox 22 broke $(window).on("beforeunload")
237 // Firefox 22 broke $(window).on("beforeunload")
238 // I'm not sure why or how.
238 // I'm not sure why or how.
239 window.onbeforeunload = function (e) {
239 window.onbeforeunload = function (e) {
240 // TODO: Make killing the kernel configurable.
240 // TODO: Make killing the kernel configurable.
241 var kill_kernel = false;
241 var kill_kernel = false;
242 if (kill_kernel) {
242 if (kill_kernel) {
243 that.session.kill_kernel();
243 that.session.kill_kernel();
244 }
244 }
245 // if we are autosaving, trigger an autosave on nav-away.
245 // if we are autosaving, trigger an autosave on nav-away.
246 // still warn, because if we don't the autosave may fail.
246 // still warn, because if we don't the autosave may fail.
247 if (that.dirty) {
247 if (that.dirty) {
248 if ( that.autosave_interval ) {
248 if ( that.autosave_interval ) {
249 // schedule autosave in a timeout
249 // schedule autosave in a timeout
250 // this gives you a chance to forcefully discard changes
250 // this gives you a chance to forcefully discard changes
251 // by reloading the page if you *really* want to.
251 // by reloading the page if you *really* want to.
252 // the timer doesn't start until you *dismiss* the dialog.
252 // the timer doesn't start until you *dismiss* the dialog.
253 setTimeout(function () {
253 setTimeout(function () {
254 if (that.dirty) {
254 if (that.dirty) {
255 that.save_notebook();
255 that.save_notebook();
256 }
256 }
257 }, 1000);
257 }, 1000);
258 return "Autosave in progress, latest changes may be lost.";
258 return "Autosave in progress, latest changes may be lost.";
259 } else {
259 } else {
260 return "Unsaved changes will be lost.";
260 return "Unsaved changes will be lost.";
261 }
261 }
262 }
262 }
263 // Null is the *only* return value that will make the browser not
263 // Null is the *only* return value that will make the browser not
264 // pop up the "don't leave" dialog.
264 // pop up the "don't leave" dialog.
265 return null;
265 return null;
266 };
266 };
267 };
267 };
268
268
269 /**
269 /**
270 * Set the dirty flag, and trigger the set_dirty.Notebook event
270 * Set the dirty flag, and trigger the set_dirty.Notebook event
271 *
271 *
272 * @method set_dirty
272 * @method set_dirty
273 */
273 */
274 Notebook.prototype.set_dirty = function (value) {
274 Notebook.prototype.set_dirty = function (value) {
275 if (value === undefined) {
275 if (value === undefined) {
276 value = true;
276 value = true;
277 }
277 }
278 if (this.dirty == value) {
278 if (this.dirty == value) {
279 return;
279 return;
280 }
280 }
281 this.events.trigger('set_dirty.Notebook', {value: value});
281 this.events.trigger('set_dirty.Notebook', {value: value});
282 };
282 };
283
283
284 /**
284 /**
285 * Scroll the top of the page to a given cell.
285 * Scroll the top of the page to a given cell.
286 *
286 *
287 * @method scroll_to_cell
287 * @method scroll_to_cell
288 * @param {Number} cell_number An index of the cell to view
288 * @param {Number} cell_number An index of the cell to view
289 * @param {Number} time Animation time in milliseconds
289 * @param {Number} time Animation time in milliseconds
290 * @return {Number} Pixel offset from the top of the container
290 * @return {Number} Pixel offset from the top of the container
291 */
291 */
292 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
292 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
293 var cells = this.get_cells();
293 var cells = this.get_cells();
294 time = time || 0;
294 time = time || 0;
295 cell_number = Math.min(cells.length-1,cell_number);
295 cell_number = Math.min(cells.length-1,cell_number);
296 cell_number = Math.max(0 ,cell_number);
296 cell_number = Math.max(0 ,cell_number);
297 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
297 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
298 this.element.animate({scrollTop:scroll_value}, time);
298 this.element.animate({scrollTop:scroll_value}, time);
299 return scroll_value;
299 return scroll_value;
300 };
300 };
301
301
302 /**
302 /**
303 * Scroll to the bottom of the page.
303 * Scroll to the bottom of the page.
304 *
304 *
305 * @method scroll_to_bottom
305 * @method scroll_to_bottom
306 */
306 */
307 Notebook.prototype.scroll_to_bottom = function () {
307 Notebook.prototype.scroll_to_bottom = function () {
308 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
308 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
309 };
309 };
310
310
311 /**
311 /**
312 * Scroll to the top of the page.
312 * Scroll to the top of the page.
313 *
313 *
314 * @method scroll_to_top
314 * @method scroll_to_top
315 */
315 */
316 Notebook.prototype.scroll_to_top = function () {
316 Notebook.prototype.scroll_to_top = function () {
317 this.element.animate({scrollTop:0}, 0);
317 this.element.animate({scrollTop:0}, 0);
318 };
318 };
319
319
320 // Edit Notebook metadata
320 // Edit Notebook metadata
321
321
322 Notebook.prototype.edit_metadata = function () {
322 Notebook.prototype.edit_metadata = function () {
323 var that = this;
323 var that = this;
324 dialog.edit_metadata({
324 dialog.edit_metadata({
325 md: this.metadata,
325 md: this.metadata,
326 callback: function (md) {
326 callback: function (md) {
327 that.metadata = md;
327 that.metadata = md;
328 },
328 },
329 name: 'Notebook',
329 name: 'Notebook',
330 notebook: this,
330 notebook: this,
331 keyboard_manager: this.keyboard_manager});
331 keyboard_manager: this.keyboard_manager});
332 };
332 };
333
333
334 // Cell indexing, retrieval, etc.
334 // Cell indexing, retrieval, etc.
335
335
336 /**
336 /**
337 * Get all cell elements in the notebook.
337 * Get all cell elements in the notebook.
338 *
338 *
339 * @method get_cell_elements
339 * @method get_cell_elements
340 * @return {jQuery} A selector of all cell elements
340 * @return {jQuery} A selector of all cell elements
341 */
341 */
342 Notebook.prototype.get_cell_elements = function () {
342 Notebook.prototype.get_cell_elements = function () {
343 return this.container.children("div.cell");
343 return this.container.children("div.cell");
344 };
344 };
345
345
346 /**
346 /**
347 * Get a particular cell element.
347 * Get a particular cell element.
348 *
348 *
349 * @method get_cell_element
349 * @method get_cell_element
350 * @param {Number} index An index of a cell to select
350 * @param {Number} index An index of a cell to select
351 * @return {jQuery} A selector of the given cell.
351 * @return {jQuery} A selector of the given cell.
352 */
352 */
353 Notebook.prototype.get_cell_element = function (index) {
353 Notebook.prototype.get_cell_element = function (index) {
354 var result = null;
354 var result = null;
355 var e = this.get_cell_elements().eq(index);
355 var e = this.get_cell_elements().eq(index);
356 if (e.length !== 0) {
356 if (e.length !== 0) {
357 result = e;
357 result = e;
358 }
358 }
359 return result;
359 return result;
360 };
360 };
361
361
362 /**
362 /**
363 * Try to get a particular cell by msg_id.
363 * Try to get a particular cell by msg_id.
364 *
364 *
365 * @method get_msg_cell
365 * @method get_msg_cell
366 * @param {String} msg_id A message UUID
366 * @param {String} msg_id A message UUID
367 * @return {Cell} Cell or null if no cell was found.
367 * @return {Cell} Cell or null if no cell was found.
368 */
368 */
369 Notebook.prototype.get_msg_cell = function (msg_id) {
369 Notebook.prototype.get_msg_cell = function (msg_id) {
370 return codecell.CodeCell.msg_cells[msg_id] || null;
370 return codecell.CodeCell.msg_cells[msg_id] || null;
371 };
371 };
372
372
373 /**
373 /**
374 * Count the cells in this notebook.
374 * Count the cells in this notebook.
375 *
375 *
376 * @method ncells
376 * @method ncells
377 * @return {Number} The number of cells in this notebook
377 * @return {Number} The number of cells in this notebook
378 */
378 */
379 Notebook.prototype.ncells = function () {
379 Notebook.prototype.ncells = function () {
380 return this.get_cell_elements().length;
380 return this.get_cell_elements().length;
381 };
381 };
382
382
383 /**
383 /**
384 * Get all Cell objects in this notebook.
384 * Get all Cell objects in this notebook.
385 *
385 *
386 * @method get_cells
386 * @method get_cells
387 * @return {Array} This notebook's Cell objects
387 * @return {Array} This notebook's Cell objects
388 */
388 */
389 // TODO: we are often calling cells as cells()[i], which we should optimize
389 // TODO: we are often calling cells as cells()[i], which we should optimize
390 // to cells(i) or a new method.
390 // to cells(i) or a new method.
391 Notebook.prototype.get_cells = function () {
391 Notebook.prototype.get_cells = function () {
392 return this.get_cell_elements().toArray().map(function (e) {
392 return this.get_cell_elements().toArray().map(function (e) {
393 return $(e).data("cell");
393 return $(e).data("cell");
394 });
394 });
395 };
395 };
396
396
397 /**
397 /**
398 * Get a Cell object from this notebook.
398 * Get a Cell object from this notebook.
399 *
399 *
400 * @method get_cell
400 * @method get_cell
401 * @param {Number} index An index of a cell to retrieve
401 * @param {Number} index An index of a cell to retrieve
402 * @return {Cell} A particular cell
402 * @return {Cell} A particular cell
403 */
403 */
404 Notebook.prototype.get_cell = function (index) {
404 Notebook.prototype.get_cell = function (index) {
405 var result = null;
405 var result = null;
406 var ce = this.get_cell_element(index);
406 var ce = this.get_cell_element(index);
407 if (ce !== null) {
407 if (ce !== null) {
408 result = ce.data('cell');
408 result = ce.data('cell');
409 }
409 }
410 return result;
410 return result;
411 };
411 };
412
412
413 /**
413 /**
414 * Get the cell below a given cell.
414 * Get the cell below a given cell.
415 *
415 *
416 * @method get_next_cell
416 * @method get_next_cell
417 * @param {Cell} cell The provided cell
417 * @param {Cell} cell The provided cell
418 * @return {Cell} The next cell
418 * @return {Cell} The next cell
419 */
419 */
420 Notebook.prototype.get_next_cell = function (cell) {
420 Notebook.prototype.get_next_cell = function (cell) {
421 var result = null;
421 var result = null;
422 var index = this.find_cell_index(cell);
422 var index = this.find_cell_index(cell);
423 if (this.is_valid_cell_index(index+1)) {
423 if (this.is_valid_cell_index(index+1)) {
424 result = this.get_cell(index+1);
424 result = this.get_cell(index+1);
425 }
425 }
426 return result;
426 return result;
427 };
427 };
428
428
429 /**
429 /**
430 * Get the cell above a given cell.
430 * Get the cell above a given cell.
431 *
431 *
432 * @method get_prev_cell
432 * @method get_prev_cell
433 * @param {Cell} cell The provided cell
433 * @param {Cell} cell The provided cell
434 * @return {Cell} The previous cell
434 * @return {Cell} The previous cell
435 */
435 */
436 Notebook.prototype.get_prev_cell = function (cell) {
436 Notebook.prototype.get_prev_cell = function (cell) {
437 // TODO: off-by-one
437 // TODO: off-by-one
438 // nb.get_prev_cell(nb.get_cell(1)) is null
438 // nb.get_prev_cell(nb.get_cell(1)) is null
439 var result = null;
439 var result = null;
440 var index = this.find_cell_index(cell);
440 var index = this.find_cell_index(cell);
441 if (index !== null && index > 1) {
441 if (index !== null && index > 1) {
442 result = this.get_cell(index-1);
442 result = this.get_cell(index-1);
443 }
443 }
444 return result;
444 return result;
445 };
445 };
446
446
447 /**
447 /**
448 * Get the numeric index of a given cell.
448 * Get the numeric index of a given cell.
449 *
449 *
450 * @method find_cell_index
450 * @method find_cell_index
451 * @param {Cell} cell The provided cell
451 * @param {Cell} cell The provided cell
452 * @return {Number} The cell's numeric index
452 * @return {Number} The cell's numeric index
453 */
453 */
454 Notebook.prototype.find_cell_index = function (cell) {
454 Notebook.prototype.find_cell_index = function (cell) {
455 var result = null;
455 var result = null;
456 this.get_cell_elements().filter(function (index) {
456 this.get_cell_elements().filter(function (index) {
457 if ($(this).data("cell") === cell) {
457 if ($(this).data("cell") === cell) {
458 result = index;
458 result = index;
459 }
459 }
460 });
460 });
461 return result;
461 return result;
462 };
462 };
463
463
464 /**
464 /**
465 * Get a given index , or the selected index if none is provided.
465 * Get a given index , or the selected index if none is provided.
466 *
466 *
467 * @method index_or_selected
467 * @method index_or_selected
468 * @param {Number} index A cell's index
468 * @param {Number} index A cell's index
469 * @return {Number} The given index, or selected index if none is provided.
469 * @return {Number} The given index, or selected index if none is provided.
470 */
470 */
471 Notebook.prototype.index_or_selected = function (index) {
471 Notebook.prototype.index_or_selected = function (index) {
472 var i;
472 var i;
473 if (index === undefined || index === null) {
473 if (index === undefined || index === null) {
474 i = this.get_selected_index();
474 i = this.get_selected_index();
475 if (i === null) {
475 if (i === null) {
476 i = 0;
476 i = 0;
477 }
477 }
478 } else {
478 } else {
479 i = index;
479 i = index;
480 }
480 }
481 return i;
481 return i;
482 };
482 };
483
483
484 /**
484 /**
485 * Get the currently selected cell.
485 * Get the currently selected cell.
486 * @method get_selected_cell
486 * @method get_selected_cell
487 * @return {Cell} The selected cell
487 * @return {Cell} The selected cell
488 */
488 */
489 Notebook.prototype.get_selected_cell = function () {
489 Notebook.prototype.get_selected_cell = function () {
490 var index = this.get_selected_index();
490 var index = this.get_selected_index();
491 return this.get_cell(index);
491 return this.get_cell(index);
492 };
492 };
493
493
494 /**
494 /**
495 * Check whether a cell index is valid.
495 * Check whether a cell index is valid.
496 *
496 *
497 * @method is_valid_cell_index
497 * @method is_valid_cell_index
498 * @param {Number} index A cell index
498 * @param {Number} index A cell index
499 * @return True if the index is valid, false otherwise
499 * @return True if the index is valid, false otherwise
500 */
500 */
501 Notebook.prototype.is_valid_cell_index = function (index) {
501 Notebook.prototype.is_valid_cell_index = function (index) {
502 if (index !== null && index >= 0 && index < this.ncells()) {
502 if (index !== null && index >= 0 && index < this.ncells()) {
503 return true;
503 return true;
504 } else {
504 } else {
505 return false;
505 return false;
506 }
506 }
507 };
507 };
508
508
509 /**
509 /**
510 * Get the index of the currently selected cell.
510 * Get the index of the currently selected cell.
511
511
512 * @method get_selected_index
512 * @method get_selected_index
513 * @return {Number} The selected cell's numeric index
513 * @return {Number} The selected cell's numeric index
514 */
514 */
515 Notebook.prototype.get_selected_index = function () {
515 Notebook.prototype.get_selected_index = function () {
516 var result = null;
516 var result = null;
517 this.get_cell_elements().filter(function (index) {
517 this.get_cell_elements().filter(function (index) {
518 if ($(this).data("cell").selected === true) {
518 if ($(this).data("cell").selected === true) {
519 result = index;
519 result = index;
520 }
520 }
521 });
521 });
522 return result;
522 return result;
523 };
523 };
524
524
525
525
526 // Cell selection.
526 // Cell selection.
527
527
528 /**
528 /**
529 * Programmatically select a cell.
529 * Programmatically select a cell.
530 *
530 *
531 * @method select
531 * @method select
532 * @param {Number} index A cell's index
532 * @param {Number} index A cell's index
533 * @return {Notebook} This notebook
533 * @return {Notebook} This notebook
534 */
534 */
535 Notebook.prototype.select = function (index) {
535 Notebook.prototype.select = function (index) {
536 if (this.is_valid_cell_index(index)) {
536 if (this.is_valid_cell_index(index)) {
537 var sindex = this.get_selected_index();
537 var sindex = this.get_selected_index();
538 if (sindex !== null && index !== sindex) {
538 if (sindex !== null && index !== sindex) {
539 // If we are about to select a different cell, make sure we are
539 // If we are about to select a different cell, make sure we are
540 // first in command mode.
540 // first in command mode.
541 if (this.mode !== 'command') {
541 if (this.mode !== 'command') {
542 this.command_mode();
542 this.command_mode();
543 }
543 }
544 this.get_cell(sindex).unselect();
544 this.get_cell(sindex).unselect();
545 }
545 }
546 var cell = this.get_cell(index);
546 var cell = this.get_cell(index);
547 cell.select();
547 cell.select();
548 if (cell.cell_type === 'heading') {
548 if (cell.cell_type === 'heading') {
549 this.events.trigger('selected_cell_type_changed.Notebook',
549 this.events.trigger('selected_cell_type_changed.Notebook',
550 {'cell_type':cell.cell_type,level:cell.level}
550 {'cell_type':cell.cell_type,level:cell.level}
551 );
551 );
552 } else {
552 } else {
553 this.events.trigger('selected_cell_type_changed.Notebook',
553 this.events.trigger('selected_cell_type_changed.Notebook',
554 {'cell_type':cell.cell_type}
554 {'cell_type':cell.cell_type}
555 );
555 );
556 }
556 }
557 }
557 }
558 return this;
558 return this;
559 };
559 };
560
560
561 /**
561 /**
562 * Programmatically select the next cell.
562 * Programmatically select the next cell.
563 *
563 *
564 * @method select_next
564 * @method select_next
565 * @return {Notebook} This notebook
565 * @return {Notebook} This notebook
566 */
566 */
567 Notebook.prototype.select_next = function () {
567 Notebook.prototype.select_next = function () {
568 var index = this.get_selected_index();
568 var index = this.get_selected_index();
569 this.select(index+1);
569 this.select(index+1);
570 return this;
570 return this;
571 };
571 };
572
572
573 /**
573 /**
574 * Programmatically select the previous cell.
574 * Programmatically select the previous cell.
575 *
575 *
576 * @method select_prev
576 * @method select_prev
577 * @return {Notebook} This notebook
577 * @return {Notebook} This notebook
578 */
578 */
579 Notebook.prototype.select_prev = function () {
579 Notebook.prototype.select_prev = function () {
580 var index = this.get_selected_index();
580 var index = this.get_selected_index();
581 this.select(index-1);
581 this.select(index-1);
582 return this;
582 return this;
583 };
583 };
584
584
585
585
586 // Edit/Command mode
586 // Edit/Command mode
587
587
588 /**
588 /**
589 * Gets the index of the cell that is in edit mode.
589 * Gets the index of the cell that is in edit mode.
590 *
590 *
591 * @method get_edit_index
591 * @method get_edit_index
592 *
592 *
593 * @return index {int}
593 * @return index {int}
594 **/
594 **/
595 Notebook.prototype.get_edit_index = function () {
595 Notebook.prototype.get_edit_index = function () {
596 var result = null;
596 var result = null;
597 this.get_cell_elements().filter(function (index) {
597 this.get_cell_elements().filter(function (index) {
598 if ($(this).data("cell").mode === 'edit') {
598 if ($(this).data("cell").mode === 'edit') {
599 result = index;
599 result = index;
600 }
600 }
601 });
601 });
602 return result;
602 return result;
603 };
603 };
604
604
605 /**
605 /**
606 * Handle when a a cell blurs and the notebook should enter command mode.
606 * Handle when a a cell blurs and the notebook should enter command mode.
607 *
607 *
608 * @method handle_command_mode
608 * @method handle_command_mode
609 * @param [cell] {Cell} Cell to enter command mode on.
609 * @param [cell] {Cell} Cell to enter command mode on.
610 **/
610 **/
611 Notebook.prototype.handle_command_mode = function (cell) {
611 Notebook.prototype.handle_command_mode = function (cell) {
612 if (this.mode !== 'command') {
612 if (this.mode !== 'command') {
613 cell.command_mode();
613 cell.command_mode();
614 this.mode = 'command';
614 this.mode = 'command';
615 this.events.trigger('command_mode.Notebook');
615 this.events.trigger('command_mode.Notebook');
616 this.keyboard_manager.command_mode();
616 this.keyboard_manager.command_mode();
617 }
617 }
618 };
618 };
619
619
620 /**
620 /**
621 * Make the notebook enter command mode.
621 * Make the notebook enter command mode.
622 *
622 *
623 * @method command_mode
623 * @method command_mode
624 **/
624 **/
625 Notebook.prototype.command_mode = function () {
625 Notebook.prototype.command_mode = function () {
626 var cell = this.get_cell(this.get_edit_index());
626 var cell = this.get_cell(this.get_edit_index());
627 if (cell && this.mode !== 'command') {
627 if (cell && this.mode !== 'command') {
628 // We don't call cell.command_mode, but rather call cell.focus_cell()
628 // We don't call cell.command_mode, but rather call cell.focus_cell()
629 // which will blur and CM editor and trigger the call to
629 // which will blur and CM editor and trigger the call to
630 // handle_command_mode.
630 // handle_command_mode.
631 cell.focus_cell();
631 cell.focus_cell();
632 }
632 }
633 };
633 };
634
634
635 /**
635 /**
636 * Handle when a cell fires it's edit_mode event.
636 * Handle when a cell fires it's edit_mode event.
637 *
637 *
638 * @method handle_edit_mode
638 * @method handle_edit_mode
639 * @param [cell] {Cell} Cell to enter edit mode on.
639 * @param [cell] {Cell} Cell to enter edit mode on.
640 **/
640 **/
641 Notebook.prototype.handle_edit_mode = function (cell) {
641 Notebook.prototype.handle_edit_mode = function (cell) {
642 if (cell && this.mode !== 'edit') {
642 if (cell && this.mode !== 'edit') {
643 cell.edit_mode();
643 cell.edit_mode();
644 this.mode = 'edit';
644 this.mode = 'edit';
645 this.events.trigger('edit_mode.Notebook');
645 this.events.trigger('edit_mode.Notebook');
646 this.keyboard_manager.edit_mode();
646 this.keyboard_manager.edit_mode();
647 }
647 }
648 };
648 };
649
649
650 /**
650 /**
651 * Make a cell enter edit mode.
651 * Make a cell enter edit mode.
652 *
652 *
653 * @method edit_mode
653 * @method edit_mode
654 **/
654 **/
655 Notebook.prototype.edit_mode = function () {
655 Notebook.prototype.edit_mode = function () {
656 var cell = this.get_selected_cell();
656 var cell = this.get_selected_cell();
657 if (cell && this.mode !== 'edit') {
657 if (cell && this.mode !== 'edit') {
658 cell.unrender();
658 cell.unrender();
659 cell.focus_editor();
659 cell.focus_editor();
660 }
660 }
661 };
661 };
662
662
663 /**
663 /**
664 * Focus the currently selected cell.
664 * Focus the currently selected cell.
665 *
665 *
666 * @method focus_cell
666 * @method focus_cell
667 **/
667 **/
668 Notebook.prototype.focus_cell = function () {
668 Notebook.prototype.focus_cell = function () {
669 var cell = this.get_selected_cell();
669 var cell = this.get_selected_cell();
670 if (cell === null) {return;} // No cell is selected
670 if (cell === null) {return;} // No cell is selected
671 cell.focus_cell();
671 cell.focus_cell();
672 };
672 };
673
673
674 // Cell movement
674 // Cell movement
675
675
676 /**
676 /**
677 * Move given (or selected) cell up and select it.
677 * Move given (or selected) cell up and select it.
678 *
678 *
679 * @method move_cell_up
679 * @method move_cell_up
680 * @param [index] {integer} cell index
680 * @param [index] {integer} cell index
681 * @return {Notebook} This notebook
681 * @return {Notebook} This notebook
682 **/
682 **/
683 Notebook.prototype.move_cell_up = function (index) {
683 Notebook.prototype.move_cell_up = function (index) {
684 var i = this.index_or_selected(index);
684 var i = this.index_or_selected(index);
685 if (this.is_valid_cell_index(i) && i > 0) {
685 if (this.is_valid_cell_index(i) && i > 0) {
686 var pivot = this.get_cell_element(i-1);
686 var pivot = this.get_cell_element(i-1);
687 var tomove = this.get_cell_element(i);
687 var tomove = this.get_cell_element(i);
688 if (pivot !== null && tomove !== null) {
688 if (pivot !== null && tomove !== null) {
689 tomove.detach();
689 tomove.detach();
690 pivot.before(tomove);
690 pivot.before(tomove);
691 this.select(i-1);
691 this.select(i-1);
692 var cell = this.get_selected_cell();
692 var cell = this.get_selected_cell();
693 cell.focus_cell();
693 cell.focus_cell();
694 }
694 }
695 this.set_dirty(true);
695 this.set_dirty(true);
696 }
696 }
697 return this;
697 return this;
698 };
698 };
699
699
700
700
701 /**
701 /**
702 * Move given (or selected) cell down and select it
702 * Move given (or selected) cell down and select it
703 *
703 *
704 * @method move_cell_down
704 * @method move_cell_down
705 * @param [index] {integer} cell index
705 * @param [index] {integer} cell index
706 * @return {Notebook} This notebook
706 * @return {Notebook} This notebook
707 **/
707 **/
708 Notebook.prototype.move_cell_down = function (index) {
708 Notebook.prototype.move_cell_down = function (index) {
709 var i = this.index_or_selected(index);
709 var i = this.index_or_selected(index);
710 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
710 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
711 var pivot = this.get_cell_element(i+1);
711 var pivot = this.get_cell_element(i+1);
712 var tomove = this.get_cell_element(i);
712 var tomove = this.get_cell_element(i);
713 if (pivot !== null && tomove !== null) {
713 if (pivot !== null && tomove !== null) {
714 tomove.detach();
714 tomove.detach();
715 pivot.after(tomove);
715 pivot.after(tomove);
716 this.select(i+1);
716 this.select(i+1);
717 var cell = this.get_selected_cell();
717 var cell = this.get_selected_cell();
718 cell.focus_cell();
718 cell.focus_cell();
719 }
719 }
720 }
720 }
721 this.set_dirty();
721 this.set_dirty();
722 return this;
722 return this;
723 };
723 };
724
724
725
725
726 // Insertion, deletion.
726 // Insertion, deletion.
727
727
728 /**
728 /**
729 * Delete a cell from the notebook.
729 * Delete a cell from the notebook.
730 *
730 *
731 * @method delete_cell
731 * @method delete_cell
732 * @param [index] A cell's numeric index
732 * @param [index] A cell's numeric index
733 * @return {Notebook} This notebook
733 * @return {Notebook} This notebook
734 */
734 */
735 Notebook.prototype.delete_cell = function (index) {
735 Notebook.prototype.delete_cell = function (index) {
736 var i = this.index_or_selected(index);
736 var i = this.index_or_selected(index);
737 var cell = this.get_selected_cell();
737 var cell = this.get_selected_cell();
738 this.undelete_backup = cell.toJSON();
738 this.undelete_backup = cell.toJSON();
739 $('#undelete_cell').removeClass('disabled');
739 $('#undelete_cell').removeClass('disabled');
740 if (this.is_valid_cell_index(i)) {
740 if (this.is_valid_cell_index(i)) {
741 var old_ncells = this.ncells();
741 var old_ncells = this.ncells();
742 var ce = this.get_cell_element(i);
742 var ce = this.get_cell_element(i);
743 ce.remove();
743 ce.remove();
744 if (i === 0) {
744 if (i === 0) {
745 // Always make sure we have at least one cell.
745 // Always make sure we have at least one cell.
746 if (old_ncells === 1) {
746 if (old_ncells === 1) {
747 this.insert_cell_below('code');
747 this.insert_cell_below('code');
748 }
748 }
749 this.select(0);
749 this.select(0);
750 this.undelete_index = 0;
750 this.undelete_index = 0;
751 this.undelete_below = false;
751 this.undelete_below = false;
752 } else if (i === old_ncells-1 && i !== 0) {
752 } else if (i === old_ncells-1 && i !== 0) {
753 this.select(i-1);
753 this.select(i-1);
754 this.undelete_index = i - 1;
754 this.undelete_index = i - 1;
755 this.undelete_below = true;
755 this.undelete_below = true;
756 } else {
756 } else {
757 this.select(i);
757 this.select(i);
758 this.undelete_index = i;
758 this.undelete_index = i;
759 this.undelete_below = false;
759 this.undelete_below = false;
760 }
760 }
761 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
761 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
762 this.set_dirty(true);
762 this.set_dirty(true);
763 }
763 }
764 return this;
764 return this;
765 };
765 };
766
766
767 /**
767 /**
768 * Restore the most recently deleted cell.
768 * Restore the most recently deleted cell.
769 *
769 *
770 * @method undelete
770 * @method undelete
771 */
771 */
772 Notebook.prototype.undelete_cell = function() {
772 Notebook.prototype.undelete_cell = function() {
773 if (this.undelete_backup !== null && this.undelete_index !== null) {
773 if (this.undelete_backup !== null && this.undelete_index !== null) {
774 var current_index = this.get_selected_index();
774 var current_index = this.get_selected_index();
775 if (this.undelete_index < current_index) {
775 if (this.undelete_index < current_index) {
776 current_index = current_index + 1;
776 current_index = current_index + 1;
777 }
777 }
778 if (this.undelete_index >= this.ncells()) {
778 if (this.undelete_index >= this.ncells()) {
779 this.select(this.ncells() - 1);
779 this.select(this.ncells() - 1);
780 }
780 }
781 else {
781 else {
782 this.select(this.undelete_index);
782 this.select(this.undelete_index);
783 }
783 }
784 var cell_data = this.undelete_backup;
784 var cell_data = this.undelete_backup;
785 var new_cell = null;
785 var new_cell = null;
786 if (this.undelete_below) {
786 if (this.undelete_below) {
787 new_cell = this.insert_cell_below(cell_data.cell_type);
787 new_cell = this.insert_cell_below(cell_data.cell_type);
788 } else {
788 } else {
789 new_cell = this.insert_cell_above(cell_data.cell_type);
789 new_cell = this.insert_cell_above(cell_data.cell_type);
790 }
790 }
791 new_cell.fromJSON(cell_data);
791 new_cell.fromJSON(cell_data);
792 if (this.undelete_below) {
792 if (this.undelete_below) {
793 this.select(current_index+1);
793 this.select(current_index+1);
794 } else {
794 } else {
795 this.select(current_index);
795 this.select(current_index);
796 }
796 }
797 this.undelete_backup = null;
797 this.undelete_backup = null;
798 this.undelete_index = null;
798 this.undelete_index = null;
799 }
799 }
800 $('#undelete_cell').addClass('disabled');
800 $('#undelete_cell').addClass('disabled');
801 };
801 };
802
802
803 /**
803 /**
804 * Insert a cell so that after insertion the cell is at given index.
804 * Insert a cell so that after insertion the cell is at given index.
805 *
805 *
806 * If cell type is not provided, it will default to the type of the
806 * If cell type is not provided, it will default to the type of the
807 * currently active cell.
807 * currently active cell.
808 *
808 *
809 * Similar to insert_above, but index parameter is mandatory
809 * Similar to insert_above, but index parameter is mandatory
810 *
810 *
811 * Index will be brought back into the accessible range [0,n]
811 * Index will be brought back into the accessible range [0,n]
812 *
812 *
813 * @method insert_cell_at_index
813 * @method insert_cell_at_index
814 * @param [type] {string} in ['code','markdown','heading'], defaults to 'code'
814 * @param [type] {string} in ['code','markdown','heading'], defaults to 'code'
815 * @param [index] {int} a valid index where to insert cell
815 * @param [index] {int} a valid index where to insert cell
816 *
816 *
817 * @return cell {cell|null} created cell or null
817 * @return cell {cell|null} created cell or null
818 **/
818 **/
819 Notebook.prototype.insert_cell_at_index = function(type, index){
819 Notebook.prototype.insert_cell_at_index = function(type, index){
820
820
821 var ncells = this.ncells();
821 var ncells = this.ncells();
822 index = Math.min(index,ncells);
822 index = Math.min(index,ncells);
823 index = Math.max(index,0);
823 index = Math.max(index,0);
824 var cell = null;
824 var cell = null;
825 type = type || this.get_selected_cell().cell_type;
825 type = type || this.get_selected_cell().cell_type;
826
826
827 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
827 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
828 var cell_options = {
828 var cell_options = {
829 events: this.events,
829 events: this.events,
830 config: this.config,
830 config: this.config,
831 keyboard_manager: this.keyboard_manager,
831 keyboard_manager: this.keyboard_manager,
832 notebook: this,
832 notebook: this,
833 tooltip: this.tooltip,
833 tooltip: this.tooltip,
834 };
834 };
835 if (type === 'code') {
835 if (type === 'code') {
836 cell = new codecell.CodeCell(this.kernel, cell_options);
836 cell = new codecell.CodeCell(this.kernel, cell_options);
837 cell.set_input_prompt();
837 cell.set_input_prompt();
838 } else if (type === 'markdown') {
838 } else if (type === 'markdown') {
839 cell = new textcell.MarkdownCell(cell_options);
839 cell = new textcell.MarkdownCell(cell_options);
840 } else if (type === 'raw') {
840 } else if (type === 'raw') {
841 cell = new textcell.RawCell(cell_options);
841 cell = new textcell.RawCell(cell_options);
842 } else if (type === 'heading') {
842 } else if (type === 'heading') {
843 cell = new textcell.HeadingCell(cell_options);
843 cell = new textcell.HeadingCell(cell_options);
844 }
844 }
845
845
846 if(this._insert_element_at_index(cell.element,index)) {
846 if(this._insert_element_at_index(cell.element,index)) {
847 cell.render();
847 cell.render();
848 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
848 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
849 cell.refresh();
849 cell.refresh();
850 // We used to select the cell after we refresh it, but there
850 // We used to select the cell after we refresh it, but there
851 // are now cases were this method is called where select is
851 // are now cases were this method is called where select is
852 // not appropriate. The selection logic should be handled by the
852 // not appropriate. The selection logic should be handled by the
853 // caller of the the top level insert_cell methods.
853 // caller of the the top level insert_cell methods.
854 this.set_dirty(true);
854 this.set_dirty(true);
855 }
855 }
856 }
856 }
857 return cell;
857 return cell;
858
858
859 };
859 };
860
860
861 /**
861 /**
862 * Insert an element at given cell index.
862 * Insert an element at given cell index.
863 *
863 *
864 * @method _insert_element_at_index
864 * @method _insert_element_at_index
865 * @param element {dom element} a cell element
865 * @param element {dom element} a cell element
866 * @param [index] {int} a valid index where to inser cell
866 * @param [index] {int} a valid index where to inser cell
867 * @private
867 * @private
868 *
868 *
869 * return true if everything whent fine.
869 * return true if everything whent fine.
870 **/
870 **/
871 Notebook.prototype._insert_element_at_index = function(element, index){
871 Notebook.prototype._insert_element_at_index = function(element, index){
872 if (element === undefined){
872 if (element === undefined){
873 return false;
873 return false;
874 }
874 }
875
875
876 var ncells = this.ncells();
876 var ncells = this.ncells();
877
877
878 if (ncells === 0) {
878 if (ncells === 0) {
879 // special case append if empty
879 // special case append if empty
880 this.element.find('div.end_space').before(element);
880 this.element.find('div.end_space').before(element);
881 } else if ( ncells === index ) {
881 } else if ( ncells === index ) {
882 // special case append it the end, but not empty
882 // special case append it the end, but not empty
883 this.get_cell_element(index-1).after(element);
883 this.get_cell_element(index-1).after(element);
884 } else if (this.is_valid_cell_index(index)) {
884 } else if (this.is_valid_cell_index(index)) {
885 // otherwise always somewhere to append to
885 // otherwise always somewhere to append to
886 this.get_cell_element(index).before(element);
886 this.get_cell_element(index).before(element);
887 } else {
887 } else {
888 return false;
888 return false;
889 }
889 }
890
890
891 if (this.undelete_index !== null && index <= this.undelete_index) {
891 if (this.undelete_index !== null && index <= this.undelete_index) {
892 this.undelete_index = this.undelete_index + 1;
892 this.undelete_index = this.undelete_index + 1;
893 this.set_dirty(true);
893 this.set_dirty(true);
894 }
894 }
895 return true;
895 return true;
896 };
896 };
897
897
898 /**
898 /**
899 * Insert a cell of given type above given index, or at top
899 * Insert a cell of given type above given index, or at top
900 * of notebook if index smaller than 0.
900 * of notebook if index smaller than 0.
901 *
901 *
902 * default index value is the one of currently selected cell
902 * default index value is the one of currently selected cell
903 *
903 *
904 * @method insert_cell_above
904 * @method insert_cell_above
905 * @param [type] {string} cell type
905 * @param [type] {string} cell type
906 * @param [index] {integer}
906 * @param [index] {integer}
907 *
907 *
908 * @return handle to created cell or null
908 * @return handle to created cell or null
909 **/
909 **/
910 Notebook.prototype.insert_cell_above = function (type, index) {
910 Notebook.prototype.insert_cell_above = function (type, index) {
911 index = this.index_or_selected(index);
911 index = this.index_or_selected(index);
912 return this.insert_cell_at_index(type, index);
912 return this.insert_cell_at_index(type, index);
913 };
913 };
914
914
915 /**
915 /**
916 * Insert a cell of given type below given index, or at bottom
916 * Insert a cell of given type below given index, or at bottom
917 * of notebook if index greater than number of cells
917 * of notebook if index greater than number of cells
918 *
918 *
919 * default index value is the one of currently selected cell
919 * default index value is the one of currently selected cell
920 *
920 *
921 * @method insert_cell_below
921 * @method insert_cell_below
922 * @param [type] {string} cell type
922 * @param [type] {string} cell type
923 * @param [index] {integer}
923 * @param [index] {integer}
924 *
924 *
925 * @return handle to created cell or null
925 * @return handle to created cell or null
926 *
926 *
927 **/
927 **/
928 Notebook.prototype.insert_cell_below = function (type, index) {
928 Notebook.prototype.insert_cell_below = function (type, index) {
929 index = this.index_or_selected(index);
929 index = this.index_or_selected(index);
930 return this.insert_cell_at_index(type, index+1);
930 return this.insert_cell_at_index(type, index+1);
931 };
931 };
932
932
933
933
934 /**
934 /**
935 * Insert cell at end of notebook
935 * Insert cell at end of notebook
936 *
936 *
937 * @method insert_cell_at_bottom
937 * @method insert_cell_at_bottom
938 * @param {String} type cell type
938 * @param {String} type cell type
939 *
939 *
940 * @return the added cell; or null
940 * @return the added cell; or null
941 **/
941 **/
942 Notebook.prototype.insert_cell_at_bottom = function (type){
942 Notebook.prototype.insert_cell_at_bottom = function (type){
943 var len = this.ncells();
943 var len = this.ncells();
944 return this.insert_cell_below(type,len-1);
944 return this.insert_cell_below(type,len-1);
945 };
945 };
946
946
947 /**
947 /**
948 * Turn a cell into a code cell.
948 * Turn a cell into a code cell.
949 *
949 *
950 * @method to_code
950 * @method to_code
951 * @param {Number} [index] A cell's index
951 * @param {Number} [index] A cell's index
952 */
952 */
953 Notebook.prototype.to_code = function (index) {
953 Notebook.prototype.to_code = function (index) {
954 var i = this.index_or_selected(index);
954 var i = this.index_or_selected(index);
955 if (this.is_valid_cell_index(i)) {
955 if (this.is_valid_cell_index(i)) {
956 var source_element = this.get_cell_element(i);
956 var source_element = this.get_cell_element(i);
957 var source_cell = source_element.data("cell");
957 var source_cell = source_element.data("cell");
958 if (!(source_cell instanceof codecell.CodeCell)) {
958 if (!(source_cell instanceof codecell.CodeCell)) {
959 var target_cell = this.insert_cell_below('code',i);
959 var target_cell = this.insert_cell_below('code',i);
960 var text = source_cell.get_text();
960 var text = source_cell.get_text();
961 if (text === source_cell.placeholder) {
961 if (text === source_cell.placeholder) {
962 text = '';
962 text = '';
963 }
963 }
964 target_cell.set_text(text);
964 target_cell.set_text(text);
965 // make this value the starting point, so that we can only undo
965 // make this value the starting point, so that we can only undo
966 // to this state, instead of a blank cell
966 // to this state, instead of a blank cell
967 target_cell.code_mirror.clearHistory();
967 target_cell.code_mirror.clearHistory();
968 source_element.remove();
968 source_element.remove();
969 this.select(i);
969 this.select(i);
970 var cursor = source_cell.code_mirror.getCursor();
970 var cursor = source_cell.code_mirror.getCursor();
971 target_cell.code_mirror.setCursor(cursor);
971 target_cell.code_mirror.setCursor(cursor);
972 this.set_dirty(true);
972 this.set_dirty(true);
973 }
973 }
974 }
974 }
975 };
975 };
976
976
977 /**
977 /**
978 * Turn a cell into a Markdown cell.
978 * Turn a cell into a Markdown cell.
979 *
979 *
980 * @method to_markdown
980 * @method to_markdown
981 * @param {Number} [index] A cell's index
981 * @param {Number} [index] A cell's index
982 */
982 */
983 Notebook.prototype.to_markdown = function (index) {
983 Notebook.prototype.to_markdown = function (index) {
984 var i = this.index_or_selected(index);
984 var i = this.index_or_selected(index);
985 if (this.is_valid_cell_index(i)) {
985 if (this.is_valid_cell_index(i)) {
986 var source_element = this.get_cell_element(i);
986 var source_element = this.get_cell_element(i);
987 var source_cell = source_element.data("cell");
987 var source_cell = source_element.data("cell");
988 if (!(source_cell instanceof textcell.MarkdownCell)) {
988 if (!(source_cell instanceof textcell.MarkdownCell)) {
989 var target_cell = this.insert_cell_below('markdown',i);
989 var target_cell = this.insert_cell_below('markdown',i);
990 var text = source_cell.get_text();
990 var text = source_cell.get_text();
991 if (text === source_cell.placeholder) {
991 if (text === source_cell.placeholder) {
992 text = '';
992 text = '';
993 }
993 }
994 // We must show the editor before setting its contents
994 // We must show the editor before setting its contents
995 target_cell.unrender();
995 target_cell.unrender();
996 target_cell.set_text(text);
996 target_cell.set_text(text);
997 // 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
998 // to this state, instead of a blank cell
998 // to this state, instead of a blank cell
999 target_cell.code_mirror.clearHistory();
999 target_cell.code_mirror.clearHistory();
1000 source_element.remove();
1000 source_element.remove();
1001 this.select(i);
1001 this.select(i);
1002 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1002 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1003 target_cell.render();
1003 target_cell.render();
1004 }
1004 }
1005 var cursor = source_cell.code_mirror.getCursor();
1005 var cursor = source_cell.code_mirror.getCursor();
1006 target_cell.code_mirror.setCursor(cursor);
1006 target_cell.code_mirror.setCursor(cursor);
1007 this.set_dirty(true);
1007 this.set_dirty(true);
1008 }
1008 }
1009 }
1009 }
1010 };
1010 };
1011
1011
1012 /**
1012 /**
1013 * Turn a cell into a raw text cell.
1013 * Turn a cell into a raw text cell.
1014 *
1014 *
1015 * @method to_raw
1015 * @method to_raw
1016 * @param {Number} [index] A cell's index
1016 * @param {Number} [index] A cell's index
1017 */
1017 */
1018 Notebook.prototype.to_raw = function (index) {
1018 Notebook.prototype.to_raw = function (index) {
1019 var i = this.index_or_selected(index);
1019 var i = this.index_or_selected(index);
1020 if (this.is_valid_cell_index(i)) {
1020 if (this.is_valid_cell_index(i)) {
1021 var source_element = this.get_cell_element(i);
1021 var source_element = this.get_cell_element(i);
1022 var source_cell = source_element.data("cell");
1022 var source_cell = source_element.data("cell");
1023 var target_cell = null;
1023 var target_cell = null;
1024 if (!(source_cell instanceof textcell.RawCell)) {
1024 if (!(source_cell instanceof textcell.RawCell)) {
1025 target_cell = this.insert_cell_below('raw',i);
1025 target_cell = this.insert_cell_below('raw',i);
1026 var text = source_cell.get_text();
1026 var text = source_cell.get_text();
1027 if (text === source_cell.placeholder) {
1027 if (text === source_cell.placeholder) {
1028 text = '';
1028 text = '';
1029 }
1029 }
1030 // We must show the editor before setting its contents
1030 // We must show the editor before setting its contents
1031 target_cell.unrender();
1031 target_cell.unrender();
1032 target_cell.set_text(text);
1032 target_cell.set_text(text);
1033 // make this value the starting point, so that we can only undo
1033 // make this value the starting point, so that we can only undo
1034 // to this state, instead of a blank cell
1034 // to this state, instead of a blank cell
1035 target_cell.code_mirror.clearHistory();
1035 target_cell.code_mirror.clearHistory();
1036 source_element.remove();
1036 source_element.remove();
1037 this.select(i);
1037 this.select(i);
1038 var cursor = source_cell.code_mirror.getCursor();
1038 var cursor = source_cell.code_mirror.getCursor();
1039 target_cell.code_mirror.setCursor(cursor);
1039 target_cell.code_mirror.setCursor(cursor);
1040 this.set_dirty(true);
1040 this.set_dirty(true);
1041 }
1041 }
1042 }
1042 }
1043 };
1043 };
1044
1044
1045 /**
1045 /**
1046 * Turn a cell into a heading cell.
1046 * Turn a cell into a heading cell.
1047 *
1047 *
1048 * @method to_heading
1048 * @method to_heading
1049 * @param {Number} [index] A cell's index
1049 * @param {Number} [index] A cell's index
1050 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
1050 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
1051 */
1051 */
1052 Notebook.prototype.to_heading = function (index, level) {
1052 Notebook.prototype.to_heading = function (index, level) {
1053 level = level || 1;
1053 level = level || 1;
1054 var i = this.index_or_selected(index);
1054 var i = this.index_or_selected(index);
1055 if (this.is_valid_cell_index(i)) {
1055 if (this.is_valid_cell_index(i)) {
1056 var source_element = this.get_cell_element(i);
1056 var source_element = this.get_cell_element(i);
1057 var source_cell = source_element.data("cell");
1057 var source_cell = source_element.data("cell");
1058 var target_cell = null;
1058 var target_cell = null;
1059 if (source_cell instanceof textcell.HeadingCell) {
1059 if (source_cell instanceof textcell.HeadingCell) {
1060 source_cell.set_level(level);
1060 source_cell.set_level(level);
1061 } else {
1061 } else {
1062 target_cell = this.insert_cell_below('heading',i);
1062 target_cell = this.insert_cell_below('heading',i);
1063 var text = source_cell.get_text();
1063 var text = source_cell.get_text();
1064 if (text === source_cell.placeholder) {
1064 if (text === source_cell.placeholder) {
1065 text = '';
1065 text = '';
1066 }
1066 }
1067 // We must show the editor before setting its contents
1067 // We must show the editor before setting its contents
1068 target_cell.set_level(level);
1068 target_cell.set_level(level);
1069 target_cell.unrender();
1069 target_cell.unrender();
1070 target_cell.set_text(text);
1070 target_cell.set_text(text);
1071 // make this value the starting point, so that we can only undo
1071 // make this value the starting point, so that we can only undo
1072 // to this state, instead of a blank cell
1072 // to this state, instead of a blank cell
1073 target_cell.code_mirror.clearHistory();
1073 target_cell.code_mirror.clearHistory();
1074 source_element.remove();
1074 source_element.remove();
1075 this.select(i);
1075 this.select(i);
1076 var cursor = source_cell.code_mirror.getCursor();
1076 var cursor = source_cell.code_mirror.getCursor();
1077 target_cell.code_mirror.setCursor(cursor);
1077 target_cell.code_mirror.setCursor(cursor);
1078 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1078 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1079 target_cell.render();
1079 target_cell.render();
1080 }
1080 }
1081 }
1081 }
1082 this.set_dirty(true);
1082 this.set_dirty(true);
1083 this.events.trigger('selected_cell_type_changed.Notebook',
1083 this.events.trigger('selected_cell_type_changed.Notebook',
1084 {'cell_type':'heading',level:level}
1084 {'cell_type':'heading',level:level}
1085 );
1085 );
1086 }
1086 }
1087 };
1087 };
1088
1088
1089
1089
1090 // Cut/Copy/Paste
1090 // Cut/Copy/Paste
1091
1091
1092 /**
1092 /**
1093 * Enable UI elements for pasting cells.
1093 * Enable UI elements for pasting cells.
1094 *
1094 *
1095 * @method enable_paste
1095 * @method enable_paste
1096 */
1096 */
1097 Notebook.prototype.enable_paste = function () {
1097 Notebook.prototype.enable_paste = function () {
1098 var that = this;
1098 var that = this;
1099 if (!this.paste_enabled) {
1099 if (!this.paste_enabled) {
1100 $('#paste_cell_replace').removeClass('disabled')
1100 $('#paste_cell_replace').removeClass('disabled')
1101 .on('click', function () {that.paste_cell_replace();});
1101 .on('click', function () {that.paste_cell_replace();});
1102 $('#paste_cell_above').removeClass('disabled')
1102 $('#paste_cell_above').removeClass('disabled')
1103 .on('click', function () {that.paste_cell_above();});
1103 .on('click', function () {that.paste_cell_above();});
1104 $('#paste_cell_below').removeClass('disabled')
1104 $('#paste_cell_below').removeClass('disabled')
1105 .on('click', function () {that.paste_cell_below();});
1105 .on('click', function () {that.paste_cell_below();});
1106 this.paste_enabled = true;
1106 this.paste_enabled = true;
1107 }
1107 }
1108 };
1108 };
1109
1109
1110 /**
1110 /**
1111 * Disable UI elements for pasting cells.
1111 * Disable UI elements for pasting cells.
1112 *
1112 *
1113 * @method disable_paste
1113 * @method disable_paste
1114 */
1114 */
1115 Notebook.prototype.disable_paste = function () {
1115 Notebook.prototype.disable_paste = function () {
1116 if (this.paste_enabled) {
1116 if (this.paste_enabled) {
1117 $('#paste_cell_replace').addClass('disabled').off('click');
1117 $('#paste_cell_replace').addClass('disabled').off('click');
1118 $('#paste_cell_above').addClass('disabled').off('click');
1118 $('#paste_cell_above').addClass('disabled').off('click');
1119 $('#paste_cell_below').addClass('disabled').off('click');
1119 $('#paste_cell_below').addClass('disabled').off('click');
1120 this.paste_enabled = false;
1120 this.paste_enabled = false;
1121 }
1121 }
1122 };
1122 };
1123
1123
1124 /**
1124 /**
1125 * Cut a cell.
1125 * Cut a cell.
1126 *
1126 *
1127 * @method cut_cell
1127 * @method cut_cell
1128 */
1128 */
1129 Notebook.prototype.cut_cell = function () {
1129 Notebook.prototype.cut_cell = function () {
1130 this.copy_cell();
1130 this.copy_cell();
1131 this.delete_cell();
1131 this.delete_cell();
1132 };
1132 };
1133
1133
1134 /**
1134 /**
1135 * Copy a cell.
1135 * Copy a cell.
1136 *
1136 *
1137 * @method copy_cell
1137 * @method copy_cell
1138 */
1138 */
1139 Notebook.prototype.copy_cell = function () {
1139 Notebook.prototype.copy_cell = function () {
1140 var cell = this.get_selected_cell();
1140 var cell = this.get_selected_cell();
1141 this.clipboard = cell.toJSON();
1141 this.clipboard = cell.toJSON();
1142 this.enable_paste();
1142 this.enable_paste();
1143 };
1143 };
1144
1144
1145 /**
1145 /**
1146 * Replace the selected cell with a cell in the clipboard.
1146 * Replace the selected cell with a cell in the clipboard.
1147 *
1147 *
1148 * @method paste_cell_replace
1148 * @method paste_cell_replace
1149 */
1149 */
1150 Notebook.prototype.paste_cell_replace = function () {
1150 Notebook.prototype.paste_cell_replace = function () {
1151 if (this.clipboard !== null && this.paste_enabled) {
1151 if (this.clipboard !== null && this.paste_enabled) {
1152 var cell_data = this.clipboard;
1152 var cell_data = this.clipboard;
1153 var new_cell = this.insert_cell_above(cell_data.cell_type);
1153 var new_cell = this.insert_cell_above(cell_data.cell_type);
1154 new_cell.fromJSON(cell_data);
1154 new_cell.fromJSON(cell_data);
1155 var old_cell = this.get_next_cell(new_cell);
1155 var old_cell = this.get_next_cell(new_cell);
1156 this.delete_cell(this.find_cell_index(old_cell));
1156 this.delete_cell(this.find_cell_index(old_cell));
1157 this.select(this.find_cell_index(new_cell));
1157 this.select(this.find_cell_index(new_cell));
1158 }
1158 }
1159 };
1159 };
1160
1160
1161 /**
1161 /**
1162 * Paste a cell from the clipboard above the selected cell.
1162 * Paste a cell from the clipboard above the selected cell.
1163 *
1163 *
1164 * @method paste_cell_above
1164 * @method paste_cell_above
1165 */
1165 */
1166 Notebook.prototype.paste_cell_above = function () {
1166 Notebook.prototype.paste_cell_above = function () {
1167 if (this.clipboard !== null && this.paste_enabled) {
1167 if (this.clipboard !== null && this.paste_enabled) {
1168 var cell_data = this.clipboard;
1168 var cell_data = this.clipboard;
1169 var new_cell = this.insert_cell_above(cell_data.cell_type);
1169 var new_cell = this.insert_cell_above(cell_data.cell_type);
1170 new_cell.fromJSON(cell_data);
1170 new_cell.fromJSON(cell_data);
1171 new_cell.focus_cell();
1171 new_cell.focus_cell();
1172 }
1172 }
1173 };
1173 };
1174
1174
1175 /**
1175 /**
1176 * Paste a cell from the clipboard below the selected cell.
1176 * Paste a cell from the clipboard below the selected cell.
1177 *
1177 *
1178 * @method paste_cell_below
1178 * @method paste_cell_below
1179 */
1179 */
1180 Notebook.prototype.paste_cell_below = function () {
1180 Notebook.prototype.paste_cell_below = function () {
1181 if (this.clipboard !== null && this.paste_enabled) {
1181 if (this.clipboard !== null && this.paste_enabled) {
1182 var cell_data = this.clipboard;
1182 var cell_data = this.clipboard;
1183 var new_cell = this.insert_cell_below(cell_data.cell_type);
1183 var new_cell = this.insert_cell_below(cell_data.cell_type);
1184 new_cell.fromJSON(cell_data);
1184 new_cell.fromJSON(cell_data);
1185 new_cell.focus_cell();
1185 new_cell.focus_cell();
1186 }
1186 }
1187 };
1187 };
1188
1188
1189 // Split/merge
1189 // Split/merge
1190
1190
1191 /**
1191 /**
1192 * Split the selected cell into two, at the cursor.
1192 * Split the selected cell into two, at the cursor.
1193 *
1193 *
1194 * @method split_cell
1194 * @method split_cell
1195 */
1195 */
1196 Notebook.prototype.split_cell = function () {
1196 Notebook.prototype.split_cell = function () {
1197 var mdc = textcell.MarkdownCell;
1197 var mdc = textcell.MarkdownCell;
1198 var rc = textcell.RawCell;
1198 var rc = textcell.RawCell;
1199 var cell = this.get_selected_cell();
1199 var cell = this.get_selected_cell();
1200 if (cell.is_splittable()) {
1200 if (cell.is_splittable()) {
1201 var texta = cell.get_pre_cursor();
1201 var texta = cell.get_pre_cursor();
1202 var textb = cell.get_post_cursor();
1202 var textb = cell.get_post_cursor();
1203 if (cell instanceof codecell.CodeCell) {
1203 if (cell instanceof codecell.CodeCell) {
1204 // In this case the operations keep the notebook in its existing mode
1204 // In this case the operations keep the notebook in its existing mode
1205 // so we don't need to do any post-op mode changes.
1205 // so we don't need to do any post-op mode changes.
1206 cell.set_text(textb);
1206 cell.set_text(textb);
1207 var new_cell = this.insert_cell_above('code');
1207 var new_cell = this.insert_cell_above('code');
1208 new_cell.set_text(texta);
1208 new_cell.set_text(texta);
1209 } else if ((cell instanceof mdc && !cell.rendered) || (cell instanceof rc)) {
1209 } else if ((cell instanceof mdc && !cell.rendered) || (cell instanceof rc)) {
1210 // We know cell is !rendered so we can use set_text.
1210 // We know cell is !rendered so we can use set_text.
1211 cell.set_text(textb);
1211 cell.set_text(textb);
1212 var new_cell = this.insert_cell_above(cell.cell_type);
1212 var new_cell = this.insert_cell_above(cell.cell_type);
1213 // Unrender the new cell so we can call set_text.
1213 // Unrender the new cell so we can call set_text.
1214 new_cell.unrender();
1214 new_cell.unrender();
1215 new_cell.set_text(texta);
1215 new_cell.set_text(texta);
1216 }
1216 }
1217 }
1217 }
1218 };
1218 };
1219
1219
1220 /**
1220 /**
1221 * Combine the selected cell into the cell above it.
1221 * Combine the selected cell into the cell above it.
1222 *
1222 *
1223 * @method merge_cell_above
1223 * @method merge_cell_above
1224 */
1224 */
1225 Notebook.prototype.merge_cell_above = function () {
1225 Notebook.prototype.merge_cell_above = function () {
1226 var mdc = textcell.MarkdownCell;
1226 var mdc = textcell.MarkdownCell;
1227 var rc = textcell.RawCell;
1227 var rc = textcell.RawCell;
1228 var index = this.get_selected_index();
1228 var index = this.get_selected_index();
1229 var cell = this.get_cell(index);
1229 var cell = this.get_cell(index);
1230 var render = cell.rendered;
1230 var render = cell.rendered;
1231 if (!cell.is_mergeable()) {
1231 if (!cell.is_mergeable()) {
1232 return;
1232 return;
1233 }
1233 }
1234 if (index > 0) {
1234 if (index > 0) {
1235 var upper_cell = this.get_cell(index-1);
1235 var upper_cell = this.get_cell(index-1);
1236 if (!upper_cell.is_mergeable()) {
1236 if (!upper_cell.is_mergeable()) {
1237 return;
1237 return;
1238 }
1238 }
1239 var upper_text = upper_cell.get_text();
1239 var upper_text = upper_cell.get_text();
1240 var text = cell.get_text();
1240 var text = cell.get_text();
1241 if (cell instanceof codecell.CodeCell) {
1241 if (cell instanceof codecell.CodeCell) {
1242 cell.set_text(upper_text+'\n'+text);
1242 cell.set_text(upper_text+'\n'+text);
1243 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1243 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1244 cell.unrender(); // Must unrender before we set_text.
1244 cell.unrender(); // Must unrender before we set_text.
1245 cell.set_text(upper_text+'\n\n'+text);
1245 cell.set_text(upper_text+'\n\n'+text);
1246 if (render) {
1246 if (render) {
1247 // The rendered state of the final cell should match
1247 // The rendered state of the final cell should match
1248 // that of the original selected cell;
1248 // that of the original selected cell;
1249 cell.render();
1249 cell.render();
1250 }
1250 }
1251 }
1251 }
1252 this.delete_cell(index-1);
1252 this.delete_cell(index-1);
1253 this.select(this.find_cell_index(cell));
1253 this.select(this.find_cell_index(cell));
1254 }
1254 }
1255 };
1255 };
1256
1256
1257 /**
1257 /**
1258 * Combine the selected cell into the cell below it.
1258 * Combine the selected cell into the cell below it.
1259 *
1259 *
1260 * @method merge_cell_below
1260 * @method merge_cell_below
1261 */
1261 */
1262 Notebook.prototype.merge_cell_below = function () {
1262 Notebook.prototype.merge_cell_below = function () {
1263 var mdc = textcell.MarkdownCell;
1263 var mdc = textcell.MarkdownCell;
1264 var rc = textcell.RawCell;
1264 var rc = textcell.RawCell;
1265 var index = this.get_selected_index();
1265 var index = this.get_selected_index();
1266 var cell = this.get_cell(index);
1266 var cell = this.get_cell(index);
1267 var render = cell.rendered;
1267 var render = cell.rendered;
1268 if (!cell.is_mergeable()) {
1268 if (!cell.is_mergeable()) {
1269 return;
1269 return;
1270 }
1270 }
1271 if (index < this.ncells()-1) {
1271 if (index < this.ncells()-1) {
1272 var lower_cell = this.get_cell(index+1);
1272 var lower_cell = this.get_cell(index+1);
1273 if (!lower_cell.is_mergeable()) {
1273 if (!lower_cell.is_mergeable()) {
1274 return;
1274 return;
1275 }
1275 }
1276 var lower_text = lower_cell.get_text();
1276 var lower_text = lower_cell.get_text();
1277 var text = cell.get_text();
1277 var text = cell.get_text();
1278 if (cell instanceof codecell.CodeCell) {
1278 if (cell instanceof codecell.CodeCell) {
1279 cell.set_text(text+'\n'+lower_text);
1279 cell.set_text(text+'\n'+lower_text);
1280 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1280 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1281 cell.unrender(); // Must unrender before we set_text.
1281 cell.unrender(); // Must unrender before we set_text.
1282 cell.set_text(text+'\n\n'+lower_text);
1282 cell.set_text(text+'\n\n'+lower_text);
1283 if (render) {
1283 if (render) {
1284 // The rendered state of the final cell should match
1284 // The rendered state of the final cell should match
1285 // that of the original selected cell;
1285 // that of the original selected cell;
1286 cell.render();
1286 cell.render();
1287 }
1287 }
1288 }
1288 }
1289 this.delete_cell(index+1);
1289 this.delete_cell(index+1);
1290 this.select(this.find_cell_index(cell));
1290 this.select(this.find_cell_index(cell));
1291 }
1291 }
1292 };
1292 };
1293
1293
1294
1294
1295 // Cell collapsing and output clearing
1295 // Cell collapsing and output clearing
1296
1296
1297 /**
1297 /**
1298 * Hide a cell's output.
1298 * Hide a cell's output.
1299 *
1299 *
1300 * @method collapse_output
1300 * @method collapse_output
1301 * @param {Number} index A cell's numeric index
1301 * @param {Number} index A cell's numeric index
1302 */
1302 */
1303 Notebook.prototype.collapse_output = function (index) {
1303 Notebook.prototype.collapse_output = function (index) {
1304 var i = this.index_or_selected(index);
1304 var i = this.index_or_selected(index);
1305 var cell = this.get_cell(i);
1305 var cell = this.get_cell(i);
1306 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1306 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1307 cell.collapse_output();
1307 cell.collapse_output();
1308 this.set_dirty(true);
1308 this.set_dirty(true);
1309 }
1309 }
1310 };
1310 };
1311
1311
1312 /**
1312 /**
1313 * Hide each code cell's output area.
1313 * Hide each code cell's output area.
1314 *
1314 *
1315 * @method collapse_all_output
1315 * @method collapse_all_output
1316 */
1316 */
1317 Notebook.prototype.collapse_all_output = function () {
1317 Notebook.prototype.collapse_all_output = function () {
1318 $.map(this.get_cells(), function (cell, i) {
1318 $.map(this.get_cells(), function (cell, i) {
1319 if (cell instanceof codecell.CodeCell) {
1319 if (cell instanceof codecell.CodeCell) {
1320 cell.collapse_output();
1320 cell.collapse_output();
1321 }
1321 }
1322 });
1322 });
1323 // this should not be set if the `collapse` key is removed from nbformat
1323 // this should not be set if the `collapse` key is removed from nbformat
1324 this.set_dirty(true);
1324 this.set_dirty(true);
1325 };
1325 };
1326
1326
1327 /**
1327 /**
1328 * Show a cell's output.
1328 * Show a cell's output.
1329 *
1329 *
1330 * @method expand_output
1330 * @method expand_output
1331 * @param {Number} index A cell's numeric index
1331 * @param {Number} index A cell's numeric index
1332 */
1332 */
1333 Notebook.prototype.expand_output = function (index) {
1333 Notebook.prototype.expand_output = function (index) {
1334 var i = this.index_or_selected(index);
1334 var i = this.index_or_selected(index);
1335 var cell = this.get_cell(i);
1335 var cell = this.get_cell(i);
1336 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1336 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1337 cell.expand_output();
1337 cell.expand_output();
1338 this.set_dirty(true);
1338 this.set_dirty(true);
1339 }
1339 }
1340 };
1340 };
1341
1341
1342 /**
1342 /**
1343 * Expand each code cell's output area, and remove scrollbars.
1343 * Expand each code cell's output area, and remove scrollbars.
1344 *
1344 *
1345 * @method expand_all_output
1345 * @method expand_all_output
1346 */
1346 */
1347 Notebook.prototype.expand_all_output = function () {
1347 Notebook.prototype.expand_all_output = function () {
1348 $.map(this.get_cells(), function (cell, i) {
1348 $.map(this.get_cells(), function (cell, i) {
1349 if (cell instanceof codecell.CodeCell) {
1349 if (cell instanceof codecell.CodeCell) {
1350 cell.expand_output();
1350 cell.expand_output();
1351 }
1351 }
1352 });
1352 });
1353 // this should not be set if the `collapse` key is removed from nbformat
1353 // this should not be set if the `collapse` key is removed from nbformat
1354 this.set_dirty(true);
1354 this.set_dirty(true);
1355 };
1355 };
1356
1356
1357 /**
1357 /**
1358 * Clear the selected CodeCell's output area.
1358 * Clear the selected CodeCell's output area.
1359 *
1359 *
1360 * @method clear_output
1360 * @method clear_output
1361 * @param {Number} index A cell's numeric index
1361 * @param {Number} index A cell's numeric index
1362 */
1362 */
1363 Notebook.prototype.clear_output = function (index) {
1363 Notebook.prototype.clear_output = function (index) {
1364 var i = this.index_or_selected(index);
1364 var i = this.index_or_selected(index);
1365 var cell = this.get_cell(i);
1365 var cell = this.get_cell(i);
1366 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1366 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1367 cell.clear_output();
1367 cell.clear_output();
1368 this.set_dirty(true);
1368 this.set_dirty(true);
1369 }
1369 }
1370 };
1370 };
1371
1371
1372 /**
1372 /**
1373 * Clear each code cell's output area.
1373 * Clear each code cell's output area.
1374 *
1374 *
1375 * @method clear_all_output
1375 * @method clear_all_output
1376 */
1376 */
1377 Notebook.prototype.clear_all_output = function () {
1377 Notebook.prototype.clear_all_output = function () {
1378 $.map(this.get_cells(), function (cell, i) {
1378 $.map(this.get_cells(), function (cell, i) {
1379 if (cell instanceof codecell.CodeCell) {
1379 if (cell instanceof codecell.CodeCell) {
1380 cell.clear_output();
1380 cell.clear_output();
1381 }
1381 }
1382 });
1382 });
1383 this.set_dirty(true);
1383 this.set_dirty(true);
1384 };
1384 };
1385
1385
1386 /**
1386 /**
1387 * Scroll the selected CodeCell's output area.
1387 * Scroll the selected CodeCell's output area.
1388 *
1388 *
1389 * @method scroll_output
1389 * @method scroll_output
1390 * @param {Number} index A cell's numeric index
1390 * @param {Number} index A cell's numeric index
1391 */
1391 */
1392 Notebook.prototype.scroll_output = function (index) {
1392 Notebook.prototype.scroll_output = function (index) {
1393 var i = this.index_or_selected(index);
1393 var i = this.index_or_selected(index);
1394 var cell = this.get_cell(i);
1394 var cell = this.get_cell(i);
1395 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1395 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1396 cell.scroll_output();
1396 cell.scroll_output();
1397 this.set_dirty(true);
1397 this.set_dirty(true);
1398 }
1398 }
1399 };
1399 };
1400
1400
1401 /**
1401 /**
1402 * Expand each code cell's output area, and add a scrollbar for long output.
1402 * Expand each code cell's output area, and add a scrollbar for long output.
1403 *
1403 *
1404 * @method scroll_all_output
1404 * @method scroll_all_output
1405 */
1405 */
1406 Notebook.prototype.scroll_all_output = function () {
1406 Notebook.prototype.scroll_all_output = function () {
1407 $.map(this.get_cells(), function (cell, i) {
1407 $.map(this.get_cells(), function (cell, i) {
1408 if (cell instanceof codecell.CodeCell) {
1408 if (cell instanceof codecell.CodeCell) {
1409 cell.scroll_output();
1409 cell.scroll_output();
1410 }
1410 }
1411 });
1411 });
1412 // this should not be set if the `collapse` key is removed from nbformat
1412 // this should not be set if the `collapse` key is removed from nbformat
1413 this.set_dirty(true);
1413 this.set_dirty(true);
1414 };
1414 };
1415
1415
1416 /** Toggle whether a cell's output is collapsed or expanded.
1416 /** Toggle whether a cell's output is collapsed or expanded.
1417 *
1417 *
1418 * @method toggle_output
1418 * @method toggle_output
1419 * @param {Number} index A cell's numeric index
1419 * @param {Number} index A cell's numeric index
1420 */
1420 */
1421 Notebook.prototype.toggle_output = function (index) {
1421 Notebook.prototype.toggle_output = function (index) {
1422 var i = this.index_or_selected(index);
1422 var i = this.index_or_selected(index);
1423 var cell = this.get_cell(i);
1423 var cell = this.get_cell(i);
1424 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1424 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1425 cell.toggle_output();
1425 cell.toggle_output();
1426 this.set_dirty(true);
1426 this.set_dirty(true);
1427 }
1427 }
1428 };
1428 };
1429
1429
1430 /**
1430 /**
1431 * Hide/show the output of all cells.
1431 * Hide/show the output of all cells.
1432 *
1432 *
1433 * @method toggle_all_output
1433 * @method toggle_all_output
1434 */
1434 */
1435 Notebook.prototype.toggle_all_output = function () {
1435 Notebook.prototype.toggle_all_output = function () {
1436 $.map(this.get_cells(), function (cell, i) {
1436 $.map(this.get_cells(), function (cell, i) {
1437 if (cell instanceof codecell.CodeCell) {
1437 if (cell instanceof codecell.CodeCell) {
1438 cell.toggle_output();
1438 cell.toggle_output();
1439 }
1439 }
1440 });
1440 });
1441 // this should not be set if the `collapse` key is removed from nbformat
1441 // this should not be set if the `collapse` key is removed from nbformat
1442 this.set_dirty(true);
1442 this.set_dirty(true);
1443 };
1443 };
1444
1444
1445 /**
1445 /**
1446 * Toggle a scrollbar for long cell outputs.
1446 * Toggle a scrollbar for long cell outputs.
1447 *
1447 *
1448 * @method toggle_output_scroll
1448 * @method toggle_output_scroll
1449 * @param {Number} index A cell's numeric index
1449 * @param {Number} index A cell's numeric index
1450 */
1450 */
1451 Notebook.prototype.toggle_output_scroll = function (index) {
1451 Notebook.prototype.toggle_output_scroll = function (index) {
1452 var i = this.index_or_selected(index);
1452 var i = this.index_or_selected(index);
1453 var cell = this.get_cell(i);
1453 var cell = this.get_cell(i);
1454 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1454 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1455 cell.toggle_output_scroll();
1455 cell.toggle_output_scroll();
1456 this.set_dirty(true);
1456 this.set_dirty(true);
1457 }
1457 }
1458 };
1458 };
1459
1459
1460 /**
1460 /**
1461 * Toggle the scrolling of long output on all cells.
1461 * Toggle the scrolling of long output on all cells.
1462 *
1462 *
1463 * @method toggle_all_output_scrolling
1463 * @method toggle_all_output_scrolling
1464 */
1464 */
1465 Notebook.prototype.toggle_all_output_scroll = function () {
1465 Notebook.prototype.toggle_all_output_scroll = function () {
1466 $.map(this.get_cells(), function (cell, i) {
1466 $.map(this.get_cells(), function (cell, i) {
1467 if (cell instanceof codecell.CodeCell) {
1467 if (cell instanceof codecell.CodeCell) {
1468 cell.toggle_output_scroll();
1468 cell.toggle_output_scroll();
1469 }
1469 }
1470 });
1470 });
1471 // this should not be set if the `collapse` key is removed from nbformat
1471 // this should not be set if the `collapse` key is removed from nbformat
1472 this.set_dirty(true);
1472 this.set_dirty(true);
1473 };
1473 };
1474
1474
1475 // Other cell functions: line numbers, ...
1475 // Other cell functions: line numbers, ...
1476
1476
1477 /**
1477 /**
1478 * Toggle line numbers in the selected cell's input area.
1478 * Toggle line numbers in the selected cell's input area.
1479 *
1479 *
1480 * @method cell_toggle_line_numbers
1480 * @method cell_toggle_line_numbers
1481 */
1481 */
1482 Notebook.prototype.cell_toggle_line_numbers = function() {
1482 Notebook.prototype.cell_toggle_line_numbers = function() {
1483 this.get_selected_cell().toggle_line_numbers();
1483 this.get_selected_cell().toggle_line_numbers();
1484 };
1484 };
1485
1485
1486 // Session related things
1486 // Session related things
1487
1487
1488 /**
1488 /**
1489 * Start a new session and set it on each code cell.
1489 * Start a new session and set it on each code cell.
1490 *
1490 *
1491 * @method start_session
1491 * @method start_session
1492 */
1492 */
1493 Notebook.prototype.start_session = function () {
1493 Notebook.prototype.start_session = function () {
1494 this.session = new session.Session({
1494 this.session = new session.Session({
1495 base_url: this.base_url,
1495 base_url: this.base_url,
1496 notebook_path: this.notebook_path,
1496 notebook_path: this.notebook_path,
1497 notebook_name: this.notebook_name,
1497 notebook_name: this.notebook_name,
1498 // For now, create all sessions with the 'python' kernel, which is the
1499 // default. Later, the user will be able to select kernels. This is
1500 // overridden if KernelManager.kernel_cmd is specified for the server.
1501 kernel_name: 'python'
1498 notebook: this});
1502 notebook: this});
1503
1499 this.session.start($.proxy(this._session_started, this));
1504 this.session.start($.proxy(this._session_started, this));
1500 };
1505 };
1501
1506
1502
1507
1503 /**
1508 /**
1504 * Once a session is started, link the code cells to the kernel and pass the
1509 * Once a session is started, link the code cells to the kernel and pass the
1505 * comm manager to the widget manager
1510 * comm manager to the widget manager
1506 *
1511 *
1507 */
1512 */
1508 Notebook.prototype._session_started = function(){
1513 Notebook.prototype._session_started = function(){
1509 this.kernel = this.session.kernel;
1514 this.kernel = this.session.kernel;
1510 var ncells = this.ncells();
1515 var ncells = this.ncells();
1511 for (var i=0; i<ncells; i++) {
1516 for (var i=0; i<ncells; i++) {
1512 var cell = this.get_cell(i);
1517 var cell = this.get_cell(i);
1513 if (cell instanceof codecell.CodeCell) {
1518 if (cell instanceof codecell.CodeCell) {
1514 cell.set_kernel(this.session.kernel);
1519 cell.set_kernel(this.session.kernel);
1515 }
1520 }
1516 }
1521 }
1517 };
1522 };
1518
1523
1519 /**
1524 /**
1520 * Prompt the user to restart the IPython kernel.
1525 * Prompt the user to restart the IPython kernel.
1521 *
1526 *
1522 * @method restart_kernel
1527 * @method restart_kernel
1523 */
1528 */
1524 Notebook.prototype.restart_kernel = function () {
1529 Notebook.prototype.restart_kernel = function () {
1525 var that = this;
1530 var that = this;
1526 dialog.modal({
1531 dialog.modal({
1527 notebook: this,
1532 notebook: this,
1528 keyboard_manager: this.keyboard_manager,
1533 keyboard_manager: this.keyboard_manager,
1529 title : "Restart kernel or continue running?",
1534 title : "Restart kernel or continue running?",
1530 body : $("<p/>").text(
1535 body : $("<p/>").text(
1531 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1536 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1532 ),
1537 ),
1533 buttons : {
1538 buttons : {
1534 "Continue running" : {},
1539 "Continue running" : {},
1535 "Restart" : {
1540 "Restart" : {
1536 "class" : "btn-danger",
1541 "class" : "btn-danger",
1537 "click" : function() {
1542 "click" : function() {
1538 that.session.restart_kernel();
1543 that.session.restart_kernel();
1539 }
1544 }
1540 }
1545 }
1541 }
1546 }
1542 });
1547 });
1543 };
1548 };
1544
1549
1545 /**
1550 /**
1546 * Execute or render cell outputs and go into command mode.
1551 * Execute or render cell outputs and go into command mode.
1547 *
1552 *
1548 * @method execute_cell
1553 * @method execute_cell
1549 */
1554 */
1550 Notebook.prototype.execute_cell = function () {
1555 Notebook.prototype.execute_cell = function () {
1551 // mode = shift, ctrl, alt
1556 // mode = shift, ctrl, alt
1552 var cell = this.get_selected_cell();
1557 var cell = this.get_selected_cell();
1553 var cell_index = this.find_cell_index(cell);
1558 var cell_index = this.find_cell_index(cell);
1554
1559
1555 cell.execute();
1560 cell.execute();
1556 this.command_mode();
1561 this.command_mode();
1557 this.set_dirty(true);
1562 this.set_dirty(true);
1558 };
1563 };
1559
1564
1560 /**
1565 /**
1561 * Execute or render cell outputs and insert a new cell below.
1566 * Execute or render cell outputs and insert a new cell below.
1562 *
1567 *
1563 * @method execute_cell_and_insert_below
1568 * @method execute_cell_and_insert_below
1564 */
1569 */
1565 Notebook.prototype.execute_cell_and_insert_below = function () {
1570 Notebook.prototype.execute_cell_and_insert_below = function () {
1566 var cell = this.get_selected_cell();
1571 var cell = this.get_selected_cell();
1567 var cell_index = this.find_cell_index(cell);
1572 var cell_index = this.find_cell_index(cell);
1568
1573
1569 cell.execute();
1574 cell.execute();
1570
1575
1571 // If we are at the end always insert a new cell and return
1576 // If we are at the end always insert a new cell and return
1572 if (cell_index === (this.ncells()-1)) {
1577 if (cell_index === (this.ncells()-1)) {
1573 this.command_mode();
1578 this.command_mode();
1574 this.insert_cell_below();
1579 this.insert_cell_below();
1575 this.select(cell_index+1);
1580 this.select(cell_index+1);
1576 this.edit_mode();
1581 this.edit_mode();
1577 this.scroll_to_bottom();
1582 this.scroll_to_bottom();
1578 this.set_dirty(true);
1583 this.set_dirty(true);
1579 return;
1584 return;
1580 }
1585 }
1581
1586
1582 this.command_mode();
1587 this.command_mode();
1583 this.insert_cell_below();
1588 this.insert_cell_below();
1584 this.select(cell_index+1);
1589 this.select(cell_index+1);
1585 this.edit_mode();
1590 this.edit_mode();
1586 this.set_dirty(true);
1591 this.set_dirty(true);
1587 };
1592 };
1588
1593
1589 /**
1594 /**
1590 * Execute or render cell outputs and select the next cell.
1595 * Execute or render cell outputs and select the next cell.
1591 *
1596 *
1592 * @method execute_cell_and_select_below
1597 * @method execute_cell_and_select_below
1593 */
1598 */
1594 Notebook.prototype.execute_cell_and_select_below = function () {
1599 Notebook.prototype.execute_cell_and_select_below = function () {
1595
1600
1596 var cell = this.get_selected_cell();
1601 var cell = this.get_selected_cell();
1597 var cell_index = this.find_cell_index(cell);
1602 var cell_index = this.find_cell_index(cell);
1598
1603
1599 cell.execute();
1604 cell.execute();
1600
1605
1601 // If we are at the end always insert a new cell and return
1606 // If we are at the end always insert a new cell and return
1602 if (cell_index === (this.ncells()-1)) {
1607 if (cell_index === (this.ncells()-1)) {
1603 this.command_mode();
1608 this.command_mode();
1604 this.insert_cell_below();
1609 this.insert_cell_below();
1605 this.select(cell_index+1);
1610 this.select(cell_index+1);
1606 this.edit_mode();
1611 this.edit_mode();
1607 this.scroll_to_bottom();
1612 this.scroll_to_bottom();
1608 this.set_dirty(true);
1613 this.set_dirty(true);
1609 return;
1614 return;
1610 }
1615 }
1611
1616
1612 this.command_mode();
1617 this.command_mode();
1613 this.select(cell_index+1);
1618 this.select(cell_index+1);
1614 this.focus_cell();
1619 this.focus_cell();
1615 this.set_dirty(true);
1620 this.set_dirty(true);
1616 };
1621 };
1617
1622
1618 /**
1623 /**
1619 * Execute all cells below the selected cell.
1624 * Execute all cells below the selected cell.
1620 *
1625 *
1621 * @method execute_cells_below
1626 * @method execute_cells_below
1622 */
1627 */
1623 Notebook.prototype.execute_cells_below = function () {
1628 Notebook.prototype.execute_cells_below = function () {
1624 this.execute_cell_range(this.get_selected_index(), this.ncells());
1629 this.execute_cell_range(this.get_selected_index(), this.ncells());
1625 this.scroll_to_bottom();
1630 this.scroll_to_bottom();
1626 };
1631 };
1627
1632
1628 /**
1633 /**
1629 * Execute all cells above the selected cell.
1634 * Execute all cells above the selected cell.
1630 *
1635 *
1631 * @method execute_cells_above
1636 * @method execute_cells_above
1632 */
1637 */
1633 Notebook.prototype.execute_cells_above = function () {
1638 Notebook.prototype.execute_cells_above = function () {
1634 this.execute_cell_range(0, this.get_selected_index());
1639 this.execute_cell_range(0, this.get_selected_index());
1635 };
1640 };
1636
1641
1637 /**
1642 /**
1638 * Execute all cells.
1643 * Execute all cells.
1639 *
1644 *
1640 * @method execute_all_cells
1645 * @method execute_all_cells
1641 */
1646 */
1642 Notebook.prototype.execute_all_cells = function () {
1647 Notebook.prototype.execute_all_cells = function () {
1643 this.execute_cell_range(0, this.ncells());
1648 this.execute_cell_range(0, this.ncells());
1644 this.scroll_to_bottom();
1649 this.scroll_to_bottom();
1645 };
1650 };
1646
1651
1647 /**
1652 /**
1648 * Execute a contiguous range of cells.
1653 * Execute a contiguous range of cells.
1649 *
1654 *
1650 * @method execute_cell_range
1655 * @method execute_cell_range
1651 * @param {Number} start Index of the first cell to execute (inclusive)
1656 * @param {Number} start Index of the first cell to execute (inclusive)
1652 * @param {Number} end Index of the last cell to execute (exclusive)
1657 * @param {Number} end Index of the last cell to execute (exclusive)
1653 */
1658 */
1654 Notebook.prototype.execute_cell_range = function (start, end) {
1659 Notebook.prototype.execute_cell_range = function (start, end) {
1655 this.command_mode();
1660 this.command_mode();
1656 for (var i=start; i<end; i++) {
1661 for (var i=start; i<end; i++) {
1657 this.select(i);
1662 this.select(i);
1658 this.execute_cell();
1663 this.execute_cell();
1659 }
1664 }
1660 };
1665 };
1661
1666
1662 // Persistance and loading
1667 // Persistance and loading
1663
1668
1664 /**
1669 /**
1665 * Getter method for this notebook's name.
1670 * Getter method for this notebook's name.
1666 *
1671 *
1667 * @method get_notebook_name
1672 * @method get_notebook_name
1668 * @return {String} This notebook's name (excluding file extension)
1673 * @return {String} This notebook's name (excluding file extension)
1669 */
1674 */
1670 Notebook.prototype.get_notebook_name = function () {
1675 Notebook.prototype.get_notebook_name = function () {
1671 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1676 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1672 return nbname;
1677 return nbname;
1673 };
1678 };
1674
1679
1675 /**
1680 /**
1676 * Setter method for this notebook's name.
1681 * Setter method for this notebook's name.
1677 *
1682 *
1678 * @method set_notebook_name
1683 * @method set_notebook_name
1679 * @param {String} name A new name for this notebook
1684 * @param {String} name A new name for this notebook
1680 */
1685 */
1681 Notebook.prototype.set_notebook_name = function (name) {
1686 Notebook.prototype.set_notebook_name = function (name) {
1682 this.notebook_name = name;
1687 this.notebook_name = name;
1683 };
1688 };
1684
1689
1685 /**
1690 /**
1686 * Check that a notebook's name is valid.
1691 * Check that a notebook's name is valid.
1687 *
1692 *
1688 * @method test_notebook_name
1693 * @method test_notebook_name
1689 * @param {String} nbname A name for this notebook
1694 * @param {String} nbname A name for this notebook
1690 * @return {Boolean} True if the name is valid, false if invalid
1695 * @return {Boolean} True if the name is valid, false if invalid
1691 */
1696 */
1692 Notebook.prototype.test_notebook_name = function (nbname) {
1697 Notebook.prototype.test_notebook_name = function (nbname) {
1693 nbname = nbname || '';
1698 nbname = nbname || '';
1694 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1699 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1695 return true;
1700 return true;
1696 } else {
1701 } else {
1697 return false;
1702 return false;
1698 }
1703 }
1699 };
1704 };
1700
1705
1701 /**
1706 /**
1702 * Load a notebook from JSON (.ipynb).
1707 * Load a notebook from JSON (.ipynb).
1703 *
1708 *
1704 * This currently handles one worksheet: others are deleted.
1709 * This currently handles one worksheet: others are deleted.
1705 *
1710 *
1706 * @method fromJSON
1711 * @method fromJSON
1707 * @param {Object} data JSON representation of a notebook
1712 * @param {Object} data JSON representation of a notebook
1708 */
1713 */
1709 Notebook.prototype.fromJSON = function (data) {
1714 Notebook.prototype.fromJSON = function (data) {
1710 var content = data.content;
1715 var content = data.content;
1711 var ncells = this.ncells();
1716 var ncells = this.ncells();
1712 var i;
1717 var i;
1713 for (i=0; i<ncells; i++) {
1718 for (i=0; i<ncells; i++) {
1714 // Always delete cell 0 as they get renumbered as they are deleted.
1719 // Always delete cell 0 as they get renumbered as they are deleted.
1715 this.delete_cell(0);
1720 this.delete_cell(0);
1716 }
1721 }
1717 // Save the metadata and name.
1722 // Save the metadata and name.
1718 this.metadata = content.metadata;
1723 this.metadata = content.metadata;
1719 this.notebook_name = data.name;
1724 this.notebook_name = data.name;
1720 var trusted = true;
1725 var trusted = true;
1721 // Only handle 1 worksheet for now.
1726 // Only handle 1 worksheet for now.
1722 var worksheet = content.worksheets[0];
1727 var worksheet = content.worksheets[0];
1723 if (worksheet !== undefined) {
1728 if (worksheet !== undefined) {
1724 if (worksheet.metadata) {
1729 if (worksheet.metadata) {
1725 this.worksheet_metadata = worksheet.metadata;
1730 this.worksheet_metadata = worksheet.metadata;
1726 }
1731 }
1727 var new_cells = worksheet.cells;
1732 var new_cells = worksheet.cells;
1728 ncells = new_cells.length;
1733 ncells = new_cells.length;
1729 var cell_data = null;
1734 var cell_data = null;
1730 var new_cell = null;
1735 var new_cell = null;
1731 for (i=0; i<ncells; i++) {
1736 for (i=0; i<ncells; i++) {
1732 cell_data = new_cells[i];
1737 cell_data = new_cells[i];
1733 // VERSIONHACK: plaintext -> raw
1738 // VERSIONHACK: plaintext -> raw
1734 // handle never-released plaintext name for raw cells
1739 // handle never-released plaintext name for raw cells
1735 if (cell_data.cell_type === 'plaintext'){
1740 if (cell_data.cell_type === 'plaintext'){
1736 cell_data.cell_type = 'raw';
1741 cell_data.cell_type = 'raw';
1737 }
1742 }
1738
1743
1739 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1744 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1740 new_cell.fromJSON(cell_data);
1745 new_cell.fromJSON(cell_data);
1741 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1746 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1742 trusted = false;
1747 trusted = false;
1743 }
1748 }
1744 }
1749 }
1745 }
1750 }
1746 if (trusted != this.trusted) {
1751 if (trusted != this.trusted) {
1747 this.trusted = trusted;
1752 this.trusted = trusted;
1748 this.events.trigger("trust_changed.Notebook", trusted);
1753 this.events.trigger("trust_changed.Notebook", trusted);
1749 }
1754 }
1750 if (content.worksheets.length > 1) {
1755 if (content.worksheets.length > 1) {
1751 dialog.modal({
1756 dialog.modal({
1752 notebook: this,
1757 notebook: this,
1753 keyboard_manager: this.keyboard_manager,
1758 keyboard_manager: this.keyboard_manager,
1754 title : "Multiple worksheets",
1759 title : "Multiple worksheets",
1755 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1760 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1756 "but this version of IPython can only handle the first. " +
1761 "but this version of IPython can only handle the first. " +
1757 "If you save this notebook, worksheets after the first will be lost.",
1762 "If you save this notebook, worksheets after the first will be lost.",
1758 buttons : {
1763 buttons : {
1759 OK : {
1764 OK : {
1760 class : "btn-danger"
1765 class : "btn-danger"
1761 }
1766 }
1762 }
1767 }
1763 });
1768 });
1764 }
1769 }
1765 };
1770 };
1766
1771
1767 /**
1772 /**
1768 * Dump this notebook into a JSON-friendly object.
1773 * Dump this notebook into a JSON-friendly object.
1769 *
1774 *
1770 * @method toJSON
1775 * @method toJSON
1771 * @return {Object} A JSON-friendly representation of this notebook.
1776 * @return {Object} A JSON-friendly representation of this notebook.
1772 */
1777 */
1773 Notebook.prototype.toJSON = function () {
1778 Notebook.prototype.toJSON = function () {
1774 var cells = this.get_cells();
1779 var cells = this.get_cells();
1775 var ncells = cells.length;
1780 var ncells = cells.length;
1776 var cell_array = new Array(ncells);
1781 var cell_array = new Array(ncells);
1777 var trusted = true;
1782 var trusted = true;
1778 for (var i=0; i<ncells; i++) {
1783 for (var i=0; i<ncells; i++) {
1779 var cell = cells[i];
1784 var cell = cells[i];
1780 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1785 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1781 trusted = false;
1786 trusted = false;
1782 }
1787 }
1783 cell_array[i] = cell.toJSON();
1788 cell_array[i] = cell.toJSON();
1784 }
1789 }
1785 var data = {
1790 var data = {
1786 // Only handle 1 worksheet for now.
1791 // Only handle 1 worksheet for now.
1787 worksheets : [{
1792 worksheets : [{
1788 cells: cell_array,
1793 cells: cell_array,
1789 metadata: this.worksheet_metadata
1794 metadata: this.worksheet_metadata
1790 }],
1795 }],
1791 metadata : this.metadata
1796 metadata : this.metadata
1792 };
1797 };
1793 if (trusted != this.trusted) {
1798 if (trusted != this.trusted) {
1794 this.trusted = trusted;
1799 this.trusted = trusted;
1795 this.events.trigger("trust_changed.Notebook", trusted);
1800 this.events.trigger("trust_changed.Notebook", trusted);
1796 }
1801 }
1797 return data;
1802 return data;
1798 };
1803 };
1799
1804
1800 /**
1805 /**
1801 * Start an autosave timer, for periodically saving the notebook.
1806 * Start an autosave timer, for periodically saving the notebook.
1802 *
1807 *
1803 * @method set_autosave_interval
1808 * @method set_autosave_interval
1804 * @param {Integer} interval the autosave interval in milliseconds
1809 * @param {Integer} interval the autosave interval in milliseconds
1805 */
1810 */
1806 Notebook.prototype.set_autosave_interval = function (interval) {
1811 Notebook.prototype.set_autosave_interval = function (interval) {
1807 var that = this;
1812 var that = this;
1808 // clear previous interval, so we don't get simultaneous timers
1813 // clear previous interval, so we don't get simultaneous timers
1809 if (this.autosave_timer) {
1814 if (this.autosave_timer) {
1810 clearInterval(this.autosave_timer);
1815 clearInterval(this.autosave_timer);
1811 }
1816 }
1812
1817
1813 this.autosave_interval = this.minimum_autosave_interval = interval;
1818 this.autosave_interval = this.minimum_autosave_interval = interval;
1814 if (interval) {
1819 if (interval) {
1815 this.autosave_timer = setInterval(function() {
1820 this.autosave_timer = setInterval(function() {
1816 if (that.dirty) {
1821 if (that.dirty) {
1817 that.save_notebook();
1822 that.save_notebook();
1818 }
1823 }
1819 }, interval);
1824 }, interval);
1820 this.events.trigger("autosave_enabled.Notebook", interval);
1825 this.events.trigger("autosave_enabled.Notebook", interval);
1821 } else {
1826 } else {
1822 this.autosave_timer = null;
1827 this.autosave_timer = null;
1823 this.events.trigger("autosave_disabled.Notebook");
1828 this.events.trigger("autosave_disabled.Notebook");
1824 }
1829 }
1825 };
1830 };
1826
1831
1827 /**
1832 /**
1828 * Save this notebook on the server. This becomes a notebook instance's
1833 * Save this notebook on the server. This becomes a notebook instance's
1829 * .save_notebook method *after* the entire notebook has been loaded.
1834 * .save_notebook method *after* the entire notebook has been loaded.
1830 *
1835 *
1831 * @method save_notebook
1836 * @method save_notebook
1832 */
1837 */
1833 Notebook.prototype.save_notebook = function (extra_settings) {
1838 Notebook.prototype.save_notebook = function (extra_settings) {
1834 // Create a JSON model to be sent to the server.
1839 // Create a JSON model to be sent to the server.
1835 var model = {};
1840 var model = {};
1836 model.name = this.notebook_name;
1841 model.name = this.notebook_name;
1837 model.path = this.notebook_path;
1842 model.path = this.notebook_path;
1838 model.content = this.toJSON();
1843 model.content = this.toJSON();
1839 model.content.nbformat = this.nbformat;
1844 model.content.nbformat = this.nbformat;
1840 model.content.nbformat_minor = this.nbformat_minor;
1845 model.content.nbformat_minor = this.nbformat_minor;
1841 // time the ajax call for autosave tuning purposes.
1846 // time the ajax call for autosave tuning purposes.
1842 var start = new Date().getTime();
1847 var start = new Date().getTime();
1843 // We do the call with settings so we can set cache to false.
1848 // We do the call with settings so we can set cache to false.
1844 var settings = {
1849 var settings = {
1845 processData : false,
1850 processData : false,
1846 cache : false,
1851 cache : false,
1847 type : "PUT",
1852 type : "PUT",
1848 data : JSON.stringify(model),
1853 data : JSON.stringify(model),
1849 headers : {'Content-Type': 'application/json'},
1854 headers : {'Content-Type': 'application/json'},
1850 success : $.proxy(this.save_notebook_success, this, start),
1855 success : $.proxy(this.save_notebook_success, this, start),
1851 error : $.proxy(this.save_notebook_error, this)
1856 error : $.proxy(this.save_notebook_error, this)
1852 };
1857 };
1853 if (extra_settings) {
1858 if (extra_settings) {
1854 for (var key in extra_settings) {
1859 for (var key in extra_settings) {
1855 settings[key] = extra_settings[key];
1860 settings[key] = extra_settings[key];
1856 }
1861 }
1857 }
1862 }
1858 this.events.trigger('notebook_saving.Notebook');
1863 this.events.trigger('notebook_saving.Notebook');
1859 var url = utils.url_join_encode(
1864 var url = utils.url_join_encode(
1860 this.base_url,
1865 this.base_url,
1861 'api/notebooks',
1866 'api/notebooks',
1862 this.notebook_path,
1867 this.notebook_path,
1863 this.notebook_name
1868 this.notebook_name
1864 );
1869 );
1865 $.ajax(url, settings);
1870 $.ajax(url, settings);
1866 };
1871 };
1867
1872
1868 /**
1873 /**
1869 * Success callback for saving a notebook.
1874 * Success callback for saving a notebook.
1870 *
1875 *
1871 * @method save_notebook_success
1876 * @method save_notebook_success
1872 * @param {Integer} start the time when the save request started
1877 * @param {Integer} start the time when the save request started
1873 * @param {Object} data JSON representation of a notebook
1878 * @param {Object} data JSON representation of a notebook
1874 * @param {String} status Description of response status
1879 * @param {String} status Description of response status
1875 * @param {jqXHR} xhr jQuery Ajax object
1880 * @param {jqXHR} xhr jQuery Ajax object
1876 */
1881 */
1877 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1882 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1878 this.set_dirty(false);
1883 this.set_dirty(false);
1879 this.events.trigger('notebook_saved.Notebook');
1884 this.events.trigger('notebook_saved.Notebook');
1880 this._update_autosave_interval(start);
1885 this._update_autosave_interval(start);
1881 if (this._checkpoint_after_save) {
1886 if (this._checkpoint_after_save) {
1882 this.create_checkpoint();
1887 this.create_checkpoint();
1883 this._checkpoint_after_save = false;
1888 this._checkpoint_after_save = false;
1884 }
1889 }
1885 };
1890 };
1886
1891
1887 /**
1892 /**
1888 * update the autosave interval based on how long the last save took
1893 * update the autosave interval based on how long the last save took
1889 *
1894 *
1890 * @method _update_autosave_interval
1895 * @method _update_autosave_interval
1891 * @param {Integer} timestamp when the save request started
1896 * @param {Integer} timestamp when the save request started
1892 */
1897 */
1893 Notebook.prototype._update_autosave_interval = function (start) {
1898 Notebook.prototype._update_autosave_interval = function (start) {
1894 var duration = (new Date().getTime() - start);
1899 var duration = (new Date().getTime() - start);
1895 if (this.autosave_interval) {
1900 if (this.autosave_interval) {
1896 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1901 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1897 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1902 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1898 // round to 10 seconds, otherwise we will be setting a new interval too often
1903 // round to 10 seconds, otherwise we will be setting a new interval too often
1899 interval = 10000 * Math.round(interval / 10000);
1904 interval = 10000 * Math.round(interval / 10000);
1900 // set new interval, if it's changed
1905 // set new interval, if it's changed
1901 if (interval != this.autosave_interval) {
1906 if (interval != this.autosave_interval) {
1902 this.set_autosave_interval(interval);
1907 this.set_autosave_interval(interval);
1903 }
1908 }
1904 }
1909 }
1905 };
1910 };
1906
1911
1907 /**
1912 /**
1908 * Failure callback for saving a notebook.
1913 * Failure callback for saving a notebook.
1909 *
1914 *
1910 * @method save_notebook_error
1915 * @method save_notebook_error
1911 * @param {jqXHR} xhr jQuery Ajax object
1916 * @param {jqXHR} xhr jQuery Ajax object
1912 * @param {String} status Description of response status
1917 * @param {String} status Description of response status
1913 * @param {String} error HTTP error message
1918 * @param {String} error HTTP error message
1914 */
1919 */
1915 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1920 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1916 this.events.trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1921 this.events.trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1917 };
1922 };
1918
1923
1919 /**
1924 /**
1920 * Explicitly trust the output of this notebook.
1925 * Explicitly trust the output of this notebook.
1921 *
1926 *
1922 * @method trust_notebook
1927 * @method trust_notebook
1923 */
1928 */
1924 Notebook.prototype.trust_notebook = function (extra_settings) {
1929 Notebook.prototype.trust_notebook = function (extra_settings) {
1925 var body = $("<div>").append($("<p>")
1930 var body = $("<div>").append($("<p>")
1926 .text("A trusted IPython notebook may execute hidden malicious code ")
1931 .text("A trusted IPython notebook may execute hidden malicious code ")
1927 .append($("<strong>")
1932 .append($("<strong>")
1928 .append(
1933 .append(
1929 $("<em>").text("when you open it")
1934 $("<em>").text("when you open it")
1930 )
1935 )
1931 ).append(".").append(
1936 ).append(".").append(
1932 " Selecting trust will immediately reload this notebook in a trusted state."
1937 " Selecting trust will immediately reload this notebook in a trusted state."
1933 ).append(
1938 ).append(
1934 " For more information, see the "
1939 " For more information, see the "
1935 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
1940 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
1936 .text("IPython security documentation")
1941 .text("IPython security documentation")
1937 ).append(".")
1942 ).append(".")
1938 );
1943 );
1939
1944
1940 var nb = this;
1945 var nb = this;
1941 dialog.modal({
1946 dialog.modal({
1942 notebook: this,
1947 notebook: this,
1943 keyboard_manager: this.keyboard_manager,
1948 keyboard_manager: this.keyboard_manager,
1944 title: "Trust this notebook?",
1949 title: "Trust this notebook?",
1945 body: body,
1950 body: body,
1946
1951
1947 buttons: {
1952 buttons: {
1948 Cancel : {},
1953 Cancel : {},
1949 Trust : {
1954 Trust : {
1950 class : "btn-danger",
1955 class : "btn-danger",
1951 click : function () {
1956 click : function () {
1952 var cells = nb.get_cells();
1957 var cells = nb.get_cells();
1953 for (var i = 0; i < cells.length; i++) {
1958 for (var i = 0; i < cells.length; i++) {
1954 var cell = cells[i];
1959 var cell = cells[i];
1955 if (cell.cell_type == 'code') {
1960 if (cell.cell_type == 'code') {
1956 cell.output_area.trusted = true;
1961 cell.output_area.trusted = true;
1957 }
1962 }
1958 }
1963 }
1959 this.events.on('notebook_saved.Notebook', function () {
1964 this.events.on('notebook_saved.Notebook', function () {
1960 window.location.reload();
1965 window.location.reload();
1961 });
1966 });
1962 nb.save_notebook();
1967 nb.save_notebook();
1963 }
1968 }
1964 }
1969 }
1965 }
1970 }
1966 });
1971 });
1967 };
1972 };
1968
1973
1969 Notebook.prototype.new_notebook = function(){
1974 Notebook.prototype.new_notebook = function(){
1970 var path = this.notebook_path;
1975 var path = this.notebook_path;
1971 var base_url = this.base_url;
1976 var base_url = this.base_url;
1972 var settings = {
1977 var settings = {
1973 processData : false,
1978 processData : false,
1974 cache : false,
1979 cache : false,
1975 type : "POST",
1980 type : "POST",
1976 dataType : "json",
1981 dataType : "json",
1977 async : false,
1982 async : false,
1978 success : function (data, status, xhr){
1983 success : function (data, status, xhr){
1979 var notebook_name = data.name;
1984 var notebook_name = data.name;
1980 window.open(
1985 window.open(
1981 utils.url_join_encode(
1986 utils.url_join_encode(
1982 base_url,
1987 base_url,
1983 'notebooks',
1988 'notebooks',
1984 path,
1989 path,
1985 notebook_name
1990 notebook_name
1986 ),
1991 ),
1987 '_blank'
1992 '_blank'
1988 );
1993 );
1989 },
1994 },
1990 error : utils.log_ajax_error,
1995 error : utils.log_ajax_error,
1991 };
1996 };
1992 var url = utils.url_join_encode(
1997 var url = utils.url_join_encode(
1993 base_url,
1998 base_url,
1994 'api/notebooks',
1999 'api/notebooks',
1995 path
2000 path
1996 );
2001 );
1997 $.ajax(url,settings);
2002 $.ajax(url,settings);
1998 };
2003 };
1999
2004
2000
2005
2001 Notebook.prototype.copy_notebook = function(){
2006 Notebook.prototype.copy_notebook = function(){
2002 var path = this.notebook_path;
2007 var path = this.notebook_path;
2003 var base_url = this.base_url;
2008 var base_url = this.base_url;
2004 var settings = {
2009 var settings = {
2005 processData : false,
2010 processData : false,
2006 cache : false,
2011 cache : false,
2007 type : "POST",
2012 type : "POST",
2008 dataType : "json",
2013 dataType : "json",
2009 data : JSON.stringify({copy_from : this.notebook_name}),
2014 data : JSON.stringify({copy_from : this.notebook_name}),
2010 async : false,
2015 async : false,
2011 success : function (data, status, xhr) {
2016 success : function (data, status, xhr) {
2012 window.open(utils.url_join_encode(
2017 window.open(utils.url_join_encode(
2013 base_url,
2018 base_url,
2014 'notebooks',
2019 'notebooks',
2015 data.path,
2020 data.path,
2016 data.name
2021 data.name
2017 ), '_blank');
2022 ), '_blank');
2018 },
2023 },
2019 error : utils.log_ajax_error,
2024 error : utils.log_ajax_error,
2020 };
2025 };
2021 var url = utils.url_join_encode(
2026 var url = utils.url_join_encode(
2022 base_url,
2027 base_url,
2023 'api/notebooks',
2028 'api/notebooks',
2024 path
2029 path
2025 );
2030 );
2026 $.ajax(url,settings);
2031 $.ajax(url,settings);
2027 };
2032 };
2028
2033
2029 Notebook.prototype.rename = function (nbname) {
2034 Notebook.prototype.rename = function (nbname) {
2030 var that = this;
2035 var that = this;
2031 if (!nbname.match(/\.ipynb$/)) {
2036 if (!nbname.match(/\.ipynb$/)) {
2032 nbname = nbname + ".ipynb";
2037 nbname = nbname + ".ipynb";
2033 }
2038 }
2034 var data = {name: nbname};
2039 var data = {name: nbname};
2035 var settings = {
2040 var settings = {
2036 processData : false,
2041 processData : false,
2037 cache : false,
2042 cache : false,
2038 type : "PATCH",
2043 type : "PATCH",
2039 data : JSON.stringify(data),
2044 data : JSON.stringify(data),
2040 dataType: "json",
2045 dataType: "json",
2041 headers : {'Content-Type': 'application/json'},
2046 headers : {'Content-Type': 'application/json'},
2042 success : $.proxy(that.rename_success, this),
2047 success : $.proxy(that.rename_success, this),
2043 error : $.proxy(that.rename_error, this)
2048 error : $.proxy(that.rename_error, this)
2044 };
2049 };
2045 this.events.trigger('rename_notebook.Notebook', data);
2050 this.events.trigger('rename_notebook.Notebook', data);
2046 var url = utils.url_join_encode(
2051 var url = utils.url_join_encode(
2047 this.base_url,
2052 this.base_url,
2048 'api/notebooks',
2053 'api/notebooks',
2049 this.notebook_path,
2054 this.notebook_path,
2050 this.notebook_name
2055 this.notebook_name
2051 );
2056 );
2052 $.ajax(url, settings);
2057 $.ajax(url, settings);
2053 };
2058 };
2054
2059
2055 Notebook.prototype.delete = function () {
2060 Notebook.prototype.delete = function () {
2056 var that = this;
2061 var that = this;
2057 var settings = {
2062 var settings = {
2058 processData : false,
2063 processData : false,
2059 cache : false,
2064 cache : false,
2060 type : "DELETE",
2065 type : "DELETE",
2061 dataType: "json",
2066 dataType: "json",
2062 error : utils.log_ajax_error,
2067 error : utils.log_ajax_error,
2063 };
2068 };
2064 var url = utils.url_join_encode(
2069 var url = utils.url_join_encode(
2065 this.base_url,
2070 this.base_url,
2066 'api/notebooks',
2071 'api/notebooks',
2067 this.notebook_path,
2072 this.notebook_path,
2068 this.notebook_name
2073 this.notebook_name
2069 );
2074 );
2070 $.ajax(url, settings);
2075 $.ajax(url, settings);
2071 };
2076 };
2072
2077
2073
2078
2074 Notebook.prototype.rename_success = function (json, status, xhr) {
2079 Notebook.prototype.rename_success = function (json, status, xhr) {
2075 var name = this.notebook_name = json.name;
2080 var name = this.notebook_name = json.name;
2076 var path = json.path;
2081 var path = json.path;
2077 this.session.rename_notebook(name, path);
2082 this.session.rename_notebook(name, path);
2078 this.events.trigger('notebook_renamed.Notebook', json);
2083 this.events.trigger('notebook_renamed.Notebook', json);
2079 };
2084 };
2080
2085
2081 Notebook.prototype.rename_error = function (xhr, status, error) {
2086 Notebook.prototype.rename_error = function (xhr, status, error) {
2082 var that = this;
2087 var that = this;
2083 var dialog_body = $('<div/>').append(
2088 var dialog_body = $('<div/>').append(
2084 $("<p/>").addClass("rename-message")
2089 $("<p/>").addClass("rename-message")
2085 .text('This notebook name already exists.')
2090 .text('This notebook name already exists.')
2086 );
2091 );
2087 this.events.trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
2092 this.events.trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
2088 dialog.modal({
2093 dialog.modal({
2089 notebook: this,
2094 notebook: this,
2090 keyboard_manager: this.keyboard_manager,
2095 keyboard_manager: this.keyboard_manager,
2091 title: "Notebook Rename Error!",
2096 title: "Notebook Rename Error!",
2092 body: dialog_body,
2097 body: dialog_body,
2093 buttons : {
2098 buttons : {
2094 "Cancel": {},
2099 "Cancel": {},
2095 "OK": {
2100 "OK": {
2096 class: "btn-primary",
2101 class: "btn-primary",
2097 click: function () {
2102 click: function () {
2098 this.save_widget.rename_notebook({notebook:that});
2103 this.save_widget.rename_notebook({notebook:that});
2099 }}
2104 }}
2100 },
2105 },
2101 open : function (event, ui) {
2106 open : function (event, ui) {
2102 var that = $(this);
2107 var that = $(this);
2103 // Upon ENTER, click the OK button.
2108 // Upon ENTER, click the OK button.
2104 that.find('input[type="text"]').keydown(function (event, ui) {
2109 that.find('input[type="text"]').keydown(function (event, ui) {
2105 if (event.which === this.keyboard.keycodes.enter) {
2110 if (event.which === this.keyboard.keycodes.enter) {
2106 that.find('.btn-primary').first().click();
2111 that.find('.btn-primary').first().click();
2107 }
2112 }
2108 });
2113 });
2109 that.find('input[type="text"]').focus();
2114 that.find('input[type="text"]').focus();
2110 }
2115 }
2111 });
2116 });
2112 };
2117 };
2113
2118
2114 /**
2119 /**
2115 * Request a notebook's data from the server.
2120 * Request a notebook's data from the server.
2116 *
2121 *
2117 * @method load_notebook
2122 * @method load_notebook
2118 * @param {String} notebook_name and path A notebook to load
2123 * @param {String} notebook_name and path A notebook to load
2119 */
2124 */
2120 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
2125 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
2121 var that = this;
2126 var that = this;
2122 this.notebook_name = notebook_name;
2127 this.notebook_name = notebook_name;
2123 this.notebook_path = notebook_path;
2128 this.notebook_path = notebook_path;
2124 // We do the call with settings so we can set cache to false.
2129 // We do the call with settings so we can set cache to false.
2125 var settings = {
2130 var settings = {
2126 processData : false,
2131 processData : false,
2127 cache : false,
2132 cache : false,
2128 type : "GET",
2133 type : "GET",
2129 dataType : "json",
2134 dataType : "json",
2130 success : $.proxy(this.load_notebook_success,this),
2135 success : $.proxy(this.load_notebook_success,this),
2131 error : $.proxy(this.load_notebook_error,this),
2136 error : $.proxy(this.load_notebook_error,this),
2132 };
2137 };
2133 this.events.trigger('notebook_loading.Notebook');
2138 this.events.trigger('notebook_loading.Notebook');
2134 var url = utils.url_join_encode(
2139 var url = utils.url_join_encode(
2135 this.base_url,
2140 this.base_url,
2136 'api/notebooks',
2141 'api/notebooks',
2137 this.notebook_path,
2142 this.notebook_path,
2138 this.notebook_name
2143 this.notebook_name
2139 );
2144 );
2140 $.ajax(url, settings);
2145 $.ajax(url, settings);
2141 };
2146 };
2142
2147
2143 /**
2148 /**
2144 * Success callback for loading a notebook from the server.
2149 * Success callback for loading a notebook from the server.
2145 *
2150 *
2146 * Load notebook data from the JSON response.
2151 * Load notebook data from the JSON response.
2147 *
2152 *
2148 * @method load_notebook_success
2153 * @method load_notebook_success
2149 * @param {Object} data JSON representation of a notebook
2154 * @param {Object} data JSON representation of a notebook
2150 * @param {String} status Description of response status
2155 * @param {String} status Description of response status
2151 * @param {jqXHR} xhr jQuery Ajax object
2156 * @param {jqXHR} xhr jQuery Ajax object
2152 */
2157 */
2153 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
2158 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
2154 this.fromJSON(data);
2159 this.fromJSON(data);
2155 if (this.ncells() === 0) {
2160 if (this.ncells() === 0) {
2156 this.insert_cell_below('code');
2161 this.insert_cell_below('code');
2157 this.edit_mode(0);
2162 this.edit_mode(0);
2158 } else {
2163 } else {
2159 this.select(0);
2164 this.select(0);
2160 this.handle_command_mode(this.get_cell(0));
2165 this.handle_command_mode(this.get_cell(0));
2161 }
2166 }
2162 this.set_dirty(false);
2167 this.set_dirty(false);
2163 this.scroll_to_top();
2168 this.scroll_to_top();
2164 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
2169 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
2165 var msg = "This notebook has been converted from an older " +
2170 var msg = "This notebook has been converted from an older " +
2166 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
2171 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
2167 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
2172 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
2168 "newer notebook format will be used and older versions of IPython " +
2173 "newer notebook format will be used and older versions of IPython " +
2169 "may not be able to read it. To keep the older version, close the " +
2174 "may not be able to read it. To keep the older version, close the " +
2170 "notebook without saving it.";
2175 "notebook without saving it.";
2171 dialog.modal({
2176 dialog.modal({
2172 notebook: this,
2177 notebook: this,
2173 keyboard_manager: this.keyboard_manager,
2178 keyboard_manager: this.keyboard_manager,
2174 title : "Notebook converted",
2179 title : "Notebook converted",
2175 body : msg,
2180 body : msg,
2176 buttons : {
2181 buttons : {
2177 OK : {
2182 OK : {
2178 class : "btn-primary"
2183 class : "btn-primary"
2179 }
2184 }
2180 }
2185 }
2181 });
2186 });
2182 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
2187 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
2183 var that = this;
2188 var that = this;
2184 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
2189 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
2185 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
2190 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
2186 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
2191 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
2187 this_vs + ". You can still work with this notebook, but some features " +
2192 this_vs + ". You can still work with this notebook, but some features " +
2188 "introduced in later notebook versions may not be available.";
2193 "introduced in later notebook versions may not be available.";
2189
2194
2190 dialog.modal({
2195 dialog.modal({
2191 notebook: this,
2196 notebook: this,
2192 keyboard_manager: this.keyboard_manager,
2197 keyboard_manager: this.keyboard_manager,
2193 title : "Newer Notebook",
2198 title : "Newer Notebook",
2194 body : msg,
2199 body : msg,
2195 buttons : {
2200 buttons : {
2196 OK : {
2201 OK : {
2197 class : "btn-danger"
2202 class : "btn-danger"
2198 }
2203 }
2199 }
2204 }
2200 });
2205 });
2201
2206
2202 }
2207 }
2203
2208
2204 // Create the session after the notebook is completely loaded to prevent
2209 // Create the session after the notebook is completely loaded to prevent
2205 // code execution upon loading, which is a security risk.
2210 // code execution upon loading, which is a security risk.
2206 if (this.session === null) {
2211 if (this.session === null) {
2207 this.start_session();
2212 this.start_session();
2208 }
2213 }
2209 // load our checkpoint list
2214 // load our checkpoint list
2210 this.list_checkpoints();
2215 this.list_checkpoints();
2211
2216
2212 // load toolbar state
2217 // load toolbar state
2213 if (this.metadata.celltoolbar) {
2218 if (this.metadata.celltoolbar) {
2214 celltoolbar.CellToolbar.global_show();
2219 celltoolbar.CellToolbar.global_show();
2215 celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar, this.events);
2220 celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar, this.events);
2216 } else {
2221 } else {
2217 celltoolbar.CellToolbar.global_hide();
2222 celltoolbar.CellToolbar.global_hide();
2218 }
2223 }
2219
2224
2220 // now that we're fully loaded, it is safe to restore save functionality
2225 // now that we're fully loaded, it is safe to restore save functionality
2221 delete(this.save_notebook);
2226 delete(this.save_notebook);
2222 this.events.trigger('notebook_loaded.Notebook');
2227 this.events.trigger('notebook_loaded.Notebook');
2223 };
2228 };
2224
2229
2225 /**
2230 /**
2226 * Failure callback for loading a notebook from the server.
2231 * Failure callback for loading a notebook from the server.
2227 *
2232 *
2228 * @method load_notebook_error
2233 * @method load_notebook_error
2229 * @param {jqXHR} xhr jQuery Ajax object
2234 * @param {jqXHR} xhr jQuery Ajax object
2230 * @param {String} status Description of response status
2235 * @param {String} status Description of response status
2231 * @param {String} error HTTP error message
2236 * @param {String} error HTTP error message
2232 */
2237 */
2233 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2238 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2234 this.events.trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2239 this.events.trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2235 var msg;
2240 var msg;
2236 if (xhr.status === 400) {
2241 if (xhr.status === 400) {
2237 msg = error;
2242 msg = error;
2238 } else if (xhr.status === 500) {
2243 } else if (xhr.status === 500) {
2239 msg = "An unknown error occurred while loading this notebook. " +
2244 msg = "An unknown error occurred while loading this notebook. " +
2240 "This version can load notebook formats " +
2245 "This version can load notebook formats " +
2241 "v" + this.nbformat + " or earlier.";
2246 "v" + this.nbformat + " or earlier.";
2242 }
2247 }
2243 dialog.modal({
2248 dialog.modal({
2244 notebook: this,
2249 notebook: this,
2245 keyboard_manager: this.keyboard_manager,
2250 keyboard_manager: this.keyboard_manager,
2246 title: "Error loading notebook",
2251 title: "Error loading notebook",
2247 body : msg,
2252 body : msg,
2248 buttons : {
2253 buttons : {
2249 "OK": {}
2254 "OK": {}
2250 }
2255 }
2251 });
2256 });
2252 };
2257 };
2253
2258
2254 /********************* checkpoint-related *********************/
2259 /********************* checkpoint-related *********************/
2255
2260
2256 /**
2261 /**
2257 * Save the notebook then immediately create a checkpoint.
2262 * Save the notebook then immediately create a checkpoint.
2258 *
2263 *
2259 * @method save_checkpoint
2264 * @method save_checkpoint
2260 */
2265 */
2261 Notebook.prototype.save_checkpoint = function () {
2266 Notebook.prototype.save_checkpoint = function () {
2262 this._checkpoint_after_save = true;
2267 this._checkpoint_after_save = true;
2263 this.save_notebook();
2268 this.save_notebook();
2264 };
2269 };
2265
2270
2266 /**
2271 /**
2267 * Add a checkpoint for this notebook.
2272 * Add a checkpoint for this notebook.
2268 * for use as a callback from checkpoint creation.
2273 * for use as a callback from checkpoint creation.
2269 *
2274 *
2270 * @method add_checkpoint
2275 * @method add_checkpoint
2271 */
2276 */
2272 Notebook.prototype.add_checkpoint = function (checkpoint) {
2277 Notebook.prototype.add_checkpoint = function (checkpoint) {
2273 var found = false;
2278 var found = false;
2274 for (var i = 0; i < this.checkpoints.length; i++) {
2279 for (var i = 0; i < this.checkpoints.length; i++) {
2275 var existing = this.checkpoints[i];
2280 var existing = this.checkpoints[i];
2276 if (existing.id == checkpoint.id) {
2281 if (existing.id == checkpoint.id) {
2277 found = true;
2282 found = true;
2278 this.checkpoints[i] = checkpoint;
2283 this.checkpoints[i] = checkpoint;
2279 break;
2284 break;
2280 }
2285 }
2281 }
2286 }
2282 if (!found) {
2287 if (!found) {
2283 this.checkpoints.push(checkpoint);
2288 this.checkpoints.push(checkpoint);
2284 }
2289 }
2285 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2290 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2286 };
2291 };
2287
2292
2288 /**
2293 /**
2289 * List checkpoints for this notebook.
2294 * List checkpoints for this notebook.
2290 *
2295 *
2291 * @method list_checkpoints
2296 * @method list_checkpoints
2292 */
2297 */
2293 Notebook.prototype.list_checkpoints = function () {
2298 Notebook.prototype.list_checkpoints = function () {
2294 var url = utils.url_join_encode(
2299 var url = utils.url_join_encode(
2295 this.base_url,
2300 this.base_url,
2296 'api/notebooks',
2301 'api/notebooks',
2297 this.notebook_path,
2302 this.notebook_path,
2298 this.notebook_name,
2303 this.notebook_name,
2299 'checkpoints'
2304 'checkpoints'
2300 );
2305 );
2301 $.get(url).done(
2306 $.get(url).done(
2302 $.proxy(this.list_checkpoints_success, this)
2307 $.proxy(this.list_checkpoints_success, this)
2303 ).fail(
2308 ).fail(
2304 $.proxy(this.list_checkpoints_error, this)
2309 $.proxy(this.list_checkpoints_error, this)
2305 );
2310 );
2306 };
2311 };
2307
2312
2308 /**
2313 /**
2309 * Success callback for listing checkpoints.
2314 * Success callback for listing checkpoints.
2310 *
2315 *
2311 * @method list_checkpoint_success
2316 * @method list_checkpoint_success
2312 * @param {Object} data JSON representation of a checkpoint
2317 * @param {Object} data JSON representation of a checkpoint
2313 * @param {String} status Description of response status
2318 * @param {String} status Description of response status
2314 * @param {jqXHR} xhr jQuery Ajax object
2319 * @param {jqXHR} xhr jQuery Ajax object
2315 */
2320 */
2316 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2321 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2317 data = $.parseJSON(data);
2322 data = $.parseJSON(data);
2318 this.checkpoints = data;
2323 this.checkpoints = data;
2319 if (data.length) {
2324 if (data.length) {
2320 this.last_checkpoint = data[data.length - 1];
2325 this.last_checkpoint = data[data.length - 1];
2321 } else {
2326 } else {
2322 this.last_checkpoint = null;
2327 this.last_checkpoint = null;
2323 }
2328 }
2324 this.events.trigger('checkpoints_listed.Notebook', [data]);
2329 this.events.trigger('checkpoints_listed.Notebook', [data]);
2325 };
2330 };
2326
2331
2327 /**
2332 /**
2328 * Failure callback for listing a checkpoint.
2333 * Failure callback for listing a checkpoint.
2329 *
2334 *
2330 * @method list_checkpoint_error
2335 * @method list_checkpoint_error
2331 * @param {jqXHR} xhr jQuery Ajax object
2336 * @param {jqXHR} xhr jQuery Ajax object
2332 * @param {String} status Description of response status
2337 * @param {String} status Description of response status
2333 * @param {String} error_msg HTTP error message
2338 * @param {String} error_msg HTTP error message
2334 */
2339 */
2335 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2340 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2336 this.events.trigger('list_checkpoints_failed.Notebook');
2341 this.events.trigger('list_checkpoints_failed.Notebook');
2337 };
2342 };
2338
2343
2339 /**
2344 /**
2340 * Create a checkpoint of this notebook on the server from the most recent save.
2345 * Create a checkpoint of this notebook on the server from the most recent save.
2341 *
2346 *
2342 * @method create_checkpoint
2347 * @method create_checkpoint
2343 */
2348 */
2344 Notebook.prototype.create_checkpoint = function () {
2349 Notebook.prototype.create_checkpoint = function () {
2345 var url = utils.url_join_encode(
2350 var url = utils.url_join_encode(
2346 this.base_url,
2351 this.base_url,
2347 'api/notebooks',
2352 'api/notebooks',
2348 this.notebook_path,
2353 this.notebook_path,
2349 this.notebook_name,
2354 this.notebook_name,
2350 'checkpoints'
2355 'checkpoints'
2351 );
2356 );
2352 $.post(url).done(
2357 $.post(url).done(
2353 $.proxy(this.create_checkpoint_success, this)
2358 $.proxy(this.create_checkpoint_success, this)
2354 ).fail(
2359 ).fail(
2355 $.proxy(this.create_checkpoint_error, this)
2360 $.proxy(this.create_checkpoint_error, this)
2356 );
2361 );
2357 };
2362 };
2358
2363
2359 /**
2364 /**
2360 * Success callback for creating a checkpoint.
2365 * Success callback for creating a checkpoint.
2361 *
2366 *
2362 * @method create_checkpoint_success
2367 * @method create_checkpoint_success
2363 * @param {Object} data JSON representation of a checkpoint
2368 * @param {Object} data JSON representation of a checkpoint
2364 * @param {String} status Description of response status
2369 * @param {String} status Description of response status
2365 * @param {jqXHR} xhr jQuery Ajax object
2370 * @param {jqXHR} xhr jQuery Ajax object
2366 */
2371 */
2367 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2372 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2368 data = $.parseJSON(data);
2373 data = $.parseJSON(data);
2369 this.add_checkpoint(data);
2374 this.add_checkpoint(data);
2370 this.events.trigger('checkpoint_created.Notebook', data);
2375 this.events.trigger('checkpoint_created.Notebook', data);
2371 };
2376 };
2372
2377
2373 /**
2378 /**
2374 * Failure callback for creating a checkpoint.
2379 * Failure callback for creating a checkpoint.
2375 *
2380 *
2376 * @method create_checkpoint_error
2381 * @method create_checkpoint_error
2377 * @param {jqXHR} xhr jQuery Ajax object
2382 * @param {jqXHR} xhr jQuery Ajax object
2378 * @param {String} status Description of response status
2383 * @param {String} status Description of response status
2379 * @param {String} error_msg HTTP error message
2384 * @param {String} error_msg HTTP error message
2380 */
2385 */
2381 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2386 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2382 this.events.trigger('checkpoint_failed.Notebook');
2387 this.events.trigger('checkpoint_failed.Notebook');
2383 };
2388 };
2384
2389
2385 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2390 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2386 var that = this;
2391 var that = this;
2387 checkpoint = checkpoint || this.last_checkpoint;
2392 checkpoint = checkpoint || this.last_checkpoint;
2388 if ( ! checkpoint ) {
2393 if ( ! checkpoint ) {
2389 console.log("restore dialog, but no checkpoint to restore to!");
2394 console.log("restore dialog, but no checkpoint to restore to!");
2390 return;
2395 return;
2391 }
2396 }
2392 var body = $('<div/>').append(
2397 var body = $('<div/>').append(
2393 $('<p/>').addClass("p-space").text(
2398 $('<p/>').addClass("p-space").text(
2394 "Are you sure you want to revert the notebook to " +
2399 "Are you sure you want to revert the notebook to " +
2395 "the latest checkpoint?"
2400 "the latest checkpoint?"
2396 ).append(
2401 ).append(
2397 $("<strong/>").text(
2402 $("<strong/>").text(
2398 " This cannot be undone."
2403 " This cannot be undone."
2399 )
2404 )
2400 )
2405 )
2401 ).append(
2406 ).append(
2402 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2407 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2403 ).append(
2408 ).append(
2404 $('<p/>').addClass("p-space").text(
2409 $('<p/>').addClass("p-space").text(
2405 Date(checkpoint.last_modified)
2410 Date(checkpoint.last_modified)
2406 ).css("text-align", "center")
2411 ).css("text-align", "center")
2407 );
2412 );
2408
2413
2409 dialog.modal({
2414 dialog.modal({
2410 notebook: this,
2415 notebook: this,
2411 keyboard_manager: this.keyboard_manager,
2416 keyboard_manager: this.keyboard_manager,
2412 title : "Revert notebook to checkpoint",
2417 title : "Revert notebook to checkpoint",
2413 body : body,
2418 body : body,
2414 buttons : {
2419 buttons : {
2415 Revert : {
2420 Revert : {
2416 class : "btn-danger",
2421 class : "btn-danger",
2417 click : function () {
2422 click : function () {
2418 that.restore_checkpoint(checkpoint.id);
2423 that.restore_checkpoint(checkpoint.id);
2419 }
2424 }
2420 },
2425 },
2421 Cancel : {}
2426 Cancel : {}
2422 }
2427 }
2423 });
2428 });
2424 };
2429 };
2425
2430
2426 /**
2431 /**
2427 * Restore the notebook to a checkpoint state.
2432 * Restore the notebook to a checkpoint state.
2428 *
2433 *
2429 * @method restore_checkpoint
2434 * @method restore_checkpoint
2430 * @param {String} checkpoint ID
2435 * @param {String} checkpoint ID
2431 */
2436 */
2432 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2437 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2433 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2438 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2434 var url = utils.url_join_encode(
2439 var url = utils.url_join_encode(
2435 this.base_url,
2440 this.base_url,
2436 'api/notebooks',
2441 'api/notebooks',
2437 this.notebook_path,
2442 this.notebook_path,
2438 this.notebook_name,
2443 this.notebook_name,
2439 'checkpoints',
2444 'checkpoints',
2440 checkpoint
2445 checkpoint
2441 );
2446 );
2442 $.post(url).done(
2447 $.post(url).done(
2443 $.proxy(this.restore_checkpoint_success, this)
2448 $.proxy(this.restore_checkpoint_success, this)
2444 ).fail(
2449 ).fail(
2445 $.proxy(this.restore_checkpoint_error, this)
2450 $.proxy(this.restore_checkpoint_error, this)
2446 );
2451 );
2447 };
2452 };
2448
2453
2449 /**
2454 /**
2450 * Success callback for restoring a notebook to a checkpoint.
2455 * Success callback for restoring a notebook to a checkpoint.
2451 *
2456 *
2452 * @method restore_checkpoint_success
2457 * @method restore_checkpoint_success
2453 * @param {Object} data (ignored, should be empty)
2458 * @param {Object} data (ignored, should be empty)
2454 * @param {String} status Description of response status
2459 * @param {String} status Description of response status
2455 * @param {jqXHR} xhr jQuery Ajax object
2460 * @param {jqXHR} xhr jQuery Ajax object
2456 */
2461 */
2457 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2462 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2458 this.events.trigger('checkpoint_restored.Notebook');
2463 this.events.trigger('checkpoint_restored.Notebook');
2459 this.load_notebook(this.notebook_name, this.notebook_path);
2464 this.load_notebook(this.notebook_name, this.notebook_path);
2460 };
2465 };
2461
2466
2462 /**
2467 /**
2463 * Failure callback for restoring a notebook to a checkpoint.
2468 * Failure callback for restoring a notebook to a checkpoint.
2464 *
2469 *
2465 * @method restore_checkpoint_error
2470 * @method restore_checkpoint_error
2466 * @param {jqXHR} xhr jQuery Ajax object
2471 * @param {jqXHR} xhr jQuery Ajax object
2467 * @param {String} status Description of response status
2472 * @param {String} status Description of response status
2468 * @param {String} error_msg HTTP error message
2473 * @param {String} error_msg HTTP error message
2469 */
2474 */
2470 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2475 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2471 this.events.trigger('checkpoint_restore_failed.Notebook');
2476 this.events.trigger('checkpoint_restore_failed.Notebook');
2472 };
2477 };
2473
2478
2474 /**
2479 /**
2475 * Delete a notebook checkpoint.
2480 * Delete a notebook checkpoint.
2476 *
2481 *
2477 * @method delete_checkpoint
2482 * @method delete_checkpoint
2478 * @param {String} checkpoint ID
2483 * @param {String} checkpoint ID
2479 */
2484 */
2480 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2485 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2481 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2486 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2482 var url = utils.url_join_encode(
2487 var url = utils.url_join_encode(
2483 this.base_url,
2488 this.base_url,
2484 'api/notebooks',
2489 'api/notebooks',
2485 this.notebook_path,
2490 this.notebook_path,
2486 this.notebook_name,
2491 this.notebook_name,
2487 'checkpoints',
2492 'checkpoints',
2488 checkpoint
2493 checkpoint
2489 );
2494 );
2490 $.ajax(url, {
2495 $.ajax(url, {
2491 type: 'DELETE',
2496 type: 'DELETE',
2492 success: $.proxy(this.delete_checkpoint_success, this),
2497 success: $.proxy(this.delete_checkpoint_success, this),
2493 error: $.proxy(this.delete_checkpoint_error, this)
2498 error: $.proxy(this.delete_checkpoint_error, this)
2494 });
2499 });
2495 };
2500 };
2496
2501
2497 /**
2502 /**
2498 * Success callback for deleting a notebook checkpoint
2503 * Success callback for deleting a notebook checkpoint
2499 *
2504 *
2500 * @method delete_checkpoint_success
2505 * @method delete_checkpoint_success
2501 * @param {Object} data (ignored, should be empty)
2506 * @param {Object} data (ignored, should be empty)
2502 * @param {String} status Description of response status
2507 * @param {String} status Description of response status
2503 * @param {jqXHR} xhr jQuery Ajax object
2508 * @param {jqXHR} xhr jQuery Ajax object
2504 */
2509 */
2505 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2510 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2506 this.events.trigger('checkpoint_deleted.Notebook', data);
2511 this.events.trigger('checkpoint_deleted.Notebook', data);
2507 this.load_notebook(this.notebook_name, this.notebook_path);
2512 this.load_notebook(this.notebook_name, this.notebook_path);
2508 };
2513 };
2509
2514
2510 /**
2515 /**
2511 * Failure callback for deleting a notebook checkpoint.
2516 * Failure callback for deleting a notebook checkpoint.
2512 *
2517 *
2513 * @method delete_checkpoint_error
2518 * @method delete_checkpoint_error
2514 * @param {jqXHR} xhr jQuery Ajax object
2519 * @param {jqXHR} xhr jQuery Ajax object
2515 * @param {String} status Description of response status
2520 * @param {String} status Description of response status
2516 * @param {String} error_msg HTTP error message
2521 * @param {String} error_msg HTTP error message
2517 */
2522 */
2518 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2523 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2519 this.events.trigger('checkpoint_delete_failed.Notebook');
2524 this.events.trigger('checkpoint_delete_failed.Notebook');
2520 };
2525 };
2521
2526
2522
2527
2523 // For backwards compatability.
2528 // For backwards compatability.
2524 IPython.Notebook = Notebook;
2529 IPython.Notebook = Notebook;
2525
2530
2526 return {'Notebook': Notebook};
2531 return {'Notebook': Notebook};
2527 });
2532 });
@@ -1,612 +1,613
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 'services/kernels/js/comm',
8 'services/kernels/js/comm',
9 'widgets/js/init',
9 'widgets/js/init',
10 ], function(IPython, $, utils, comm, widgetmanager) {
10 ], function(IPython, $, utils, comm, widgetmanager) {
11 "use strict";
11 "use strict";
12
12
13 // Initialization and connection.
13 // Initialization and connection.
14 /**
14 /**
15 * A Kernel Class to communicate with the Python kernel
15 * A Kernel Class to communicate with the Python kernel
16 * @Class Kernel
16 * @Class Kernel
17 */
17 */
18 var Kernel = function (kernel_service_url, notebook) {
18 var Kernel = function (kernel_service_url, notebook, name) {
19 this.events = notebook.events;
19 this.events = notebook.events;
20 this.kernel_id = null;
20 this.kernel_id = null;
21 this.shell_channel = null;
21 this.shell_channel = null;
22 this.iopub_channel = null;
22 this.iopub_channel = null;
23 this.stdin_channel = null;
23 this.stdin_channel = null;
24 this.kernel_service_url = kernel_service_url;
24 this.kernel_service_url = kernel_service_url;
25 this.name = name;
25 this.running = false;
26 this.running = false;
26 this.username = "username";
27 this.username = "username";
27 this.session_id = utils.uuid();
28 this.session_id = utils.uuid();
28 this._msg_callbacks = {};
29 this._msg_callbacks = {};
29 this.post = $.post;
30 this.post = $.post;
30
31
31 if (typeof(WebSocket) !== 'undefined') {
32 if (typeof(WebSocket) !== 'undefined') {
32 this.WebSocket = WebSocket;
33 this.WebSocket = WebSocket;
33 } else if (typeof(MozWebSocket) !== 'undefined') {
34 } else if (typeof(MozWebSocket) !== 'undefined') {
34 this.WebSocket = MozWebSocket;
35 this.WebSocket = MozWebSocket;
35 } else {
36 } else {
36 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.');
37 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.');
37 }
38 }
38
39
39 this.bind_events();
40 this.bind_events();
40 this.init_iopub_handlers();
41 this.init_iopub_handlers();
41 this.comm_manager = new comm.CommManager(this);
42 this.comm_manager = new comm.CommManager(this);
42 this.widget_manager = new widgetmanager.WidgetManager(this.comm_manager, notebook);
43 this.widget_manager = new widgetmanager.WidgetManager(this.comm_manager, notebook);
43
44
44 this.last_msg_id = null;
45 this.last_msg_id = null;
45 this.last_msg_callbacks = {};
46 this.last_msg_callbacks = {};
46 };
47 };
47
48
48
49
49 Kernel.prototype._get_msg = function (msg_type, content, metadata) {
50 Kernel.prototype._get_msg = function (msg_type, content, metadata) {
50 var msg = {
51 var msg = {
51 header : {
52 header : {
52 msg_id : utils.uuid(),
53 msg_id : utils.uuid(),
53 username : this.username,
54 username : this.username,
54 session : this.session_id,
55 session : this.session_id,
55 msg_type : msg_type,
56 msg_type : msg_type,
56 version : "5.0"
57 version : "5.0"
57 },
58 },
58 metadata : metadata || {},
59 metadata : metadata || {},
59 content : content,
60 content : content,
60 parent_header : {}
61 parent_header : {}
61 };
62 };
62 return msg;
63 return msg;
63 };
64 };
64
65
65 Kernel.prototype.bind_events = function () {
66 Kernel.prototype.bind_events = function () {
66 var that = this;
67 var that = this;
67 this.events.on('send_input_reply.Kernel', function(evt, data) {
68 this.events.on('send_input_reply.Kernel', function(evt, data) {
68 that.send_input_reply(data);
69 that.send_input_reply(data);
69 });
70 });
70 };
71 };
71
72
72 // Initialize the iopub handlers
73 // Initialize the iopub handlers
73
74
74 Kernel.prototype.init_iopub_handlers = function () {
75 Kernel.prototype.init_iopub_handlers = function () {
75 var output_msg_types = ['stream', 'display_data', 'execute_result', 'error'];
76 var output_msg_types = ['stream', 'display_data', 'execute_result', 'error'];
76 this._iopub_handlers = {};
77 this._iopub_handlers = {};
77 this.register_iopub_handler('status', $.proxy(this._handle_status_message, this));
78 this.register_iopub_handler('status', $.proxy(this._handle_status_message, this));
78 this.register_iopub_handler('clear_output', $.proxy(this._handle_clear_output, this));
79 this.register_iopub_handler('clear_output', $.proxy(this._handle_clear_output, this));
79
80
80 for (var i=0; i < output_msg_types.length; i++) {
81 for (var i=0; i < output_msg_types.length; i++) {
81 this.register_iopub_handler(output_msg_types[i], $.proxy(this._handle_output_message, this));
82 this.register_iopub_handler(output_msg_types[i], $.proxy(this._handle_output_message, this));
82 }
83 }
83 };
84 };
84
85
85 /**
86 /**
86 * Start the Python kernel
87 * Start the Python kernel
87 * @method start
88 * @method start
88 */
89 */
89 Kernel.prototype.start = function (params) {
90 Kernel.prototype.start = function (params) {
90 params = params || {};
91 params = params || {};
91 if (!this.running) {
92 if (!this.running) {
92 var qs = $.param(params);
93 var qs = $.param(params);
93 this.post(utils.url_join_encode(this.kernel_service_url) + '?' + qs,
94 this.post(utils.url_join_encode(this.kernel_service_url) + '?' + qs,
94 $.proxy(this._kernel_started, this),
95 $.proxy(this._kernel_started, this),
95 'json'
96 'json'
96 );
97 );
97 }
98 }
98 };
99 };
99
100
100 /**
101 /**
101 * Restart the python kernel.
102 * Restart the python kernel.
102 *
103 *
103 * Emit a 'status_restarting.Kernel' event with
104 * Emit a 'status_restarting.Kernel' event with
104 * the current object as parameter
105 * the current object as parameter
105 *
106 *
106 * @method restart
107 * @method restart
107 */
108 */
108 Kernel.prototype.restart = function () {
109 Kernel.prototype.restart = function () {
109 this.events.trigger('status_restarting.Kernel', {kernel: this});
110 this.events.trigger('status_restarting.Kernel', {kernel: this});
110 if (this.running) {
111 if (this.running) {
111 this.stop_channels();
112 this.stop_channels();
112 this.post(utils.url_join_encode(this.kernel_url, "restart"),
113 this.post(utils.url_join_encode(this.kernel_url, "restart"),
113 $.proxy(this._kernel_started, this),
114 $.proxy(this._kernel_started, this),
114 'json'
115 'json'
115 );
116 );
116 }
117 }
117 };
118 };
118
119
119
120
120 Kernel.prototype._kernel_started = function (json) {
121 Kernel.prototype._kernel_started = function (json) {
121 console.log("Kernel started: ", json.id);
122 console.log("Kernel started: ", json.id);
122 this.running = true;
123 this.running = true;
123 this.kernel_id = json.id;
124 this.kernel_id = json.id;
124 // trailing 's' in https will become wss for secure web sockets
125 // trailing 's' in https will become wss for secure web sockets
125 this.ws_host = location.protocol.replace('http', 'ws') + "//" + location.host;
126 this.ws_host = location.protocol.replace('http', 'ws') + "//" + location.host;
126 this.kernel_url = utils.url_path_join(this.kernel_service_url, this.kernel_id);
127 this.kernel_url = utils.url_path_join(this.kernel_service_url, this.kernel_id);
127 this.start_channels();
128 this.start_channels();
128 };
129 };
129
130
130
131
131 Kernel.prototype._websocket_closed = function(ws_url, early) {
132 Kernel.prototype._websocket_closed = function(ws_url, early) {
132 this.stop_channels();
133 this.stop_channels();
133 this.events.trigger('websocket_closed.Kernel',
134 this.events.trigger('websocket_closed.Kernel',
134 {ws_url: ws_url, kernel: this, early: early}
135 {ws_url: ws_url, kernel: this, early: early}
135 );
136 );
136 };
137 };
137
138
138 /**
139 /**
139 * Start the `shell`and `iopub` channels.
140 * Start the `shell`and `iopub` channels.
140 * Will stop and restart them if they already exist.
141 * Will stop and restart them if they already exist.
141 *
142 *
142 * @method start_channels
143 * @method start_channels
143 */
144 */
144 Kernel.prototype.start_channels = function () {
145 Kernel.prototype.start_channels = function () {
145 var that = this;
146 var that = this;
146 this.stop_channels();
147 this.stop_channels();
147 var ws_host_url = this.ws_host + this.kernel_url;
148 var ws_host_url = this.ws_host + this.kernel_url;
148 console.log("Starting WebSockets:", ws_host_url);
149 console.log("Starting WebSockets:", ws_host_url);
149 this.shell_channel = new this.WebSocket(
150 this.shell_channel = new this.WebSocket(
150 this.ws_host + utils.url_join_encode(this.kernel_url, "shell")
151 this.ws_host + utils.url_join_encode(this.kernel_url, "shell")
151 );
152 );
152 this.stdin_channel = new this.WebSocket(
153 this.stdin_channel = new this.WebSocket(
153 this.ws_host + utils.url_join_encode(this.kernel_url, "stdin")
154 this.ws_host + utils.url_join_encode(this.kernel_url, "stdin")
154 );
155 );
155 this.iopub_channel = new this.WebSocket(
156 this.iopub_channel = new this.WebSocket(
156 this.ws_host + utils.url_join_encode(this.kernel_url, "iopub")
157 this.ws_host + utils.url_join_encode(this.kernel_url, "iopub")
157 );
158 );
158
159
159 var already_called_onclose = false; // only alert once
160 var already_called_onclose = false; // only alert once
160 var ws_closed_early = function(evt){
161 var ws_closed_early = function(evt){
161 if (already_called_onclose){
162 if (already_called_onclose){
162 return;
163 return;
163 }
164 }
164 already_called_onclose = true;
165 already_called_onclose = true;
165 if ( ! evt.wasClean ){
166 if ( ! evt.wasClean ){
166 that._websocket_closed(ws_host_url, true);
167 that._websocket_closed(ws_host_url, true);
167 }
168 }
168 };
169 };
169 var ws_closed_late = function(evt){
170 var ws_closed_late = function(evt){
170 if (already_called_onclose){
171 if (already_called_onclose){
171 return;
172 return;
172 }
173 }
173 already_called_onclose = true;
174 already_called_onclose = true;
174 if ( ! evt.wasClean ){
175 if ( ! evt.wasClean ){
175 that._websocket_closed(ws_host_url, false);
176 that._websocket_closed(ws_host_url, false);
176 }
177 }
177 };
178 };
178 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
179 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
179 for (var i=0; i < channels.length; i++) {
180 for (var i=0; i < channels.length; i++) {
180 channels[i].onopen = $.proxy(this._ws_opened, this);
181 channels[i].onopen = $.proxy(this._ws_opened, this);
181 channels[i].onclose = ws_closed_early;
182 channels[i].onclose = ws_closed_early;
182 }
183 }
183 // switch from early-close to late-close message after 1s
184 // switch from early-close to late-close message after 1s
184 setTimeout(function() {
185 setTimeout(function() {
185 for (var i=0; i < channels.length; i++) {
186 for (var i=0; i < channels.length; i++) {
186 if (channels[i] !== null) {
187 if (channels[i] !== null) {
187 channels[i].onclose = ws_closed_late;
188 channels[i].onclose = ws_closed_late;
188 }
189 }
189 }
190 }
190 }, 1000);
191 }, 1000);
191 this.shell_channel.onmessage = $.proxy(this._handle_shell_reply, this);
192 this.shell_channel.onmessage = $.proxy(this._handle_shell_reply, this);
192 this.iopub_channel.onmessage = $.proxy(this._handle_iopub_message, this);
193 this.iopub_channel.onmessage = $.proxy(this._handle_iopub_message, this);
193 this.stdin_channel.onmessage = $.proxy(this._handle_input_request, this);
194 this.stdin_channel.onmessage = $.proxy(this._handle_input_request, this);
194 };
195 };
195
196
196 /**
197 /**
197 * Handle a websocket entering the open state
198 * Handle a websocket entering the open state
198 * sends session and cookie authentication info as first message.
199 * sends session and cookie authentication info as first message.
199 * Once all sockets are open, signal the Kernel.status_started event.
200 * Once all sockets are open, signal the Kernel.status_started event.
200 * @method _ws_opened
201 * @method _ws_opened
201 */
202 */
202 Kernel.prototype._ws_opened = function (evt) {
203 Kernel.prototype._ws_opened = function (evt) {
203 // send the session id so the Session object Python-side
204 // send the session id so the Session object Python-side
204 // has the same identity
205 // has the same identity
205 evt.target.send(this.session_id + ':' + document.cookie);
206 evt.target.send(this.session_id + ':' + document.cookie);
206
207
207 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
208 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
208 for (var i=0; i < channels.length; i++) {
209 for (var i=0; i < channels.length; i++) {
209 // if any channel is not ready, don't trigger event.
210 // if any channel is not ready, don't trigger event.
210 if ( !channels[i].readyState ) return;
211 if ( !channels[i].readyState ) return;
211 }
212 }
212 // all events ready, trigger started event.
213 // all events ready, trigger started event.
213 this.events.trigger('status_started.Kernel', {kernel: this});
214 this.events.trigger('status_started.Kernel', {kernel: this});
214 };
215 };
215
216
216 /**
217 /**
217 * Stop the websocket channels.
218 * Stop the websocket channels.
218 * @method stop_channels
219 * @method stop_channels
219 */
220 */
220 Kernel.prototype.stop_channels = function () {
221 Kernel.prototype.stop_channels = function () {
221 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
222 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
222 for (var i=0; i < channels.length; i++) {
223 for (var i=0; i < channels.length; i++) {
223 if ( channels[i] !== null ) {
224 if ( channels[i] !== null ) {
224 channels[i].onclose = null;
225 channels[i].onclose = null;
225 channels[i].close();
226 channels[i].close();
226 }
227 }
227 }
228 }
228 this.shell_channel = this.iopub_channel = this.stdin_channel = null;
229 this.shell_channel = this.iopub_channel = this.stdin_channel = null;
229 };
230 };
230
231
231 // Main public methods.
232 // Main public methods.
232
233
233 // send a message on the Kernel's shell channel
234 // send a message on the Kernel's shell channel
234 Kernel.prototype.send_shell_message = function (msg_type, content, callbacks, metadata) {
235 Kernel.prototype.send_shell_message = function (msg_type, content, callbacks, metadata) {
235 var msg = this._get_msg(msg_type, content, metadata);
236 var msg = this._get_msg(msg_type, content, metadata);
236 this.shell_channel.send(JSON.stringify(msg));
237 this.shell_channel.send(JSON.stringify(msg));
237 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
238 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
238 return msg.header.msg_id;
239 return msg.header.msg_id;
239 };
240 };
240
241
241 /**
242 /**
242 * Get kernel info
243 * Get kernel info
243 *
244 *
244 * @param callback {function}
245 * @param callback {function}
245 * @method kernel_info
246 * @method kernel_info
246 *
247 *
247 * When calling this method, pass a callback function that expects one argument.
248 * When calling this method, pass a callback function that expects one argument.
248 * The callback will be passed the complete `kernel_info_reply` message documented
249 * The callback will be passed the complete `kernel_info_reply` message documented
249 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#kernel-info)
250 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#kernel-info)
250 */
251 */
251 Kernel.prototype.kernel_info = function (callback) {
252 Kernel.prototype.kernel_info = function (callback) {
252 var callbacks;
253 var callbacks;
253 if (callback) {
254 if (callback) {
254 callbacks = { shell : { reply : callback } };
255 callbacks = { shell : { reply : callback } };
255 }
256 }
256 return this.send_shell_message("kernel_info_request", {}, callbacks);
257 return this.send_shell_message("kernel_info_request", {}, callbacks);
257 };
258 };
258
259
259 /**
260 /**
260 * Get info on an object
261 * Get info on an object
261 *
262 *
262 * @param code {string}
263 * @param code {string}
263 * @param cursor_pos {integer}
264 * @param cursor_pos {integer}
264 * @param callback {function}
265 * @param callback {function}
265 * @method inspect
266 * @method inspect
266 *
267 *
267 * When calling this method, pass a callback function that expects one argument.
268 * When calling this method, pass a callback function that expects one argument.
268 * The callback will be passed the complete `inspect_reply` message documented
269 * The callback will be passed the complete `inspect_reply` message documented
269 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#object-information)
270 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#object-information)
270 */
271 */
271 Kernel.prototype.inspect = function (code, cursor_pos, callback) {
272 Kernel.prototype.inspect = function (code, cursor_pos, callback) {
272 var callbacks;
273 var callbacks;
273 if (callback) {
274 if (callback) {
274 callbacks = { shell : { reply : callback } };
275 callbacks = { shell : { reply : callback } };
275 }
276 }
276
277
277 var content = {
278 var content = {
278 code : code,
279 code : code,
279 cursor_pos : cursor_pos,
280 cursor_pos : cursor_pos,
280 detail_level : 0,
281 detail_level : 0,
281 };
282 };
282 return this.send_shell_message("inspect_request", content, callbacks);
283 return this.send_shell_message("inspect_request", content, callbacks);
283 };
284 };
284
285
285 /**
286 /**
286 * Execute given code into kernel, and pass result to callback.
287 * Execute given code into kernel, and pass result to callback.
287 *
288 *
288 * @async
289 * @async
289 * @method execute
290 * @method execute
290 * @param {string} code
291 * @param {string} code
291 * @param [callbacks] {Object} With the following keys (all optional)
292 * @param [callbacks] {Object} With the following keys (all optional)
292 * @param callbacks.shell.reply {function}
293 * @param callbacks.shell.reply {function}
293 * @param callbacks.shell.payload.[payload_name] {function}
294 * @param callbacks.shell.payload.[payload_name] {function}
294 * @param callbacks.iopub.output {function}
295 * @param callbacks.iopub.output {function}
295 * @param callbacks.iopub.clear_output {function}
296 * @param callbacks.iopub.clear_output {function}
296 * @param callbacks.input {function}
297 * @param callbacks.input {function}
297 * @param {object} [options]
298 * @param {object} [options]
298 * @param [options.silent=false] {Boolean}
299 * @param [options.silent=false] {Boolean}
299 * @param [options.user_expressions=empty_dict] {Dict}
300 * @param [options.user_expressions=empty_dict] {Dict}
300 * @param [options.allow_stdin=false] {Boolean} true|false
301 * @param [options.allow_stdin=false] {Boolean} true|false
301 *
302 *
302 * @example
303 * @example
303 *
304 *
304 * The options object should contain the options for the execute call. Its default
305 * The options object should contain the options for the execute call. Its default
305 * values are:
306 * values are:
306 *
307 *
307 * options = {
308 * options = {
308 * silent : true,
309 * silent : true,
309 * user_expressions : {},
310 * user_expressions : {},
310 * allow_stdin : false
311 * allow_stdin : false
311 * }
312 * }
312 *
313 *
313 * When calling this method pass a callbacks structure of the form:
314 * When calling this method pass a callbacks structure of the form:
314 *
315 *
315 * callbacks = {
316 * callbacks = {
316 * shell : {
317 * shell : {
317 * reply : execute_reply_callback,
318 * reply : execute_reply_callback,
318 * payload : {
319 * payload : {
319 * set_next_input : set_next_input_callback,
320 * set_next_input : set_next_input_callback,
320 * }
321 * }
321 * },
322 * },
322 * iopub : {
323 * iopub : {
323 * output : output_callback,
324 * output : output_callback,
324 * clear_output : clear_output_callback,
325 * clear_output : clear_output_callback,
325 * },
326 * },
326 * input : raw_input_callback
327 * input : raw_input_callback
327 * }
328 * }
328 *
329 *
329 * Each callback will be passed the entire message as a single arugment.
330 * Each callback will be passed the entire message as a single arugment.
330 * Payload handlers will be passed the corresponding payload and the execute_reply message.
331 * Payload handlers will be passed the corresponding payload and the execute_reply message.
331 */
332 */
332 Kernel.prototype.execute = function (code, callbacks, options) {
333 Kernel.prototype.execute = function (code, callbacks, options) {
333
334
334 var content = {
335 var content = {
335 code : code,
336 code : code,
336 silent : true,
337 silent : true,
337 store_history : false,
338 store_history : false,
338 user_expressions : {},
339 user_expressions : {},
339 allow_stdin : false
340 allow_stdin : false
340 };
341 };
341 callbacks = callbacks || {};
342 callbacks = callbacks || {};
342 if (callbacks.input !== undefined) {
343 if (callbacks.input !== undefined) {
343 content.allow_stdin = true;
344 content.allow_stdin = true;
344 }
345 }
345 $.extend(true, content, options);
346 $.extend(true, content, options);
346 this.events.trigger('execution_request.Kernel', {kernel: this, content:content});
347 this.events.trigger('execution_request.Kernel', {kernel: this, content:content});
347 return this.send_shell_message("execute_request", content, callbacks);
348 return this.send_shell_message("execute_request", content, callbacks);
348 };
349 };
349
350
350 /**
351 /**
351 * When calling this method, pass a function to be called with the `complete_reply` message
352 * When calling this method, pass a function to be called with the `complete_reply` message
352 * as its only argument when it arrives.
353 * as its only argument when it arrives.
353 *
354 *
354 * `complete_reply` is documented
355 * `complete_reply` is documented
355 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#complete)
356 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#complete)
356 *
357 *
357 * @method complete
358 * @method complete
358 * @param code {string}
359 * @param code {string}
359 * @param cursor_pos {integer}
360 * @param cursor_pos {integer}
360 * @param callback {function}
361 * @param callback {function}
361 *
362 *
362 */
363 */
363 Kernel.prototype.complete = function (code, cursor_pos, callback) {
364 Kernel.prototype.complete = function (code, cursor_pos, callback) {
364 var callbacks;
365 var callbacks;
365 if (callback) {
366 if (callback) {
366 callbacks = { shell : { reply : callback } };
367 callbacks = { shell : { reply : callback } };
367 }
368 }
368 var content = {
369 var content = {
369 code : code,
370 code : code,
370 cursor_pos : cursor_pos,
371 cursor_pos : cursor_pos,
371 };
372 };
372 return this.send_shell_message("complete_request", content, callbacks);
373 return this.send_shell_message("complete_request", content, callbacks);
373 };
374 };
374
375
375
376
376 Kernel.prototype.interrupt = function () {
377 Kernel.prototype.interrupt = function () {
377 if (this.running) {
378 if (this.running) {
378 this.events.trigger('status_interrupting.Kernel', {kernel: this});
379 this.events.trigger('status_interrupting.Kernel', {kernel: this});
379 this.post(utils.url_join_encode(this.kernel_url, "interrupt"));
380 this.post(utils.url_join_encode(this.kernel_url, "interrupt"));
380 }
381 }
381 };
382 };
382
383
383
384
384 Kernel.prototype.kill = function () {
385 Kernel.prototype.kill = function () {
385 if (this.running) {
386 if (this.running) {
386 this.running = false;
387 this.running = false;
387 var settings = {
388 var settings = {
388 cache : false,
389 cache : false,
389 type : "DELETE",
390 type : "DELETE",
390 error : utils.log_ajax_error,
391 error : utils.log_ajax_error,
391 };
392 };
392 $.ajax(utils.url_join_encode(this.kernel_url), settings);
393 $.ajax(utils.url_join_encode(this.kernel_url), settings);
393 }
394 }
394 };
395 };
395
396
396 Kernel.prototype.send_input_reply = function (input) {
397 Kernel.prototype.send_input_reply = function (input) {
397 var content = {
398 var content = {
398 value : input,
399 value : input,
399 };
400 };
400 this.events.trigger('input_reply.Kernel', {kernel: this, content:content});
401 this.events.trigger('input_reply.Kernel', {kernel: this, content:content});
401 var msg = this._get_msg("input_reply", content);
402 var msg = this._get_msg("input_reply", content);
402 this.stdin_channel.send(JSON.stringify(msg));
403 this.stdin_channel.send(JSON.stringify(msg));
403 return msg.header.msg_id;
404 return msg.header.msg_id;
404 };
405 };
405
406
406
407
407 // Reply handlers
408 // Reply handlers
408
409
409 Kernel.prototype.register_iopub_handler = function (msg_type, callback) {
410 Kernel.prototype.register_iopub_handler = function (msg_type, callback) {
410 this._iopub_handlers[msg_type] = callback;
411 this._iopub_handlers[msg_type] = callback;
411 };
412 };
412
413
413 Kernel.prototype.get_iopub_handler = function (msg_type) {
414 Kernel.prototype.get_iopub_handler = function (msg_type) {
414 // get iopub handler for a specific message type
415 // get iopub handler for a specific message type
415 return this._iopub_handlers[msg_type];
416 return this._iopub_handlers[msg_type];
416 };
417 };
417
418
418
419
419 Kernel.prototype.get_callbacks_for_msg = function (msg_id) {
420 Kernel.prototype.get_callbacks_for_msg = function (msg_id) {
420 // get callbacks for a specific message
421 // get callbacks for a specific message
421 if (msg_id == this.last_msg_id) {
422 if (msg_id == this.last_msg_id) {
422 return this.last_msg_callbacks;
423 return this.last_msg_callbacks;
423 } else {
424 } else {
424 return this._msg_callbacks[msg_id];
425 return this._msg_callbacks[msg_id];
425 }
426 }
426 };
427 };
427
428
428
429
429 Kernel.prototype.clear_callbacks_for_msg = function (msg_id) {
430 Kernel.prototype.clear_callbacks_for_msg = function (msg_id) {
430 if (this._msg_callbacks[msg_id] !== undefined ) {
431 if (this._msg_callbacks[msg_id] !== undefined ) {
431 delete this._msg_callbacks[msg_id];
432 delete this._msg_callbacks[msg_id];
432 }
433 }
433 };
434 };
434
435
435 Kernel.prototype._finish_shell = function (msg_id) {
436 Kernel.prototype._finish_shell = function (msg_id) {
436 var callbacks = this._msg_callbacks[msg_id];
437 var callbacks = this._msg_callbacks[msg_id];
437 if (callbacks !== undefined) {
438 if (callbacks !== undefined) {
438 callbacks.shell_done = true;
439 callbacks.shell_done = true;
439 if (callbacks.iopub_done) {
440 if (callbacks.iopub_done) {
440 this.clear_callbacks_for_msg(msg_id);
441 this.clear_callbacks_for_msg(msg_id);
441 }
442 }
442 }
443 }
443 };
444 };
444
445
445 Kernel.prototype._finish_iopub = function (msg_id) {
446 Kernel.prototype._finish_iopub = function (msg_id) {
446 var callbacks = this._msg_callbacks[msg_id];
447 var callbacks = this._msg_callbacks[msg_id];
447 if (callbacks !== undefined) {
448 if (callbacks !== undefined) {
448 callbacks.iopub_done = true;
449 callbacks.iopub_done = true;
449 if (callbacks.shell_done) {
450 if (callbacks.shell_done) {
450 this.clear_callbacks_for_msg(msg_id);
451 this.clear_callbacks_for_msg(msg_id);
451 }
452 }
452 }
453 }
453 };
454 };
454
455
455 /* Set callbacks for a particular message.
456 /* Set callbacks for a particular message.
456 * Callbacks should be a struct of the following form:
457 * Callbacks should be a struct of the following form:
457 * shell : {
458 * shell : {
458 *
459 *
459 * }
460 * }
460
461
461 */
462 */
462 Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks) {
463 Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks) {
463 this.last_msg_id = msg_id;
464 this.last_msg_id = msg_id;
464 if (callbacks) {
465 if (callbacks) {
465 // shallow-copy mapping, because we will modify it at the top level
466 // shallow-copy mapping, because we will modify it at the top level
466 var cbcopy = this._msg_callbacks[msg_id] = this.last_msg_callbacks = {};
467 var cbcopy = this._msg_callbacks[msg_id] = this.last_msg_callbacks = {};
467 cbcopy.shell = callbacks.shell;
468 cbcopy.shell = callbacks.shell;
468 cbcopy.iopub = callbacks.iopub;
469 cbcopy.iopub = callbacks.iopub;
469 cbcopy.input = callbacks.input;
470 cbcopy.input = callbacks.input;
470 cbcopy.shell_done = (!callbacks.shell);
471 cbcopy.shell_done = (!callbacks.shell);
471 cbcopy.iopub_done = (!callbacks.iopub);
472 cbcopy.iopub_done = (!callbacks.iopub);
472 } else {
473 } else {
473 this.last_msg_callbacks = {};
474 this.last_msg_callbacks = {};
474 }
475 }
475 };
476 };
476
477
477
478
478 Kernel.prototype._handle_shell_reply = function (e) {
479 Kernel.prototype._handle_shell_reply = function (e) {
479 var reply = $.parseJSON(e.data);
480 var reply = $.parseJSON(e.data);
480 this.events.trigger('shell_reply.Kernel', {kernel: this, reply:reply});
481 this.events.trigger('shell_reply.Kernel', {kernel: this, reply:reply});
481 var content = reply.content;
482 var content = reply.content;
482 var metadata = reply.metadata;
483 var metadata = reply.metadata;
483 var parent_id = reply.parent_header.msg_id;
484 var parent_id = reply.parent_header.msg_id;
484 var callbacks = this.get_callbacks_for_msg(parent_id);
485 var callbacks = this.get_callbacks_for_msg(parent_id);
485 if (!callbacks || !callbacks.shell) {
486 if (!callbacks || !callbacks.shell) {
486 return;
487 return;
487 }
488 }
488 var shell_callbacks = callbacks.shell;
489 var shell_callbacks = callbacks.shell;
489
490
490 // signal that shell callbacks are done
491 // signal that shell callbacks are done
491 this._finish_shell(parent_id);
492 this._finish_shell(parent_id);
492
493
493 if (shell_callbacks.reply !== undefined) {
494 if (shell_callbacks.reply !== undefined) {
494 shell_callbacks.reply(reply);
495 shell_callbacks.reply(reply);
495 }
496 }
496 if (content.payload && shell_callbacks.payload) {
497 if (content.payload && shell_callbacks.payload) {
497 this._handle_payloads(content.payload, shell_callbacks.payload, reply);
498 this._handle_payloads(content.payload, shell_callbacks.payload, reply);
498 }
499 }
499 };
500 };
500
501
501
502
502 Kernel.prototype._handle_payloads = function (payloads, payload_callbacks, msg) {
503 Kernel.prototype._handle_payloads = function (payloads, payload_callbacks, msg) {
503 var l = payloads.length;
504 var l = payloads.length;
504 // Payloads are handled by triggering events because we don't want the Kernel
505 // Payloads are handled by triggering events because we don't want the Kernel
505 // to depend on the Notebook or Pager classes.
506 // to depend on the Notebook or Pager classes.
506 for (var i=0; i<l; i++) {
507 for (var i=0; i<l; i++) {
507 var payload = payloads[i];
508 var payload = payloads[i];
508 var callback = payload_callbacks[payload.source];
509 var callback = payload_callbacks[payload.source];
509 if (callback) {
510 if (callback) {
510 callback(payload, msg);
511 callback(payload, msg);
511 }
512 }
512 }
513 }
513 };
514 };
514
515
515 Kernel.prototype._handle_status_message = function (msg) {
516 Kernel.prototype._handle_status_message = function (msg) {
516 var execution_state = msg.content.execution_state;
517 var execution_state = msg.content.execution_state;
517 var parent_id = msg.parent_header.msg_id;
518 var parent_id = msg.parent_header.msg_id;
518
519
519 // dispatch status msg callbacks, if any
520 // dispatch status msg callbacks, if any
520 var callbacks = this.get_callbacks_for_msg(parent_id);
521 var callbacks = this.get_callbacks_for_msg(parent_id);
521 if (callbacks && callbacks.iopub && callbacks.iopub.status) {
522 if (callbacks && callbacks.iopub && callbacks.iopub.status) {
522 try {
523 try {
523 callbacks.iopub.status(msg);
524 callbacks.iopub.status(msg);
524 } catch (e) {
525 } catch (e) {
525 console.log("Exception in status msg handler", e, e.stack);
526 console.log("Exception in status msg handler", e, e.stack);
526 }
527 }
527 }
528 }
528
529
529 if (execution_state === 'busy') {
530 if (execution_state === 'busy') {
530 this.events.trigger('status_busy.Kernel', {kernel: this});
531 this.events.trigger('status_busy.Kernel', {kernel: this});
531 } else if (execution_state === 'idle') {
532 } else if (execution_state === 'idle') {
532 // signal that iopub callbacks are (probably) done
533 // signal that iopub callbacks are (probably) done
533 // async output may still arrive,
534 // async output may still arrive,
534 // but only for the most recent request
535 // but only for the most recent request
535 this._finish_iopub(parent_id);
536 this._finish_iopub(parent_id);
536
537
537 // trigger status_idle event
538 // trigger status_idle event
538 this.events.trigger('status_idle.Kernel', {kernel: this});
539 this.events.trigger('status_idle.Kernel', {kernel: this});
539 } else if (execution_state === 'restarting') {
540 } else if (execution_state === 'restarting') {
540 // autorestarting is distinct from restarting,
541 // autorestarting is distinct from restarting,
541 // in that it means the kernel died and the server is restarting it.
542 // in that it means the kernel died and the server is restarting it.
542 // status_restarting sets the notification widget,
543 // status_restarting sets the notification widget,
543 // autorestart shows the more prominent dialog.
544 // autorestart shows the more prominent dialog.
544 this.events.trigger('status_autorestarting.Kernel', {kernel: this});
545 this.events.trigger('status_autorestarting.Kernel', {kernel: this});
545 this.events.trigger('status_restarting.Kernel', {kernel: this});
546 this.events.trigger('status_restarting.Kernel', {kernel: this});
546 } else if (execution_state === 'dead') {
547 } else if (execution_state === 'dead') {
547 this.stop_channels();
548 this.stop_channels();
548 this.events.trigger('status_dead.Kernel', {kernel: this});
549 this.events.trigger('status_dead.Kernel', {kernel: this});
549 }
550 }
550 };
551 };
551
552
552
553
553 // handle clear_output message
554 // handle clear_output message
554 Kernel.prototype._handle_clear_output = function (msg) {
555 Kernel.prototype._handle_clear_output = function (msg) {
555 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
556 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
556 if (!callbacks || !callbacks.iopub) {
557 if (!callbacks || !callbacks.iopub) {
557 return;
558 return;
558 }
559 }
559 var callback = callbacks.iopub.clear_output;
560 var callback = callbacks.iopub.clear_output;
560 if (callback) {
561 if (callback) {
561 callback(msg);
562 callback(msg);
562 }
563 }
563 };
564 };
564
565
565
566
566 // handle an output message (execute_result, display_data, etc.)
567 // handle an output message (execute_result, display_data, etc.)
567 Kernel.prototype._handle_output_message = function (msg) {
568 Kernel.prototype._handle_output_message = function (msg) {
568 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
569 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
569 if (!callbacks || !callbacks.iopub) {
570 if (!callbacks || !callbacks.iopub) {
570 return;
571 return;
571 }
572 }
572 var callback = callbacks.iopub.output;
573 var callback = callbacks.iopub.output;
573 if (callback) {
574 if (callback) {
574 callback(msg);
575 callback(msg);
575 }
576 }
576 };
577 };
577
578
578 // dispatch IOPub messages to respective handlers.
579 // dispatch IOPub messages to respective handlers.
579 // each message type should have a handler.
580 // each message type should have a handler.
580 Kernel.prototype._handle_iopub_message = function (e) {
581 Kernel.prototype._handle_iopub_message = function (e) {
581 var msg = $.parseJSON(e.data);
582 var msg = $.parseJSON(e.data);
582
583
583 var handler = this.get_iopub_handler(msg.header.msg_type);
584 var handler = this.get_iopub_handler(msg.header.msg_type);
584 if (handler !== undefined) {
585 if (handler !== undefined) {
585 handler(msg);
586 handler(msg);
586 }
587 }
587 };
588 };
588
589
589
590
590 Kernel.prototype._handle_input_request = function (e) {
591 Kernel.prototype._handle_input_request = function (e) {
591 var request = $.parseJSON(e.data);
592 var request = $.parseJSON(e.data);
592 var header = request.header;
593 var header = request.header;
593 var content = request.content;
594 var content = request.content;
594 var metadata = request.metadata;
595 var metadata = request.metadata;
595 var msg_type = header.msg_type;
596 var msg_type = header.msg_type;
596 if (msg_type !== 'input_request') {
597 if (msg_type !== 'input_request') {
597 console.log("Invalid input request!", request);
598 console.log("Invalid input request!", request);
598 return;
599 return;
599 }
600 }
600 var callbacks = this.get_callbacks_for_msg(request.parent_header.msg_id);
601 var callbacks = this.get_callbacks_for_msg(request.parent_header.msg_id);
601 if (callbacks) {
602 if (callbacks) {
602 if (callbacks.input) {
603 if (callbacks.input) {
603 callbacks.input(request);
604 callbacks.input(request);
604 }
605 }
605 }
606 }
606 };
607 };
607
608
608 // Backwards compatability.
609 // Backwards compatability.
609 IPython.Kernel = Kernel;
610 IPython.Kernel = Kernel;
610
611
611 return {'Kernel': Kernel};
612 return {'Kernel': Kernel};
612 });
613 });
@@ -1,116 +1,120
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 'services/kernels/js/kernel',
8 'services/kernels/js/kernel',
9 ], function(IPython, $, utils, kernel) {
9 ], function(IPython, $, utils, kernel) {
10 "use strict";
10 "use strict";
11
11
12 var Session = function(options){
12 var Session = function(options){
13 this.kernel = null;
13 this.kernel = null;
14 this.id = null;
14 this.id = null;
15 this.notebook = options.notebook;
15 this.notebook = options.notebook;
16 this.name = options.notebook_name;
16 this.name = options.notebook_name;
17 this.path = options.notebook_path;
17 this.path = options.notebook_path;
18 this.kernel_name = options.kernel_name;
18 this.base_url = options.base_url;
19 this.base_url = options.base_url;
19 };
20 };
20
21
21 Session.prototype.start = function(callback) {
22 Session.prototype.start = function(callback) {
22 var that = this;
23 var that = this;
23 var model = {
24 var model = {
24 notebook : {
25 notebook : {
25 name : this.name,
26 name : this.name,
26 path : this.path
27 path : this.path
28 },
29 kernel : {
30 name : this.kernel_name
27 }
31 }
28 };
32 };
29 var settings = {
33 var settings = {
30 processData : false,
34 processData : false,
31 cache : false,
35 cache : false,
32 type : "POST",
36 type : "POST",
33 data: JSON.stringify(model),
37 data: JSON.stringify(model),
34 dataType : "json",
38 dataType : "json",
35 success : function (data, status, xhr) {
39 success : function (data, status, xhr) {
36 that._handle_start_success(data);
40 that._handle_start_success(data);
37 if (callback) {
41 if (callback) {
38 callback(data, status, xhr);
42 callback(data, status, xhr);
39 }
43 }
40 },
44 },
41 error : utils.log_ajax_error,
45 error : utils.log_ajax_error,
42 };
46 };
43 var url = utils.url_join_encode(this.base_url, 'api/sessions');
47 var url = utils.url_join_encode(this.base_url, 'api/sessions');
44 $.ajax(url, settings);
48 $.ajax(url, settings);
45 };
49 };
46
50
47 Session.prototype.rename_notebook = function (name, path) {
51 Session.prototype.rename_notebook = function (name, path) {
48 this.name = name;
52 this.name = name;
49 this.path = path;
53 this.path = path;
50 var model = {
54 var model = {
51 notebook : {
55 notebook : {
52 name : this.name,
56 name : this.name,
53 path : this.path
57 path : this.path
54 }
58 }
55 };
59 };
56 var settings = {
60 var settings = {
57 processData : false,
61 processData : false,
58 cache : false,
62 cache : false,
59 type : "PATCH",
63 type : "PATCH",
60 data: JSON.stringify(model),
64 data: JSON.stringify(model),
61 dataType : "json",
65 dataType : "json",
62 error : utils.log_ajax_error,
66 error : utils.log_ajax_error,
63 };
67 };
64 var url = utils.url_join_encode(this.base_url, 'api/sessions', this.id);
68 var url = utils.url_join_encode(this.base_url, 'api/sessions', this.id);
65 $.ajax(url, settings);
69 $.ajax(url, settings);
66 };
70 };
67
71
68 Session.prototype.delete = function() {
72 Session.prototype.delete = function() {
69 var settings = {
73 var settings = {
70 processData : false,
74 processData : false,
71 cache : false,
75 cache : false,
72 type : "DELETE",
76 type : "DELETE",
73 dataType : "json",
77 dataType : "json",
74 error : utils.log_ajax_error,
78 error : utils.log_ajax_error,
75 };
79 };
76 this.kernel.running = false;
80 this.kernel.running = false;
77 var url = utils.url_join_encode(this.base_url, 'api/sessions', this.id);
81 var url = utils.url_join_encode(this.base_url, 'api/sessions', this.id);
78 $.ajax(url, settings);
82 $.ajax(url, settings);
79 };
83 };
80
84
81 // Kernel related things
85 // Kernel related things
82 /**
86 /**
83 * Create the Kernel object associated with this Session.
87 * Create the Kernel object associated with this Session.
84 *
88 *
85 * @method _handle_start_success
89 * @method _handle_start_success
86 */
90 */
87 Session.prototype._handle_start_success = function (data, status, xhr) {
91 Session.prototype._handle_start_success = function (data, status, xhr) {
88 this.id = data.id;
92 this.id = data.id;
89 var kernel_service_url = utils.url_path_join(this.base_url, "api/kernels");
93 var kernel_service_url = utils.url_path_join(this.base_url, "api/kernels");
90 this.kernel = new kernel.Kernel(kernel_service_url, this.notebook);
94 this.kernel = new kernel.Kernel(kernel_service_url, this.notebook, this.kernel_name);
91 this.kernel._kernel_started(data.kernel);
95 this.kernel._kernel_started(data.kernel);
92 };
96 };
93
97
94 /**
98 /**
95 * Prompt the user to restart the IPython kernel.
99 * Prompt the user to restart the IPython kernel.
96 *
100 *
97 * @method restart_kernel
101 * @method restart_kernel
98 */
102 */
99 Session.prototype.restart_kernel = function () {
103 Session.prototype.restart_kernel = function () {
100 this.kernel.restart();
104 this.kernel.restart();
101 };
105 };
102
106
103 Session.prototype.interrupt_kernel = function() {
107 Session.prototype.interrupt_kernel = function() {
104 this.kernel.interrupt();
108 this.kernel.interrupt();
105 };
109 };
106
110
107
111
108 Session.prototype.kill_kernel = function() {
112 Session.prototype.kill_kernel = function() {
109 this.kernel.kill();
113 this.kernel.kill();
110 };
114 };
111
115
112 // For backwards compatability.
116 // For backwards compatability.
113 IPython.Session = Session;
117 IPython.Session = Session;
114
118
115 return {'Session': Session};
119 return {'Session': Session};
116 });
120 });
@@ -1,105 +1,105
1 //
1 //
2 // Test saving a notebook with escaped characters
2 // Test saving a notebook with escaped characters
3 //
3 //
4
4
5 casper.notebook_test(function () {
5 casper.notebook_test(function () {
6 // don't use unicode with ambiguous composed/decomposed normalization
6 // don't use unicode with ambiguous composed/decomposed normalization
7 // because the filesystem may use a different normalization than literals.
7 // because the filesystem may use a different normalization than literals.
8 // This causes no actual problems, but will break string comparison.
8 // This causes no actual problems, but will break string comparison.
9 var nbname = "has#hash and space and unicΓΈβˆ‚e.ipynb";
9 var nbname = "has#hash and space and unicΓΈβˆ‚e.ipynb";
10
10
11 this.evaluate(function (nbname) {
11 this.evaluate(function (nbname) {
12 IPython.notebook.notebook_name = nbname;
12 IPython.notebook.notebook_name = nbname;
13 IPython._save_success = IPython._save_failed = false;
13 IPython._save_success = IPython._save_failed = false;
14 IPython.events.on('notebook_saved.Notebook', function () {
14 IPython.events.on('notebook_saved.Notebook', function () {
15 IPython._save_success = true;
15 IPython._save_success = true;
16 });
16 });
17 IPython.events.on('notebook_save_failed.Notebook',
17 IPython.events.on('notebook_save_failed.Notebook',
18 function (event, xhr, status, error) {
18 function (event, xhr, status, error) {
19 IPython._save_failed = "save failed with " + xhr.status + xhr.responseText;
19 IPython._save_failed = "save failed with " + xhr.status + xhr.responseText;
20 });
20 });
21 IPython.notebook.save_notebook();
21 IPython.notebook.save_notebook();
22 }, {nbname:nbname});
22 }, {nbname:nbname});
23
23
24 this.waitFor(function () {
24 this.waitFor(function () {
25 return this.evaluate(function(){
25 return this.evaluate(function(){
26 return IPython._save_failed || IPython._save_success;
26 return IPython._save_failed || IPython._save_success;
27 });
27 });
28 });
28 });
29
29
30 this.then(function(){
30 this.then(function(){
31 var success_failure = this.evaluate(function(){
31 var success_failure = this.evaluate(function(){
32 return [IPython._save_success, IPython._save_failed];
32 return [IPython._save_success, IPython._save_failed];
33 });
33 });
34 this.test.assertEquals(success_failure[1], false, "Save did not fail");
34 this.test.assertEquals(success_failure[1], false, "Save did not fail");
35 this.test.assertEquals(success_failure[0], true, "Save OK");
35 this.test.assertEquals(success_failure[0], true, "Save OK");
36
36
37 var current_name = this.evaluate(function(){
37 var current_name = this.evaluate(function(){
38 return IPython.notebook.notebook_name;
38 return IPython.notebook.notebook_name;
39 });
39 });
40 this.test.assertEquals(current_name, nbname, "Save with complicated name");
40 this.test.assertEquals(current_name, nbname, "Save with complicated name");
41 });
41 });
42
42
43 this.thenEvaluate(function(){
43 this.thenEvaluate(function(){
44 IPython.events.on('checkpoint_created.Notebook', function (evt, data) {
44 IPython.events.on('checkpoint_created.Notebook', function (evt, data) {
45 IPython._checkpoint_created = true;
45 IPython._checkpoint_created = true;
46 });
46 });
47 IPython._checkpoint_created = false;
47 IPython._checkpoint_created = false;
48 IPython.notebook.save_checkpoint();
48 IPython.notebook.save_checkpoint();
49 });
49 });
50
50
51 this.waitFor(function () {
51 this.waitFor(function () {
52 return this.evaluate(function(){
52 return this.evaluate(function(){
53 return IPython._checkpoint_created;
53 return IPython._checkpoint_created;
54 });
54 });
55 });
55 });
56
56
57 this.then(function(){
57 this.then(function(){
58 var checkpoints = this.evaluate(function(){
58 var checkpoints = this.evaluate(function(){
59 return IPython.notebook.checkpoints;
59 return IPython.notebook.checkpoints;
60 });
60 });
61 this.test.assertEquals(checkpoints.length, 1, "checkpoints OK");
61 this.test.assertEquals(checkpoints.length, 1, "checkpoints OK");
62 });
62 });
63
63
64 this.then(function(){
64 this.then(function(){
65 var baseUrl = this.get_notebook_server();
65 var baseUrl = this.get_notebook_server();
66 this.open(baseUrl);
66 this.open(baseUrl);
67 });
67 });
68
68
69 this.waitForSelector('.list_item');
69 this.waitForSelector('.list_item');
70
70
71 this.then(function(){
71 this.then(function(){
72 var notebook_url = this.evaluate(function(nbname){
72 var notebook_url = this.evaluate(function(nbname){
73 var escaped_name = encodeURIComponent(nbname);
73 var escaped_name = encodeURIComponent(nbname);
74 var return_this_thing;
74 var return_this_thing;
75 $("a.item_link").map(function (i,a) {
75 $("a.item_link").map(function (i,a) {
76 if (a.href.indexOf(escaped_name) >= 0) {
76 if (a.href.indexOf(escaped_name) >= 0) {
77 return_this_thing = a.href;
77 return_this_thing = a.href;
78 return;
78 return;
79 }
79 }
80 });
80 });
81 return return_this_thing;
81 return return_this_thing;
82 }, {nbname:nbname});
82 }, {nbname:nbname});
83 this.test.assertEquals(notebook_url == null, false, "Escaped URL in notebook list");
83 this.test.assertNotEquals(notebook_url, null, "Escaped URL in notebook list");
84 // open the notebook
84 // open the notebook
85 this.open(notebook_url);
85 this.open(notebook_url);
86 });
86 });
87
87
88 // wait for the notebook
88 // wait for the notebook
89 this.waitForSelector("#notebook");
89 this.waitForSelector("#notebook");
90
90
91 this.waitFor(function(){
91 this.waitFor(function(){
92 return this.evaluate(function(){
92 return this.evaluate(function(){
93 return IPython.notebook || false;
93 return IPython.notebook || false;
94 });
94 });
95 });
95 });
96
96
97 this.then(function(){
97 this.then(function(){
98 // check that the notebook name is correct
98 // check that the notebook name is correct
99 var notebook_name = this.evaluate(function(){
99 var notebook_name = this.evaluate(function(){
100 return IPython.notebook.notebook_name;
100 return IPython.notebook.notebook_name;
101 });
101 });
102 this.test.assertEquals(notebook_name, nbname, "Notebook name is correct");
102 this.test.assertEquals(notebook_name, nbname, "Notebook name is correct");
103 });
103 });
104
104
105 });
105 });
General Comments 0
You need to be logged in to leave comments. Login now