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