##// END OF EJS Templates
This feature was discussed in #6123, but it doesn't look like anything was ever incorporated into the IPython Notebook....
Nathan Heijermans -
Show More
@@ -1,503 +1,511 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define(['require'
5 5 ], function(require) {
6 6 "use strict";
7 7
8 8 var ActionHandler = function (env) {
9 9 this.env = env || {};
10 10 Object.seal(this);
11 11 };
12 12
13 13 /**
14 14 * A bunch of predefined `Simple Actions` used by IPython.
15 15 * `Simple Actions` have the following keys:
16 16 * help (optional): a short string the describe the action.
17 17 * will be used in various context, like as menu name, tool tips on buttons,
18 18 * and short description in help menu.
19 19 * help_index (optional): a string used to sort action in help menu.
20 20 * icon (optional): a short string that represent the icon that have to be used with this
21 21 * action. this should mainly correspond to a Font_awesome class.
22 22 * handler : a function which is called when the action is activated. It will receive at first parameter
23 23 * a dictionary containing various handle to element of the notebook.
24 24 *
25 25 * action need to be registered with a **name** that can be use to refer to this action.
26 26 *
27 27 *
28 28 * if `help` is not provided it will be derived by replacing any dash by space
29 29 * in the **name** of the action. It is advised to provide a prefix to action name to
30 30 * avoid conflict the prefix should be all lowercase and end with a dot `.`
31 31 * in the absence of a prefix the behavior of the action is undefined.
32 32 *
33 33 * All action provided by IPython are prefixed with `ipython.`.
34 34 *
35 35 * One can register extra actions or replace an existing action with another one is possible
36 36 * but is considered undefined behavior.
37 37 *
38 38 **/
39 39 var _action = {
40 40 'run-select-next': {
41 41 icon: 'fa-play',
42 42 help : 'run cell, select below',
43 43 help_index : 'ba',
44 44 handler : function (env) {
45 45 env.notebook.execute_cell_and_select_below();
46 46 }
47 47 },
48 48 'execute-in-place':{
49 49 help : 'run cell',
50 50 help_index : 'bb',
51 51 handler : function (env) {
52 52 env.notebook.execute_cell();
53 53 }
54 54 },
55 55 'execute-and-insert-after':{
56 56 help : 'run cell, insert below',
57 57 help_index : 'bc',
58 58 handler : function (env) {
59 59 env.notebook.execute_cell_and_insert_below();
60 60 }
61 61 },
62 62 'go-to-command-mode': {
63 63 help : 'command mode',
64 64 help_index : 'aa',
65 65 handler : function (env) {
66 66 env.notebook.command_mode();
67 67 }
68 68 },
69 69 'split-cell-at-cursor': {
70 70 help : 'split cell',
71 71 help_index : 'ea',
72 72 handler : function (env) {
73 73 env.notebook.split_cell();
74 74 }
75 75 },
76 76 'enter-edit-mode' : {
77 77 help_index : 'aa',
78 78 handler : function (env) {
79 79 env.notebook.edit_mode();
80 80 }
81 81 },
82 82 'select-previous-cell' : {
83 83 help_index : 'da',
84 84 handler : function (env) {
85 85 var index = env.notebook.get_selected_index();
86 86 if (index !== 0 && index !== null) {
87 87 env.notebook.select_prev();
88 88 env.notebook.focus_cell();
89 89 }
90 90 }
91 91 },
92 92 'select-next-cell' : {
93 93 help_index : 'db',
94 94 handler : function (env) {
95 95 var index = env.notebook.get_selected_index();
96 96 if (index !== (env.notebook.ncells()-1) && index !== null) {
97 97 env.notebook.select_next();
98 98 env.notebook.focus_cell();
99 99 }
100 100 }
101 101 },
102 102 'cut-selected-cell' : {
103 103 icon: 'fa-cut',
104 104 help_index : 'ee',
105 105 handler : function (env) {
106 106 env.notebook.cut_cell();
107 107 }
108 108 },
109 109 'copy-selected-cell' : {
110 110 icon: 'fa-copy',
111 111 help_index : 'ef',
112 112 handler : function (env) {
113 113 env.notebook.copy_cell();
114 114 }
115 115 },
116 116 'paste-cell-before' : {
117 117 help_index : 'eg',
118 118 handler : function (env) {
119 119 env.notebook.paste_cell_above();
120 120 }
121 121 },
122 122 'paste-cell-after' : {
123 123 icon: 'fa-paste',
124 124 help_index : 'eh',
125 125 handler : function (env) {
126 126 env.notebook.paste_cell_below();
127 127 }
128 128 },
129 129 'insert-cell-before' : {
130 130 help_index : 'ec',
131 131 handler : function (env) {
132 132 env.notebook.insert_cell_above();
133 133 env.notebook.select_prev();
134 134 env.notebook.focus_cell();
135 135 }
136 136 },
137 137 'insert-cell-after' : {
138 138 icon : 'fa-plus',
139 139 help_index : 'ed',
140 140 handler : function (env) {
141 141 env.notebook.insert_cell_below();
142 142 env.notebook.select_next();
143 143 env.notebook.focus_cell();
144 144 }
145 145 },
146 146 'change-selected-cell-to-code-cell' : {
147 147 help : 'to code',
148 148 help_index : 'ca',
149 149 handler : function (env) {
150 150 env.notebook.to_code();
151 151 }
152 152 },
153 153 'change-selected-cell-to-markdown-cell' : {
154 154 help : 'to markdown',
155 155 help_index : 'cb',
156 156 handler : function (env) {
157 157 env.notebook.to_markdown();
158 158 }
159 159 },
160 160 'change-selected-cell-to-raw-cell' : {
161 161 help : 'to raw',
162 162 help_index : 'cc',
163 163 handler : function (env) {
164 164 env.notebook.to_raw();
165 165 }
166 166 },
167 167 'change-selected-cell-to-heading-1' : {
168 168 help : 'to heading 1',
169 169 help_index : 'cd',
170 170 handler : function (env) {
171 171 env.notebook.to_heading(undefined, 1);
172 172 }
173 173 },
174 174 'change-selected-cell-to-heading-2' : {
175 175 help : 'to heading 2',
176 176 help_index : 'ce',
177 177 handler : function (env) {
178 178 env.notebook.to_heading(undefined, 2);
179 179 }
180 180 },
181 181 'change-selected-cell-to-heading-3' : {
182 182 help : 'to heading 3',
183 183 help_index : 'cf',
184 184 handler : function (env) {
185 185 env.notebook.to_heading(undefined, 3);
186 186 }
187 187 },
188 188 'change-selected-cell-to-heading-4' : {
189 189 help : 'to heading 4',
190 190 help_index : 'cg',
191 191 handler : function (env) {
192 192 env.notebook.to_heading(undefined, 4);
193 193 }
194 194 },
195 195 'change-selected-cell-to-heading-5' : {
196 196 help : 'to heading 5',
197 197 help_index : 'ch',
198 198 handler : function (env) {
199 199 env.notebook.to_heading(undefined, 5);
200 200 }
201 201 },
202 202 'change-selected-cell-to-heading-6' : {
203 203 help : 'to heading 6',
204 204 help_index : 'ci',
205 205 handler : function (env) {
206 206 env.notebook.to_heading(undefined, 6);
207 207 }
208 208 },
209 209 'toggle-output-visibility-selected-cell' : {
210 210 help : 'toggle output',
211 211 help_index : 'gb',
212 212 handler : function (env) {
213 213 env.notebook.toggle_output();
214 214 }
215 215 },
216 216 'toggle-output-scrolling-selected-cell' : {
217 217 help : 'toggle output scrolling',
218 218 help_index : 'gc',
219 219 handler : function (env) {
220 220 env.notebook.toggle_output_scroll();
221 221 }
222 222 },
223 223 'move-selected-cell-down' : {
224 224 icon: 'fa-arrow-down',
225 225 help_index : 'eb',
226 226 handler : function (env) {
227 227 env.notebook.move_cell_down();
228 228 }
229 229 },
230 230 'move-selected-cell-up' : {
231 231 icon: 'fa-arrow-up',
232 232 help_index : 'ea',
233 233 handler : function (env) {
234 234 env.notebook.move_cell_up();
235 235 }
236 236 },
237 237 'toggle-line-number-selected-cell' : {
238 238 help : 'toggle line numbers',
239 239 help_index : 'ga',
240 240 handler : function (env) {
241 241 env.notebook.cell_toggle_line_numbers();
242 242 }
243 243 },
244 244 'show-keyboard-shortcut-help-dialog' : {
245 245 help_index : 'ge',
246 246 handler : function (env) {
247 247 env.quick_help.show_keyboard_shortcuts();
248 248 }
249 249 },
250 250 'delete-cell': {
251 251 help_index : 'ej',
252 252 handler : function (env) {
253 253 env.notebook.delete_cell();
254 254 }
255 255 },
256 'toggle-unsolicited-message-display':{
257 help: 'toggle display from external clients',
258 icon: 'fa-sitemap',
259 help_index: 'gb',
260 handler: function (env) {
261 env.notebook.toggle_ignore_unsolicited_msgs();
262 }
263 },
256 264 'interrupt-kernel':{
257 265 icon: 'fa-stop',
258 266 help_index : 'ha',
259 267 handler : function (env) {
260 268 env.notebook.kernel.interrupt();
261 269 }
262 270 },
263 271 'restart-kernel':{
264 272 icon: 'fa-repeat',
265 273 help_index : 'hb',
266 274 handler : function (env) {
267 275 env.notebook.restart_kernel();
268 276 }
269 277 },
270 278 'undo-last-cell-deletion' : {
271 279 help_index : 'ei',
272 280 handler : function (env) {
273 281 env.notebook.undelete_cell();
274 282 }
275 283 },
276 284 'merge-selected-cell-with-cell-after' : {
277 285 help : 'merge cell below',
278 286 help_index : 'ek',
279 287 handler : function (env) {
280 288 env.notebook.merge_cell_below();
281 289 }
282 290 },
283 291 'close-pager' : {
284 292 help_index : 'gd',
285 293 handler : function (env) {
286 294 env.pager.collapse();
287 295 }
288 296 }
289 297
290 298 };
291 299
292 300 /**
293 301 * A bunch of `Advance actions` for IPython.
294 302 * Cf `Simple Action` plus the following properties.
295 303 *
296 304 * handler: first argument of the handler is the event that triggerd the action
297 305 * (typically keypress). The handler is responsible for any modification of the
298 306 * event and event propagation.
299 307 * Is also responsible for returning false if the event have to be further ignored,
300 308 * true, to tell keyboard manager that it ignored the event.
301 309 *
302 310 * the second parameter of the handler is the environemnt passed to Simple Actions
303 311 *
304 312 **/
305 313 var custom_ignore = {
306 314 'ignore':{
307 315 handler : function () {
308 316 return true;
309 317 }
310 318 },
311 319 'move-cursor-up-or-previous-cell':{
312 320 handler : function (env, event) {
313 321 var index = env.notebook.get_selected_index();
314 322 var cell = env.notebook.get_cell(index);
315 323 var cm = env.notebook.get_selected_cell().code_mirror;
316 324 var cur = cm.getCursor();
317 325 if (cell && cell.at_top() && index !== 0 && cur.ch === 0) {
318 326 if(event){
319 327 event.preventDefault();
320 328 }
321 329 env.notebook.command_mode();
322 330 env.notebook.select_prev();
323 331 env.notebook.edit_mode();
324 332 cm = env.notebook.get_selected_cell().code_mirror;
325 333 cm.setCursor(cm.lastLine(), 0);
326 334 }
327 335 return false;
328 336 }
329 337 },
330 338 'move-cursor-down-or-next-cell':{
331 339 handler : function (env, event) {
332 340 var index = env.notebook.get_selected_index();
333 341 var cell = env.notebook.get_cell(index);
334 342 if (cell.at_bottom() && index !== (env.notebook.ncells()-1)) {
335 343 if(event){
336 344 event.preventDefault();
337 345 }
338 346 env.notebook.command_mode();
339 347 env.notebook.select_next();
340 348 env.notebook.edit_mode();
341 349 var cm = env.notebook.get_selected_cell().code_mirror;
342 350 cm.setCursor(0, 0);
343 351 }
344 352 return false;
345 353 }
346 354 },
347 355 'scroll-down': {
348 356 handler: function(env, event) {
349 357 if(event){
350 358 event.preventDefault();
351 359 }
352 360 return env.notebook.scroll_manager.scroll(1);
353 361 },
354 362 },
355 363 'scroll-up': {
356 364 handler: function(env, event) {
357 365 if(event){
358 366 event.preventDefault();
359 367 }
360 368 return env.notebook.scroll_manager.scroll(-1);
361 369 },
362 370 },
363 371 'save-notebook':{
364 372 help: "Save and Checkpoint",
365 373 help_index : 'fb',
366 374 icon: 'fa-save',
367 375 handler : function (env, event) {
368 376 env.notebook.save_checkpoint();
369 377 if(event){
370 378 event.preventDefault();
371 379 }
372 380 return false;
373 381 }
374 382 },
375 383 };
376 384
377 385 // private stuff that prepend `.ipython` to actions names
378 386 // and uniformize/fill in missing pieces in of an action.
379 387 var _prepare_handler = function(registry, subkey, source){
380 388 registry['ipython.'+subkey] = {};
381 389 registry['ipython.'+subkey].help = source[subkey].help||subkey.replace(/-/g,' ');
382 390 registry['ipython.'+subkey].help_index = source[subkey].help_index;
383 391 registry['ipython.'+subkey].icon = source[subkey].icon;
384 392 return source[subkey].handler;
385 393 };
386 394
387 395 // Will actually generate/register all the IPython actions
388 396 var fun = function(){
389 397 var final_actions = {};
390 398 for(var k in _action){
391 399 // Js closure are function level not block level need to wrap in a IIFE
392 400 // and append ipython to event name these things do intercept event so are wrapped
393 401 // in a function that return false.
394 402 var handler = _prepare_handler(final_actions, k, _action);
395 403 (function(key, handler){
396 404 final_actions['ipython.'+key].handler = function(env, event){
397 405 handler(env);
398 406 if(event){
399 407 event.preventDefault();
400 408 }
401 409 return false;
402 410 };
403 411 })(k, handler);
404 412 }
405 413
406 414 for(var k in custom_ignore){
407 415 // Js closure are function level not block level need to wrap in a IIFE
408 416 // same as above, but decide for themselves wether or not they intercept events.
409 417 var handler = _prepare_handler(final_actions, k, custom_ignore);
410 418 (function(key, handler){
411 419 final_actions['ipython.'+key].handler = function(env, event){
412 420 return handler(env, event);
413 421 };
414 422 })(k, handler);
415 423 }
416 424
417 425 return final_actions;
418 426 };
419 427 ActionHandler.prototype._actions = fun();
420 428
421 429
422 430 /**
423 431 * extend the environment variable that will be pass to handlers
424 432 **/
425 433 ActionHandler.prototype.extend_env = function(env){
426 434 for(var k in env){
427 435 this.env[k] = env[k];
428 436 }
429 437 };
430 438
431 439 ActionHandler.prototype.register = function(action, name, prefix){
432 440 /**
433 441 * Register an `action` with an optional name and prefix.
434 442 *
435 443 * if name and prefix are not given they will be determined automatically.
436 444 * if action if just a `function` it will be wrapped in an anonymous action.
437 445 *
438 446 * @return the full name to access this action .
439 447 **/
440 448 action = this.normalise(action);
441 449 if( !name ){
442 450 name = 'autogenerated-'+String(action.handler);
443 451 }
444 452 prefix = prefix || 'auto';
445 453 var full_name = prefix+'.'+name;
446 454 this._actions[full_name] = action;
447 455 return full_name;
448 456
449 457 };
450 458
451 459
452 460 ActionHandler.prototype.normalise = function(data){
453 461 /**
454 462 * given an `action` or `function`, return a normalised `action`
455 463 * by setting all known attributes and removing unknown attributes;
456 464 **/
457 465 if(typeof(data) === 'function'){
458 466 data = {handler:data};
459 467 }
460 468 if(typeof(data.handler) !== 'function'){
461 469 throw('unknown datatype, cannot register');
462 470 }
463 471 var _data = data;
464 472 data = {};
465 473 data.handler = _data.handler;
466 474 data.help = data.help || '';
467 475 data.icon = data.icon || '';
468 476 data.help_index = data.help_index || '';
469 477 return data;
470 478 };
471 479
472 480 ActionHandler.prototype.get_name = function(name_or_data){
473 481 /**
474 482 * given an `action` or `name` of a action, return the name attached to this action.
475 483 * if given the name of and corresponding actions does not exist in registry, return `null`.
476 484 **/
477 485
478 486 if(typeof(name_or_data) === 'string'){
479 487 if(this.exists(name_or_data)){
480 488 return name_or_data;
481 489 } else {
482 490 return null;
483 491 }
484 492 } else {
485 493 return this.register(name_or_data);
486 494 }
487 495 };
488 496
489 497 ActionHandler.prototype.get = function(name){
490 498 return this._actions[name];
491 499 };
492 500
493 501 ActionHandler.prototype.call = function(name, event, env){
494 502 return this._actions[name].handler(env|| this.env, event);
495 503 };
496 504
497 505 ActionHandler.prototype.exists = function(name){
498 506 return (typeof(this._actions[name]) !== 'undefined');
499 507 };
500 508
501 509 return {init:ActionHandler};
502 510
503 511 });
@@ -1,232 +1,233 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3 /**
4 4 *
5 5 *
6 6 * @module keyboardmanager
7 7 * @namespace keyboardmanager
8 8 * @class KeyboardManager
9 9 */
10 10
11 11 define([
12 12 'base/js/namespace',
13 13 'jquery',
14 14 'base/js/utils',
15 15 'base/js/keyboard',
16 16 ], function(IPython, $, utils, keyboard) {
17 17 "use strict";
18 18
19 19 // Main keyboard manager for the notebook
20 20 var keycodes = keyboard.keycodes;
21 21
22 22 var KeyboardManager = function (options) {
23 23 /**
24 24 * A class to deal with keyboard event and shortcut
25 25 *
26 26 * @class KeyboardManager
27 27 * @constructor
28 28 * @param options {dict} Dictionary of keyword arguments :
29 29 * @param options.events {$(Events)} instance
30 30 * @param options.pager: {Pager} pager instance
31 31 */
32 32 this.mode = 'command';
33 33 this.enabled = true;
34 34 this.pager = options.pager;
35 35 this.quick_help = undefined;
36 36 this.notebook = undefined;
37 37 this.last_mode = undefined;
38 38 this.bind_events();
39 39 this.env = {pager:this.pager};
40 40 this.actions = options.actions;
41 41 this.command_shortcuts = new keyboard.ShortcutManager(undefined, options.events, this.actions, this.env );
42 42 this.command_shortcuts.add_shortcuts(this.get_default_common_shortcuts());
43 43 this.command_shortcuts.add_shortcuts(this.get_default_command_shortcuts());
44 44 this.edit_shortcuts = new keyboard.ShortcutManager(undefined, options.events, this.actions, this.env);
45 45 this.edit_shortcuts.add_shortcuts(this.get_default_common_shortcuts());
46 46 this.edit_shortcuts.add_shortcuts(this.get_default_edit_shortcuts());
47 47 Object.seal(this);
48 48 };
49 49
50 50
51 51
52 52
53 53 /**
54 54 * Return a dict of common shortcut
55 55 * @method get_default_common_shortcuts
56 56 *
57 57 * @example Example of returned shortcut
58 58 * ```
59 59 * 'shortcut-key': 'action-name'
60 60 * // a string representing the shortcut as dash separated value.
61 61 * // e.g. 'shift' , 'shift-enter', 'cmd-t'
62 62 *```
63 63 */
64 64 KeyboardManager.prototype.get_default_common_shortcuts = function() {
65 65 return {
66 66 'shift' : 'ipython.ignore',
67 67 'shift-enter' : 'ipython.run-select-next',
68 68 'ctrl-enter' : 'ipython.execute-in-place',
69 69 'alt-enter' : 'ipython.execute-and-insert-after',
70 70 // cmd on mac, ctrl otherwise
71 71 'cmdtrl-s' : 'ipython.save-notebook',
72 72 };
73 73 };
74 74
75 75 KeyboardManager.prototype.get_default_edit_shortcuts = function() {
76 76 return {
77 77 'esc' : 'ipython.go-to-command-mode',
78 78 'ctrl-m' : 'ipython.go-to-command-mode',
79 79 'up' : 'ipython.move-cursor-up-or-previous-cell',
80 80 'down' : 'ipython.move-cursor-down-or-next-cell',
81 81 'ctrl-shift--' : 'ipython.split-cell-at-cursor',
82 82 'ctrl-shift-subtract' : 'ipython.split-cell-at-cursor'
83 83 };
84 84 };
85 85
86 86 KeyboardManager.prototype.get_default_command_shortcuts = function() {
87 87 return {
88 88 'shift-space': 'ipython.scroll-up',
89 89 'shift-v' : 'ipython.paste-cell-before',
90 90 'shift-m' : 'ipython.merge-selected-cell-with-cell-after',
91 91 'shift-o' : 'ipython.toggle-output-scrolling-selected-cell',
92 92 'ctrl-j' : 'ipython.move-selected-cell-down',
93 93 'ctrl-k' : 'ipython.move-selected-cell-up',
94 94 'enter' : 'ipython.enter-edit-mode',
95 95 'space' : 'ipython.scroll-down',
96 96 'down' : 'ipython.select-next-cell',
97 97 'i,i' : 'ipython.interrupt-kernel',
98 'e': 'ipython.toggle-unsolicited-message-display',
98 99 '0,0' : 'ipython.restart-kernel',
99 100 'd,d' : 'ipython.delete-cell',
100 101 'esc': 'ipython.close-pager',
101 102 'up' : 'ipython.select-previous-cell',
102 103 'k' : 'ipython.select-previous-cell',
103 104 'j' : 'ipython.select-next-cell',
104 105 'x' : 'ipython.cut-selected-cell',
105 106 'c' : 'ipython.copy-selected-cell',
106 107 'v' : 'ipython.paste-cell-after',
107 108 'a' : 'ipython.insert-cell-before',
108 109 'b' : 'ipython.insert-cell-after',
109 110 'y' : 'ipython.change-selected-cell-to-code-cell',
110 111 'm' : 'ipython.change-selected-cell-to-markdown-cell',
111 112 'r' : 'ipython.change-selected-cell-to-raw-cell',
112 113 '1' : 'ipython.change-selected-cell-to-heading-1',
113 114 '2' : 'ipython.change-selected-cell-to-heading-2',
114 115 '3' : 'ipython.change-selected-cell-to-heading-3',
115 116 '4' : 'ipython.change-selected-cell-to-heading-4',
116 117 '5' : 'ipython.change-selected-cell-to-heading-5',
117 118 '6' : 'ipython.change-selected-cell-to-heading-6',
118 119 'o' : 'ipython.toggle-output-visibility-selected-cell',
119 120 's' : 'ipython.save-notebook',
120 121 'l' : 'ipython.toggle-line-number-selected-cell',
121 122 'h' : 'ipython.show-keyboard-shortcut-help-dialog',
122 123 'z' : 'ipython.undo-last-cell-deletion',
123 124 'q' : 'ipython.close-pager',
124 125 };
125 126 };
126 127
127 128 KeyboardManager.prototype.bind_events = function () {
128 129 var that = this;
129 130 $(document).keydown(function (event) {
130 131 if(event._ipkmIgnore===true||(event.originalEvent||{})._ipkmIgnore===true){
131 132 return false;
132 133 }
133 134 return that.handle_keydown(event);
134 135 });
135 136 };
136 137
137 138 KeyboardManager.prototype.set_notebook = function (notebook) {
138 139 this.notebook = notebook;
139 140 this.actions.extend_env({notebook:notebook});
140 141 };
141 142
142 143 KeyboardManager.prototype.set_quickhelp = function (notebook) {
143 144 this.actions.extend_env({quick_help:notebook});
144 145 };
145 146
146 147
147 148 KeyboardManager.prototype.handle_keydown = function (event) {
148 149 /**
149 150 * returning false from this will stop event propagation
150 151 **/
151 152
152 153 if (event.which === keycodes.esc) {
153 154 // Intercept escape at highest level to avoid closing
154 155 // websocket connection with firefox
155 156 event.preventDefault();
156 157 }
157 158
158 159 if (!this.enabled) {
159 160 if (event.which === keycodes.esc) {
160 161 this.notebook.command_mode();
161 162 return false;
162 163 }
163 164 return true;
164 165 }
165 166
166 167 if (this.mode === 'edit') {
167 168 return this.edit_shortcuts.call_handler(event);
168 169 } else if (this.mode === 'command') {
169 170 return this.command_shortcuts.call_handler(event);
170 171 }
171 172 return true;
172 173 };
173 174
174 175 KeyboardManager.prototype.edit_mode = function () {
175 176 this.last_mode = this.mode;
176 177 this.mode = 'edit';
177 178 };
178 179
179 180 KeyboardManager.prototype.command_mode = function () {
180 181 this.last_mode = this.mode;
181 182 this.mode = 'command';
182 183 };
183 184
184 185 KeyboardManager.prototype.enable = function () {
185 186 this.enabled = true;
186 187 };
187 188
188 189 KeyboardManager.prototype.disable = function () {
189 190 this.enabled = false;
190 191 };
191 192
192 193 KeyboardManager.prototype.register_events = function (e) {
193 194 var that = this;
194 195 var handle_focus = function () {
195 196 that.disable();
196 197 };
197 198 var handle_blur = function () {
198 199 that.enable();
199 200 };
200 201 e.on('focusin', handle_focus);
201 202 e.on('focusout', handle_blur);
202 203 // TODO: Very strange. The focusout event does not seem fire for the
203 204 // bootstrap textboxes on FF25&26... This works around that by
204 205 // registering focus and blur events recursively on all inputs within
205 206 // registered element.
206 207 e.find('input').blur(handle_blur);
207 208 e.on('DOMNodeInserted', function (event) {
208 209 var target = $(event.target);
209 210 if (target.is('input')) {
210 211 target.blur(handle_blur);
211 212 } else {
212 213 target.find('input').blur(handle_blur);
213 214 }
214 215 });
215 216 // There are times (raw_input) where we remove the element from the DOM before
216 217 // focusout is called. In this case we bind to the remove event of jQueryUI,
217 218 // which gets triggered upon removal, iff it is focused at the time.
218 219 // is_focused must be used to check for the case where an element within
219 220 // the element being removed is focused.
220 221 e.on('remove', function () {
221 222 if (utils.is_focused(e[0])) {
222 223 that.enable();
223 224 }
224 225 });
225 226 };
226 227
227 228
228 229 // For backwards compatibility.
229 230 IPython.KeyboardManager = KeyboardManager;
230 231
231 232 return {'KeyboardManager': KeyboardManager};
232 233 });
@@ -1,383 +1,386 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'jquery',
6 6 'base/js/namespace',
7 7 'base/js/dialog',
8 8 'base/js/utils',
9 9 'notebook/js/tour',
10 10 'bootstrap',
11 11 'moment',
12 12 ], function($, IPython, dialog, utils, tour, bootstrap, moment) {
13 13 "use strict";
14 14
15 15 var MenuBar = function (selector, options) {
16 16 // Constructor
17 17 //
18 18 // A MenuBar Class to generate the menubar of IPython notebook
19 19 //
20 20 // Parameters:
21 21 // selector: string
22 22 // options: dictionary
23 23 // Dictionary of keyword arguments.
24 24 // notebook: Notebook instance
25 25 // contents: ContentManager instance
26 26 // layout_manager: LayoutManager instance
27 27 // events: $(Events) instance
28 28 // save_widget: SaveWidget instance
29 29 // quick_help: QuickHelp instance
30 30 // base_url : string
31 31 // notebook_path : string
32 32 // notebook_name : string
33 33 options = options || {};
34 34 this.base_url = options.base_url || utils.get_body_data("baseUrl");
35 35 this.selector = selector;
36 36 this.notebook = options.notebook;
37 37 this.contents = options.contents;
38 38 this.layout_manager = options.layout_manager;
39 39 this.events = options.events;
40 40 this.save_widget = options.save_widget;
41 41 this.quick_help = options.quick_help;
42 42
43 43 try {
44 44 this.tour = new tour.Tour(this.notebook, this.events);
45 45 } catch (e) {
46 46 this.tour = undefined;
47 47 console.log("Failed to instantiate Notebook Tour", e);
48 48 }
49 49
50 50 if (this.selector !== undefined) {
51 51 this.element = $(selector);
52 52 this.style();
53 53 this.bind_events();
54 54 }
55 55 };
56 56
57 57 // TODO: This has definitively nothing to do with style ...
58 58 MenuBar.prototype.style = function () {
59 59 var that = this;
60 60 this.element.find("li").click(function (event, ui) {
61 61 // The selected cell loses focus when the menu is entered, so we
62 62 // re-select it upon selection.
63 63 var i = that.notebook.get_selected_index();
64 64 that.notebook.select(i);
65 65 }
66 66 );
67 67 };
68 68
69 69 MenuBar.prototype._nbconvert = function (format, download) {
70 70 download = download || false;
71 71 var notebook_path = this.notebook.notebook_path;
72 72 var url = utils.url_join_encode(
73 73 this.base_url,
74 74 'nbconvert',
75 75 format,
76 76 notebook_path
77 77 ) + "?download=" + download.toString();
78 78
79 79 var w = window.open()
80 80 if (this.notebook.dirty) {
81 81 this.notebook.save_notebook().then(function() {
82 82 w.location = url;
83 83 });
84 84 } else {
85 85 w.location = url;
86 86 }
87 87 };
88 88
89 89 MenuBar.prototype.bind_events = function () {
90 90 // File
91 91 var that = this;
92 92 this.element.find('#new_notebook').click(function () {
93 93 var w = window.open();
94 94 // Create a new notebook in the same path as the current
95 95 // notebook's path.
96 96 var parent = utils.url_path_split(that.notebook.notebook_path)[0];
97 97 that.contents.new_untitled(parent, {type: "notebook"}).then(
98 98 function (data) {
99 99 w.location = utils.url_join_encode(
100 100 that.base_url, 'notebooks', data.path
101 101 );
102 102 },
103 103 function(error) {
104 104 w.close();
105 105 dialog.modal({
106 106 title : 'Creating Notebook Failed',
107 107 body : "The error was: " + error.message,
108 108 buttons : {'OK' : {'class' : 'btn-primary'}}
109 109 });
110 110 }
111 111 );
112 112 });
113 113 this.element.find('#open_notebook').click(function () {
114 114 var parent = utils.url_path_split(that.notebook.notebook_path)[0];
115 115 window.open(utils.url_join_encode(that.base_url, 'tree', parent));
116 116 });
117 117 this.element.find('#copy_notebook').click(function () {
118 118 that.notebook.copy_notebook();
119 119 return false;
120 120 });
121 121 this.element.find('#download_ipynb').click(function () {
122 122 var base_url = that.notebook.base_url;
123 123 var notebook_path = that.notebook.notebook_path;
124 124 if (that.notebook.dirty) {
125 125 that.notebook.save_notebook({async : false});
126 126 }
127 127
128 128 var url = utils.url_join_encode(base_url, 'files', notebook_path);
129 129 window.open(url + '?download=1');
130 130 });
131 131
132 132 this.element.find('#print_preview').click(function () {
133 133 that._nbconvert('html', false);
134 134 });
135 135
136 136 this.element.find('#download_html').click(function () {
137 137 that._nbconvert('html', true);
138 138 });
139 139
140 140 this.element.find('#download_rst').click(function () {
141 141 that._nbconvert('rst', true);
142 142 });
143 143
144 144 this.element.find('#download_pdf').click(function () {
145 145 that._nbconvert('pdf', true);
146 146 });
147 147
148 148 this.element.find('#rename_notebook').click(function () {
149 149 that.save_widget.rename_notebook({notebook: that.notebook});
150 150 });
151 151 this.element.find('#save_checkpoint').click(function () {
152 152 that.notebook.save_checkpoint();
153 153 });
154 154 this.element.find('#restore_checkpoint').click(function () {
155 155 });
156 156 this.element.find('#trust_notebook').click(function () {
157 157 that.notebook.trust_notebook();
158 158 });
159 159 this.events.on('trust_changed.Notebook', function (event, trusted) {
160 160 if (trusted) {
161 161 that.element.find('#trust_notebook')
162 162 .addClass("disabled")
163 163 .find("a").text("Trusted Notebook");
164 164 } else {
165 165 that.element.find('#trust_notebook')
166 166 .removeClass("disabled")
167 167 .find("a").text("Trust Notebook");
168 168 }
169 169 });
170 170 this.element.find('#kill_and_exit').click(function () {
171 171 var close_window = function () {
172 172 // allow closing of new tabs in Chromium, impossible in FF
173 173 window.open('', '_self', '');
174 174 window.close();
175 175 };
176 176 // finish with close on success or failure
177 177 that.notebook.session.delete(close_window, close_window);
178 178 });
179 179 // Edit
180 180 this.element.find('#cut_cell').click(function () {
181 181 that.notebook.cut_cell();
182 182 });
183 183 this.element.find('#copy_cell').click(function () {
184 184 that.notebook.copy_cell();
185 185 });
186 186 this.element.find('#delete_cell').click(function () {
187 187 that.notebook.delete_cell();
188 188 });
189 189 this.element.find('#undelete_cell').click(function () {
190 190 that.notebook.undelete_cell();
191 191 });
192 192 this.element.find('#split_cell').click(function () {
193 193 that.notebook.split_cell();
194 194 });
195 195 this.element.find('#merge_cell_above').click(function () {
196 196 that.notebook.merge_cell_above();
197 197 });
198 198 this.element.find('#merge_cell_below').click(function () {
199 199 that.notebook.merge_cell_below();
200 200 });
201 201 this.element.find('#move_cell_up').click(function () {
202 202 that.notebook.move_cell_up();
203 203 });
204 204 this.element.find('#move_cell_down').click(function () {
205 205 that.notebook.move_cell_down();
206 206 });
207 207 this.element.find('#edit_nb_metadata').click(function () {
208 208 that.notebook.edit_metadata({
209 209 notebook: that.notebook,
210 210 keyboard_manager: that.notebook.keyboard_manager});
211 211 });
212 212
213 213 // View
214 214 this.element.find('#toggle_header').click(function () {
215 215 $('div#header').toggle();
216 216 that.layout_manager.do_resize();
217 217 });
218 218 this.element.find('#toggle_toolbar').click(function () {
219 219 $('div#maintoolbar').toggle();
220 220 that.layout_manager.do_resize();
221 221 });
222 222 // Insert
223 223 this.element.find('#insert_cell_above').click(function () {
224 224 that.notebook.insert_cell_above('code');
225 225 that.notebook.select_prev();
226 226 });
227 227 this.element.find('#insert_cell_below').click(function () {
228 228 that.notebook.insert_cell_below('code');
229 229 that.notebook.select_next();
230 230 });
231 231 // Cell
232 232 this.element.find('#run_cell').click(function () {
233 233 that.notebook.execute_cell();
234 234 });
235 235 this.element.find('#run_cell_select_below').click(function () {
236 236 that.notebook.execute_cell_and_select_below();
237 237 });
238 238 this.element.find('#run_cell_insert_below').click(function () {
239 239 that.notebook.execute_cell_and_insert_below();
240 240 });
241 241 this.element.find('#run_all_cells').click(function () {
242 242 that.notebook.execute_all_cells();
243 243 });
244 244 this.element.find('#run_all_cells_above').click(function () {
245 245 that.notebook.execute_cells_above();
246 246 });
247 247 this.element.find('#run_all_cells_below').click(function () {
248 248 that.notebook.execute_cells_below();
249 249 });
250 250 this.element.find('#to_code').click(function () {
251 251 that.notebook.to_code();
252 252 });
253 253 this.element.find('#to_markdown').click(function () {
254 254 that.notebook.to_markdown();
255 255 });
256 256 this.element.find('#to_raw').click(function () {
257 257 that.notebook.to_raw();
258 258 });
259 259
260 260 this.element.find('#toggle_current_output').click(function () {
261 261 that.notebook.toggle_output();
262 262 });
263 263 this.element.find('#toggle_current_output_scroll').click(function () {
264 264 that.notebook.toggle_output_scroll();
265 265 });
266 266 this.element.find('#clear_current_output').click(function () {
267 267 that.notebook.clear_output();
268 268 });
269 269
270 270 this.element.find('#toggle_all_output').click(function () {
271 271 that.notebook.toggle_all_output();
272 272 });
273 273 this.element.find('#toggle_all_output_scroll').click(function () {
274 274 that.notebook.toggle_all_output_scroll();
275 275 });
276 276 this.element.find('#clear_all_output').click(function () {
277 277 that.notebook.clear_all_output();
278 278 });
279 279
280 280 // Kernel
281 this.element.find('#toggle_unsolicited').click(function() {
282 IPython.notebook.toggle_ignore_unsolicited_msgs();
283 });
281 284 this.element.find('#int_kernel').click(function () {
282 285 that.notebook.kernel.interrupt();
283 286 });
284 287 this.element.find('#restart_kernel').click(function () {
285 288 that.notebook.restart_kernel();
286 289 });
287 290 this.element.find('#reconnect_kernel').click(function () {
288 291 that.notebook.kernel.reconnect();
289 292 });
290 293 // Help
291 294 if (this.tour) {
292 295 this.element.find('#notebook_tour').click(function () {
293 296 that.tour.start();
294 297 });
295 298 } else {
296 299 this.element.find('#notebook_tour').addClass("disabled");
297 300 }
298 301 this.element.find('#keyboard_shortcuts').click(function () {
299 302 that.quick_help.show_keyboard_shortcuts();
300 303 });
301 304
302 305 this.update_restore_checkpoint(null);
303 306
304 307 this.events.on('checkpoints_listed.Notebook', function (event, data) {
305 308 that.update_restore_checkpoint(that.notebook.checkpoints);
306 309 });
307 310
308 311 this.events.on('checkpoint_created.Notebook', function (event, data) {
309 312 that.update_restore_checkpoint(that.notebook.checkpoints);
310 313 });
311 314
312 315 this.events.on('notebook_loaded.Notebook', function() {
313 316 var langinfo = that.notebook.metadata.language_info || {};
314 317 that.update_nbconvert_script(langinfo);
315 318 });
316 319
317 320 this.events.on('kernel_ready.Kernel', function(event, data) {
318 321 var langinfo = data.kernel.info_reply.language_info || {};
319 322 that.update_nbconvert_script(langinfo);
320 323 });
321 324 };
322 325
323 326 MenuBar.prototype.update_restore_checkpoint = function(checkpoints) {
324 327 var ul = this.element.find("#restore_checkpoint").find("ul");
325 328 ul.empty();
326 329 if (!checkpoints || checkpoints.length === 0) {
327 330 ul.append(
328 331 $("<li/>")
329 332 .addClass("disabled")
330 333 .append(
331 334 $("<a/>")
332 335 .text("No checkpoints")
333 336 )
334 337 );
335 338 return;
336 339 }
337 340
338 341 var that = this;
339 342 checkpoints.map(function (checkpoint) {
340 343 var d = new Date(checkpoint.last_modified);
341 344 ul.append(
342 345 $("<li/>").append(
343 346 $("<a/>")
344 347 .attr("href", "#")
345 348 .text(moment(d).format("LLLL"))
346 349 .click(function () {
347 350 that.notebook.restore_checkpoint_dialog(checkpoint);
348 351 })
349 352 )
350 353 );
351 354 });
352 355 };
353 356
354 357 MenuBar.prototype.update_nbconvert_script = function(langinfo) {
355 358 // Set the 'Download as foo' menu option for the relevant language.
356 359 var el = this.element.find('#download_script');
357 360 var that = this;
358 361
359 362 // Set menu entry text to e.g. "Python (.py)"
360 363 var langname = (langinfo.name || 'Script')
361 364 langname = langname.charAt(0).toUpperCase()+langname.substr(1) // Capitalise
362 365 el.find('a').text(langname + ' ('+(langinfo.file_extension || 'txt')+')');
363 366
364 367 // Unregister any previously registered handlers
365 368 el.off('click');
366 369 if (langinfo.nbconvert_exporter) {
367 370 // Metadata specifies a specific exporter, e.g. 'python'
368 371 el.click(function() {
369 372 that._nbconvert(langinfo.nbconvert_exporter, true);
370 373 });
371 374 } else {
372 375 // Use generic 'script' exporter
373 376 el.click(function() {
374 377 that._nbconvert('script', true);
375 378 });
376 379 }
377 380 };
378 381
379 382 // Backwards compatability.
380 383 IPython.MenuBar = MenuBar;
381 384
382 385 return {'MenuBar': MenuBar};
383 386 });
@@ -1,2508 +1,2564 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'base/js/utils',
8 8 'base/js/dialog',
9 9 'notebook/js/cell',
10 10 'notebook/js/textcell',
11 11 'notebook/js/codecell',
12 12 'services/sessions/session',
13 13 'notebook/js/celltoolbar',
14 14 'components/marked/lib/marked',
15 15 'codemirror/lib/codemirror',
16 16 'codemirror/addon/runmode/runmode',
17 17 'notebook/js/mathjaxutils',
18 18 'base/js/keyboard',
19 19 'notebook/js/tooltip',
20 20 'notebook/js/celltoolbarpresets/default',
21 21 'notebook/js/celltoolbarpresets/rawcell',
22 22 'notebook/js/celltoolbarpresets/slideshow',
23 23 'notebook/js/scrollmanager'
24 24 ], function (
25 25 IPython,
26 26 $,
27 27 utils,
28 28 dialog,
29 29 cellmod,
30 30 textcell,
31 31 codecell,
32 32 session,
33 33 celltoolbar,
34 34 marked,
35 35 CodeMirror,
36 36 runMode,
37 37 mathjaxutils,
38 38 keyboard,
39 39 tooltip,
40 40 default_celltoolbar,
41 41 rawcell_celltoolbar,
42 42 slideshow_celltoolbar,
43 43 scrollmanager
44 44 ) {
45 45 "use strict";
46 46
47 47 var Notebook = function (selector, options) {
48 48 // Constructor
49 49 //
50 50 // A notebook contains and manages cells.
51 51 //
52 52 // Parameters:
53 53 // selector: string
54 54 // options: dictionary
55 55 // Dictionary of keyword arguments.
56 56 // events: $(Events) instance
57 57 // keyboard_manager: KeyboardManager instance
58 58 // contents: Contents instance
59 59 // save_widget: SaveWidget instance
60 60 // config: dictionary
61 61 // base_url : string
62 62 // notebook_path : string
63 63 // notebook_name : string
64 64 this.config = utils.mergeopt(Notebook, options.config);
65 65 this.base_url = options.base_url;
66 66 this.notebook_path = options.notebook_path;
67 67 this.notebook_name = options.notebook_name;
68 68 this.events = options.events;
69 69 this.keyboard_manager = options.keyboard_manager;
70 70 this.contents = options.contents;
71 71 this.save_widget = options.save_widget;
72 72 this.tooltip = new tooltip.Tooltip(this.events);
73 73 this.ws_url = options.ws_url;
74 74 this._session_starting = false;
75 75 this.default_cell_type = this.config.default_cell_type || 'code';
76 76
77 77 // Create default scroll manager.
78 78 this.scroll_manager = new scrollmanager.ScrollManager(this);
79 79
80 80 // TODO: This code smells (and the other `= this` line a couple lines down)
81 81 // We need a better way to deal with circular instance references.
82 82 this.keyboard_manager.notebook = this;
83 83 this.save_widget.notebook = this;
84 84
85 85 mathjaxutils.init();
86 86
87 87 if (marked) {
88 88 marked.setOptions({
89 89 gfm : true,
90 90 tables: true,
91 91 // FIXME: probably want central config for CodeMirror theme when we have js config
92 92 langPrefix: "cm-s-ipython language-",
93 93 highlight: function(code, lang, callback) {
94 94 if (!lang) {
95 95 // no language, no highlight
96 96 if (callback) {
97 97 callback(null, code);
98 98 return;
99 99 } else {
100 100 return code;
101 101 }
102 102 }
103 103 utils.requireCodeMirrorMode(lang, function () {
104 104 var el = document.createElement("div");
105 105 var mode = CodeMirror.getMode({}, lang);
106 106 if (!mode) {
107 107 console.log("No CodeMirror mode: " + lang);
108 108 callback(null, code);
109 109 return;
110 110 }
111 111 try {
112 112 CodeMirror.runMode(code, mode, el);
113 113 callback(null, el.innerHTML);
114 114 } catch (err) {
115 115 console.log("Failed to highlight " + lang + " code", err);
116 116 callback(err, code);
117 117 }
118 118 }, function (err) {
119 119 console.log("No CodeMirror mode: " + lang);
120 120 callback(err, code);
121 121 });
122 122 }
123 123 });
124 124 }
125 125
126 126 this.element = $(selector);
127 127 this.element.scroll();
128 128 this.element.data("notebook", this);
129 129 this.next_prompt_number = 1;
130 130 this.session = null;
131 131 this.kernel = null;
132 132 this.clipboard = null;
133 133 this.undelete_backup = null;
134 134 this.undelete_index = null;
135 135 this.undelete_below = false;
136 136 this.paste_enabled = false;
137 137 this.writable = false;
138 this.ignore_unsolicited_msgs = false;
138 139 // It is important to start out in command mode to match the intial mode
139 140 // of the KeyboardManager.
140 141 this.mode = 'command';
141 142 this.set_dirty(false);
142 143 this.metadata = {};
143 144 this._checkpoint_after_save = false;
144 145 this.last_checkpoint = null;
145 146 this.checkpoints = [];
146 147 this.autosave_interval = 0;
147 148 this.autosave_timer = null;
148 149 // autosave *at most* every two minutes
149 150 this.minimum_autosave_interval = 120000;
150 151 this.notebook_name_blacklist_re = /[\/\\:]/;
151 152 this.nbformat = 4; // Increment this when changing the nbformat
152 153 this.nbformat_minor = this.current_nbformat_minor = 0; // Increment this when changing the nbformat
153 154 this.codemirror_mode = 'ipython';
154 155 this.create_elements();
155 156 this.bind_events();
156 157 this.kernel_selector = null;
157 158 this.dirty = null;
158 159 this.trusted = null;
159 160 this._fully_loaded = false;
160 161
161 162 // Trigger cell toolbar registration.
162 163 default_celltoolbar.register(this);
163 164 rawcell_celltoolbar.register(this);
164 165 slideshow_celltoolbar.register(this);
165 166
166 167 // prevent assign to miss-typed properties.
167 168 Object.seal(this);
168 169 };
169 170
170 171 Notebook.options_default = {
171 172 // can be any cell type, or the special values of
172 173 // 'above', 'below', or 'selected' to get the value from another cell.
173 174 Notebook: {
174 175 default_cell_type: 'code'
175 176 }
176 177 };
177 178
178 179
179 180 /**
180 181 * Create an HTML and CSS representation of the notebook.
181 182 *
182 183 * @method create_elements
183 184 */
184 185 Notebook.prototype.create_elements = function () {
185 186 var that = this;
186 187 this.element.attr('tabindex','-1');
187 188 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
188 189 // We add this end_space div to the end of the notebook div to:
189 190 // i) provide a margin between the last cell and the end of the notebook
190 191 // ii) to prevent the div from scrolling up when the last cell is being
191 192 // edited, but is too low on the page, which browsers will do automatically.
192 193 var end_space = $('<div/>').addClass('end_space');
193 194 end_space.dblclick(function (e) {
194 195 var ncells = that.ncells();
195 196 that.insert_cell_below('code',ncells-1);
196 197 });
197 198 this.element.append(this.container);
198 199 this.container.append(end_space);
199 200 };
200 201
201 202 /**
202 203 * Bind JavaScript events: key presses and custom IPython events.
203 204 *
204 205 * @method bind_events
205 206 */
206 207 Notebook.prototype.bind_events = function () {
207 208 var that = this;
208 209
209 210 this.events.on('set_next_input.Notebook', function (event, data) {
210 211 var index = that.find_cell_index(data.cell);
211 212 var new_cell = that.insert_cell_below('code',index);
212 213 new_cell.set_text(data.text);
213 214 that.dirty = true;
214 215 });
215 216
216 217 this.events.on('unrecognized_cell.Cell', function () {
217 218 that.warn_nbformat_minor();
218 219 });
219 220
220 221 this.events.on('unrecognized_output.OutputArea', function () {
221 222 that.warn_nbformat_minor();
222 223 });
223 224
224 225 this.events.on('set_dirty.Notebook', function (event, data) {
225 226 that.dirty = data.value;
226 227 });
227 228
228 229 this.events.on('trust_changed.Notebook', function (event, trusted) {
229 230 that.trusted = trusted;
230 231 });
231 232
232 233 this.events.on('select.Cell', function (event, data) {
233 234 var index = that.find_cell_index(data.cell);
234 235 that.select(index);
235 236 });
236 237
237 238 this.events.on('edit_mode.Cell', function (event, data) {
238 239 that.handle_edit_mode(data.cell);
239 240 });
240 241
241 242 this.events.on('command_mode.Cell', function (event, data) {
242 243 that.handle_command_mode(data.cell);
243 244 });
244 245
245 246 this.events.on('spec_changed.Kernel', function(event, data) {
246 247 that.metadata.kernelspec =
247 248 {name: data.name, display_name: data.display_name};
248 249 });
249 250
250 251 this.events.on('kernel_ready.Kernel', function(event, data) {
251 252 var kinfo = data.kernel.info_reply;
252 253 var langinfo = kinfo.language_info || {};
253 254 that.metadata.language_info = langinfo;
254 255 // Mode 'null' should be plain, unhighlighted text.
255 256 var cm_mode = langinfo.codemirror_mode || langinfo.name || 'null';
256 257 that.set_codemirror_mode(cm_mode);
257 258 });
258 259
259 260 var collapse_time = function (time) {
260 261 var app_height = $('#ipython-main-app').height(); // content height
261 262 var splitter_height = $('div#pager_splitter').outerHeight(true);
262 263 var new_height = app_height - splitter_height;
263 264 that.element.animate({height : new_height + 'px'}, time);
264 265 };
265 266
266 267 this.element.bind('collapse_pager', function (event, extrap) {
267 268 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
268 269 collapse_time(time);
269 270 });
270 271
271 272 var expand_time = function (time) {
272 273 var app_height = $('#ipython-main-app').height(); // content height
273 274 var splitter_height = $('div#pager_splitter').outerHeight(true);
274 275 var pager_height = $('div#pager').outerHeight(true);
275 276 var new_height = app_height - pager_height - splitter_height;
276 277 that.element.animate({height : new_height + 'px'}, time);
277 278 };
278 279
279 280 this.element.bind('expand_pager', function (event, extrap) {
280 281 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
281 282 expand_time(time);
282 283 });
283 284
284 285 // Firefox 22 broke $(window).on("beforeunload")
285 286 // I'm not sure why or how.
286 287 window.onbeforeunload = function (e) {
287 288 // TODO: Make killing the kernel configurable.
288 289 var kill_kernel = false;
289 290 if (kill_kernel) {
290 291 that.session.delete();
291 292 }
292 293 // if we are autosaving, trigger an autosave on nav-away.
293 294 // still warn, because if we don't the autosave may fail.
294 295 if (that.dirty) {
295 296 if ( that.autosave_interval ) {
296 297 // schedule autosave in a timeout
297 298 // this gives you a chance to forcefully discard changes
298 299 // by reloading the page if you *really* want to.
299 300 // the timer doesn't start until you *dismiss* the dialog.
300 301 setTimeout(function () {
301 302 if (that.dirty) {
302 303 that.save_notebook();
303 304 }
304 305 }, 1000);
305 306 return "Autosave in progress, latest changes may be lost.";
306 307 } else {
307 308 return "Unsaved changes will be lost.";
308 309 }
309 310 }
310 311 // Null is the *only* return value that will make the browser not
311 312 // pop up the "don't leave" dialog.
312 313 return null;
313 314 };
314 315 };
315 316
316 317 Notebook.prototype.warn_nbformat_minor = function (event) {
317 318 // trigger a warning dialog about missing functionality from newer minor versions
318 319 var v = 'v' + this.nbformat + '.';
319 320 var orig_vs = v + this.nbformat_minor;
320 321 var this_vs = v + this.current_nbformat_minor;
321 322 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
322 323 this_vs + ". You can still work with this notebook, but cell and output types " +
323 324 "introduced in later notebook versions will not be available.";
324 325
325 326 dialog.modal({
326 327 notebook: this,
327 328 keyboard_manager: this.keyboard_manager,
328 329 title : "Newer Notebook",
329 330 body : msg,
330 331 buttons : {
331 332 OK : {
332 333 "class" : "btn-danger"
333 334 }
334 335 }
335 336 });
336 337 }
337 338
338 339 /**
339 340 * Set the dirty flag, and trigger the set_dirty.Notebook event
340 341 *
341 342 * @method set_dirty
342 343 */
343 344 Notebook.prototype.set_dirty = function (value) {
344 345 if (value === undefined) {
345 346 value = true;
346 347 }
347 348 if (this.dirty == value) {
348 349 return;
349 350 }
350 351 this.events.trigger('set_dirty.Notebook', {value: value});
351 352 };
352 353
353 354 /**
354 355 * Scroll the top of the page to a given cell.
355 356 *
356 357 * @method scroll_to_cell
357 358 * @param {Number} cell_number An index of the cell to view
358 359 * @param {Number} time Animation time in milliseconds
359 360 * @return {Number} Pixel offset from the top of the container
360 361 */
361 362 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
362 363 var cells = this.get_cells();
363 364 time = time || 0;
364 365 cell_number = Math.min(cells.length-1,cell_number);
365 366 cell_number = Math.max(0 ,cell_number);
366 367 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
367 368 this.element.animate({scrollTop:scroll_value}, time);
368 369 return scroll_value;
369 370 };
370 371
371 372 /**
372 373 * Scroll to the bottom of the page.
373 374 *
374 375 * @method scroll_to_bottom
375 376 */
376 377 Notebook.prototype.scroll_to_bottom = function () {
377 378 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
378 379 };
379 380
380 381 /**
381 382 * Scroll to the top of the page.
382 383 *
383 384 * @method scroll_to_top
384 385 */
385 386 Notebook.prototype.scroll_to_top = function () {
386 387 this.element.animate({scrollTop:0}, 0);
387 388 };
388 389
389 390 // Edit Notebook metadata
390 391
391 392 Notebook.prototype.edit_metadata = function () {
392 393 var that = this;
393 394 dialog.edit_metadata({
394 395 md: this.metadata,
395 396 callback: function (md) {
396 397 that.metadata = md;
397 398 },
398 399 name: 'Notebook',
399 400 notebook: this,
400 401 keyboard_manager: this.keyboard_manager});
401 402 };
402 403
403 404 // Cell indexing, retrieval, etc.
404 405
405 406 /**
406 407 * Get all cell elements in the notebook.
407 408 *
408 409 * @method get_cell_elements
409 410 * @return {jQuery} A selector of all cell elements
410 411 */
411 412 Notebook.prototype.get_cell_elements = function () {
412 413 return this.container.find(".cell").not('.cell .cell');
413 414 };
414 415
415 416 /**
416 417 * Get a particular cell element.
417 418 *
418 419 * @method get_cell_element
419 420 * @param {Number} index An index of a cell to select
420 421 * @return {jQuery} A selector of the given cell.
421 422 */
422 423 Notebook.prototype.get_cell_element = function (index) {
423 424 var result = null;
424 425 var e = this.get_cell_elements().eq(index);
425 426 if (e.length !== 0) {
426 427 result = e;
427 428 }
428 429 return result;
429 430 };
430 431
431 432 /**
432 433 * Try to get a particular cell by msg_id.
433 434 *
434 435 * @method get_msg_cell
435 436 * @param {String} msg_id A message UUID
436 437 * @return {Cell} Cell or null if no cell was found.
437 438 */
438 439 Notebook.prototype.get_msg_cell = function (msg_id) {
439 440 return codecell.CodeCell.msg_cells[msg_id] || null;
440 441 };
441 442
442 443 /**
443 444 * Count the cells in this notebook.
444 445 *
445 446 * @method ncells
446 447 * @return {Number} The number of cells in this notebook
447 448 */
448 449 Notebook.prototype.ncells = function () {
449 450 return this.get_cell_elements().length;
450 451 };
451 452
452 453 /**
453 454 * Get all Cell objects in this notebook.
454 455 *
455 456 * @method get_cells
456 457 * @return {Array} This notebook's Cell objects
457 458 */
458 459 // TODO: we are often calling cells as cells()[i], which we should optimize
459 460 // to cells(i) or a new method.
460 461 Notebook.prototype.get_cells = function () {
461 462 return this.get_cell_elements().toArray().map(function (e) {
462 463 return $(e).data("cell");
463 464 });
464 465 };
465 466
466 467 /**
467 468 * Get a Cell object from this notebook.
468 469 *
469 470 * @method get_cell
470 471 * @param {Number} index An index of a cell to retrieve
471 472 * @return {Cell} Cell or null if no cell was found.
472 473 */
473 474 Notebook.prototype.get_cell = function (index) {
474 475 var result = null;
475 476 var ce = this.get_cell_element(index);
476 477 if (ce !== null) {
477 478 result = ce.data('cell');
478 479 }
479 480 return result;
480 481 };
481 482
482 483 /**
483 484 * Get the cell below a given cell.
484 485 *
485 486 * @method get_next_cell
486 487 * @param {Cell} cell The provided cell
487 488 * @return {Cell} the next cell or null if no cell was found.
488 489 */
489 490 Notebook.prototype.get_next_cell = function (cell) {
490 491 var result = null;
491 492 var index = this.find_cell_index(cell);
492 493 if (this.is_valid_cell_index(index+1)) {
493 494 result = this.get_cell(index+1);
494 495 }
495 496 return result;
496 497 };
497 498
498 499 /**
499 500 * Get the cell above a given cell.
500 501 *
501 502 * @method get_prev_cell
502 503 * @param {Cell} cell The provided cell
503 504 * @return {Cell} The previous cell or null if no cell was found.
504 505 */
505 506 Notebook.prototype.get_prev_cell = function (cell) {
506 507 var result = null;
507 508 var index = this.find_cell_index(cell);
508 509 if (index !== null && index > 0) {
509 510 result = this.get_cell(index-1);
510 511 }
511 512 return result;
512 513 };
513 514
514 515 /**
515 516 * Get the numeric index of a given cell.
516 517 *
517 518 * @method find_cell_index
518 519 * @param {Cell} cell The provided cell
519 520 * @return {Number} The cell's numeric index or null if no cell was found.
520 521 */
521 522 Notebook.prototype.find_cell_index = function (cell) {
522 523 var result = null;
523 524 this.get_cell_elements().filter(function (index) {
524 525 if ($(this).data("cell") === cell) {
525 526 result = index;
526 527 }
527 528 });
528 529 return result;
529 530 };
530 531
531 532 /**
532 533 * Get a given index , or the selected index if none is provided.
533 534 *
534 535 * @method index_or_selected
535 536 * @param {Number} index A cell's index
536 537 * @return {Number} The given index, or selected index if none is provided.
537 538 */
538 539 Notebook.prototype.index_or_selected = function (index) {
539 540 var i;
540 541 if (index === undefined || index === null) {
541 542 i = this.get_selected_index();
542 543 if (i === null) {
543 544 i = 0;
544 545 }
545 546 } else {
546 547 i = index;
547 548 }
548 549 return i;
549 550 };
550 551
551 552 /**
552 553 * Get the currently selected cell.
553 554 * @method get_selected_cell
554 555 * @return {Cell} The selected cell
555 556 */
556 557 Notebook.prototype.get_selected_cell = function () {
557 558 var index = this.get_selected_index();
558 559 return this.get_cell(index);
559 560 };
560 561
561 562 /**
562 563 * Check whether a cell index is valid.
563 564 *
564 565 * @method is_valid_cell_index
565 566 * @param {Number} index A cell index
566 567 * @return True if the index is valid, false otherwise
567 568 */
568 569 Notebook.prototype.is_valid_cell_index = function (index) {
569 570 if (index !== null && index >= 0 && index < this.ncells()) {
570 571 return true;
571 572 } else {
572 573 return false;
573 574 }
574 575 };
575 576
576 577 /**
577 578 * Get the index of the currently selected cell.
578 579
579 580 * @method get_selected_index
580 581 * @return {Number} The selected cell's numeric index
581 582 */
582 583 Notebook.prototype.get_selected_index = function () {
583 584 var result = null;
584 585 this.get_cell_elements().filter(function (index) {
585 586 if ($(this).data("cell").selected === true) {
586 587 result = index;
587 588 }
588 589 });
589 590 return result;
590 591 };
591 592
592 593
593 594 // Cell selection.
594 595
595 596 /**
596 597 * Programmatically select a cell.
597 598 *
598 599 * @method select
599 600 * @param {Number} index A cell's index
600 601 * @return {Notebook} This notebook
601 602 */
602 603 Notebook.prototype.select = function (index) {
603 604 if (this.is_valid_cell_index(index)) {
604 605 var sindex = this.get_selected_index();
605 606 if (sindex !== null && index !== sindex) {
606 607 // If we are about to select a different cell, make sure we are
607 608 // first in command mode.
608 609 if (this.mode !== 'command') {
609 610 this.command_mode();
610 611 }
611 612 this.get_cell(sindex).unselect();
612 613 }
613 614 var cell = this.get_cell(index);
614 615 cell.select();
615 616 if (cell.cell_type === 'heading') {
616 617 this.events.trigger('selected_cell_type_changed.Notebook',
617 618 {'cell_type':cell.cell_type,level:cell.level}
618 619 );
619 620 } else {
620 621 this.events.trigger('selected_cell_type_changed.Notebook',
621 622 {'cell_type':cell.cell_type}
622 623 );
623 624 }
624 625 }
625 626 return this;
626 627 };
627 628
628 629 /**
629 630 * Programmatically select the next cell.
630 631 *
631 632 * @method select_next
632 633 * @return {Notebook} This notebook
633 634 */
634 635 Notebook.prototype.select_next = function () {
635 636 var index = this.get_selected_index();
636 637 this.select(index+1);
637 638 return this;
638 639 };
639 640
640 641 /**
641 642 * Programmatically select the previous cell.
642 643 *
643 644 * @method select_prev
644 645 * @return {Notebook} This notebook
645 646 */
646 647 Notebook.prototype.select_prev = function () {
647 648 var index = this.get_selected_index();
648 649 this.select(index-1);
649 650 return this;
650 651 };
651 652
652 653
653 654 // Edit/Command mode
654 655
655 656 /**
656 657 * Gets the index of the cell that is in edit mode.
657 658 *
658 659 * @method get_edit_index
659 660 *
660 661 * @return index {int}
661 662 **/
662 663 Notebook.prototype.get_edit_index = function () {
663 664 var result = null;
664 665 this.get_cell_elements().filter(function (index) {
665 666 if ($(this).data("cell").mode === 'edit') {
666 667 result = index;
667 668 }
668 669 });
669 670 return result;
670 671 };
671 672
672 673 /**
673 674 * Handle when a a cell blurs and the notebook should enter command mode.
674 675 *
675 676 * @method handle_command_mode
676 677 * @param [cell] {Cell} Cell to enter command mode on.
677 678 **/
678 679 Notebook.prototype.handle_command_mode = function (cell) {
679 680 if (this.mode !== 'command') {
680 681 cell.command_mode();
681 682 this.mode = 'command';
682 683 this.events.trigger('command_mode.Notebook');
683 684 this.keyboard_manager.command_mode();
684 685 }
685 686 };
686 687
687 688 /**
688 689 * Make the notebook enter command mode.
689 690 *
690 691 * @method command_mode
691 692 **/
692 693 Notebook.prototype.command_mode = function () {
693 694 var cell = this.get_cell(this.get_edit_index());
694 695 if (cell && this.mode !== 'command') {
695 696 // We don't call cell.command_mode, but rather call cell.focus_cell()
696 697 // which will blur and CM editor and trigger the call to
697 698 // handle_command_mode.
698 699 cell.focus_cell();
699 700 }
700 701 };
701 702
702 703 /**
703 704 * Handle when a cell fires it's edit_mode event.
704 705 *
705 706 * @method handle_edit_mode
706 707 * @param [cell] {Cell} Cell to enter edit mode on.
707 708 **/
708 709 Notebook.prototype.handle_edit_mode = function (cell) {
709 710 if (cell && this.mode !== 'edit') {
710 711 cell.edit_mode();
711 712 this.mode = 'edit';
712 713 this.events.trigger('edit_mode.Notebook');
713 714 this.keyboard_manager.edit_mode();
714 715 }
715 716 };
716 717
717 718 /**
718 719 * Make a cell enter edit mode.
719 720 *
720 721 * @method edit_mode
721 722 **/
722 723 Notebook.prototype.edit_mode = function () {
723 724 var cell = this.get_selected_cell();
724 725 if (cell && this.mode !== 'edit') {
725 726 cell.unrender();
726 727 cell.focus_editor();
727 728 }
728 729 };
729 730
730 731 /**
731 732 * Focus the currently selected cell.
732 733 *
733 734 * @method focus_cell
734 735 **/
735 736 Notebook.prototype.focus_cell = function () {
736 737 var cell = this.get_selected_cell();
737 738 if (cell === null) {return;} // No cell is selected
738 739 cell.focus_cell();
739 740 };
740 741
741 742 // Cell movement
742 743
743 744 /**
744 745 * Move given (or selected) cell up and select it.
745 746 *
746 747 * @method move_cell_up
747 748 * @param [index] {integer} cell index
748 749 * @return {Notebook} This notebook
749 750 **/
750 751 Notebook.prototype.move_cell_up = function (index) {
751 752 var i = this.index_or_selected(index);
752 753 if (this.is_valid_cell_index(i) && i > 0) {
753 754 var pivot = this.get_cell_element(i-1);
754 755 var tomove = this.get_cell_element(i);
755 756 if (pivot !== null && tomove !== null) {
756 757 tomove.detach();
757 758 pivot.before(tomove);
758 759 this.select(i-1);
759 760 var cell = this.get_selected_cell();
760 761 cell.focus_cell();
761 762 }
762 763 this.set_dirty(true);
763 764 }
764 765 return this;
765 766 };
766 767
767 768
768 769 /**
769 770 * Move given (or selected) cell down and select it
770 771 *
771 772 * @method move_cell_down
772 773 * @param [index] {integer} cell index
773 774 * @return {Notebook} This notebook
774 775 **/
775 776 Notebook.prototype.move_cell_down = function (index) {
776 777 var i = this.index_or_selected(index);
777 778 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
778 779 var pivot = this.get_cell_element(i+1);
779 780 var tomove = this.get_cell_element(i);
780 781 if (pivot !== null && tomove !== null) {
781 782 tomove.detach();
782 783 pivot.after(tomove);
783 784 this.select(i+1);
784 785 var cell = this.get_selected_cell();
785 786 cell.focus_cell();
786 787 }
787 788 }
788 789 this.set_dirty();
789 790 return this;
790 791 };
791 792
792 793
793 794 // Insertion, deletion.
794 795
795 796 /**
796 797 * Delete a cell from the notebook.
797 798 *
798 799 * @method delete_cell
799 800 * @param [index] A cell's numeric index
800 801 * @return {Notebook} This notebook
801 802 */
802 803 Notebook.prototype.delete_cell = function (index) {
803 804 var i = this.index_or_selected(index);
804 805 var cell = this.get_cell(i);
805 806 if (!cell.is_deletable()) {
806 807 return this;
807 808 }
808 809
809 810 this.undelete_backup = cell.toJSON();
810 811 $('#undelete_cell').removeClass('disabled');
811 812 if (this.is_valid_cell_index(i)) {
812 813 var old_ncells = this.ncells();
813 814 var ce = this.get_cell_element(i);
814 815 ce.remove();
815 816 if (i === 0) {
816 817 // Always make sure we have at least one cell.
817 818 if (old_ncells === 1) {
818 819 this.insert_cell_below('code');
819 820 }
820 821 this.select(0);
821 822 this.undelete_index = 0;
822 823 this.undelete_below = false;
823 824 } else if (i === old_ncells-1 && i !== 0) {
824 825 this.select(i-1);
825 826 this.undelete_index = i - 1;
826 827 this.undelete_below = true;
827 828 } else {
828 829 this.select(i);
829 830 this.undelete_index = i;
830 831 this.undelete_below = false;
831 832 }
832 833 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
833 834 this.set_dirty(true);
834 835 }
835 836 return this;
836 837 };
837 838
838 839 /**
839 840 * Restore the most recently deleted cell.
840 841 *
841 842 * @method undelete
842 843 */
843 844 Notebook.prototype.undelete_cell = function() {
844 845 if (this.undelete_backup !== null && this.undelete_index !== null) {
845 846 var current_index = this.get_selected_index();
846 847 if (this.undelete_index < current_index) {
847 848 current_index = current_index + 1;
848 849 }
849 850 if (this.undelete_index >= this.ncells()) {
850 851 this.select(this.ncells() - 1);
851 852 }
852 853 else {
853 854 this.select(this.undelete_index);
854 855 }
855 856 var cell_data = this.undelete_backup;
856 857 var new_cell = null;
857 858 if (this.undelete_below) {
858 859 new_cell = this.insert_cell_below(cell_data.cell_type);
859 860 } else {
860 861 new_cell = this.insert_cell_above(cell_data.cell_type);
861 862 }
862 863 new_cell.fromJSON(cell_data);
863 864 if (this.undelete_below) {
864 865 this.select(current_index+1);
865 866 } else {
866 867 this.select(current_index);
867 868 }
868 869 this.undelete_backup = null;
869 870 this.undelete_index = null;
870 871 }
871 872 $('#undelete_cell').addClass('disabled');
872 873 };
873 874
874 875 /**
875 876 * Insert a cell so that after insertion the cell is at given index.
876 877 *
877 878 * If cell type is not provided, it will default to the type of the
878 879 * currently active cell.
879 880 *
880 881 * Similar to insert_above, but index parameter is mandatory
881 882 *
882 883 * Index will be brought back into the accessible range [0,n]
883 884 *
884 885 * @method insert_cell_at_index
885 886 * @param [type] {string} in ['code','markdown', 'raw'], defaults to 'code'
886 887 * @param [index] {int} a valid index where to insert cell
887 888 *
888 889 * @return cell {cell|null} created cell or null
889 890 **/
890 891 Notebook.prototype.insert_cell_at_index = function(type, index){
891 892
892 893 var ncells = this.ncells();
893 894 index = Math.min(index, ncells);
894 895 index = Math.max(index, 0);
895 896 var cell = null;
896 897 type = type || this.default_cell_type;
897 898 if (type === 'above') {
898 899 if (index > 0) {
899 900 type = this.get_cell(index-1).cell_type;
900 901 } else {
901 902 type = 'code';
902 903 }
903 904 } else if (type === 'below') {
904 905 if (index < ncells) {
905 906 type = this.get_cell(index).cell_type;
906 907 } else {
907 908 type = 'code';
908 909 }
909 910 } else if (type === 'selected') {
910 911 type = this.get_selected_cell().cell_type;
911 912 }
912 913
913 914 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
914 915 var cell_options = {
915 916 events: this.events,
916 917 config: this.config,
917 918 keyboard_manager: this.keyboard_manager,
918 919 notebook: this,
919 920 tooltip: this.tooltip
920 921 };
921 922 switch(type) {
922 923 case 'code':
923 924 cell = new codecell.CodeCell(this.kernel, cell_options);
924 925 cell.set_input_prompt();
925 926 break;
926 927 case 'markdown':
927 928 cell = new textcell.MarkdownCell(cell_options);
928 929 break;
929 930 case 'raw':
930 931 cell = new textcell.RawCell(cell_options);
931 932 break;
932 933 default:
933 934 console.log("Unrecognized cell type: ", type, cellmod);
934 935 cell = new cellmod.UnrecognizedCell(cell_options);
935 936 }
936 937
937 938 if(this._insert_element_at_index(cell.element,index)) {
938 939 cell.render();
939 940 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
940 941 cell.refresh();
941 942 // We used to select the cell after we refresh it, but there
942 943 // are now cases were this method is called where select is
943 944 // not appropriate. The selection logic should be handled by the
944 945 // caller of the the top level insert_cell methods.
945 946 this.set_dirty(true);
946 947 }
947 948 }
948 949 return cell;
949 950
950 951 };
951 952
952 953 /**
953 954 * Insert an element at given cell index.
954 955 *
955 956 * @method _insert_element_at_index
956 957 * @param element {dom_element} a cell element
957 958 * @param [index] {int} a valid index where to inser cell
958 959 * @private
959 960 *
960 961 * return true if everything whent fine.
961 962 **/
962 963 Notebook.prototype._insert_element_at_index = function(element, index){
963 964 if (element === undefined){
964 965 return false;
965 966 }
966 967
967 968 var ncells = this.ncells();
968 969
969 970 if (ncells === 0) {
970 971 // special case append if empty
971 972 this.element.find('div.end_space').before(element);
972 973 } else if ( ncells === index ) {
973 974 // special case append it the end, but not empty
974 975 this.get_cell_element(index-1).after(element);
975 976 } else if (this.is_valid_cell_index(index)) {
976 977 // otherwise always somewhere to append to
977 978 this.get_cell_element(index).before(element);
978 979 } else {
979 980 return false;
980 981 }
981 982
982 983 if (this.undelete_index !== null && index <= this.undelete_index) {
983 984 this.undelete_index = this.undelete_index + 1;
984 985 this.set_dirty(true);
985 986 }
986 987 return true;
987 988 };
988 989
989 990 /**
990 991 * Insert a cell of given type above given index, or at top
991 992 * of notebook if index smaller than 0.
992 993 *
993 994 * default index value is the one of currently selected cell
994 995 *
995 996 * @method insert_cell_above
996 997 * @param [type] {string} cell type
997 998 * @param [index] {integer}
998 999 *
999 1000 * @return handle to created cell or null
1000 1001 **/
1001 1002 Notebook.prototype.insert_cell_above = function (type, index) {
1002 1003 index = this.index_or_selected(index);
1003 1004 return this.insert_cell_at_index(type, index);
1004 1005 };
1005 1006
1006 1007 /**
1007 1008 * Insert a cell of given type below given index, or at bottom
1008 1009 * of notebook if index greater than number of cells
1009 1010 *
1010 1011 * default index value is the one of currently selected cell
1011 1012 *
1012 1013 * @method insert_cell_below
1013 1014 * @param [type] {string} cell type
1014 1015 * @param [index] {integer}
1015 1016 *
1016 1017 * @return handle to created cell or null
1017 1018 *
1018 1019 **/
1019 1020 Notebook.prototype.insert_cell_below = function (type, index) {
1020 1021 index = this.index_or_selected(index);
1021 1022 return this.insert_cell_at_index(type, index+1);
1022 1023 };
1023 1024
1024 1025
1025 1026 /**
1026 1027 * Insert cell at end of notebook
1027 1028 *
1028 1029 * @method insert_cell_at_bottom
1029 1030 * @param {String} type cell type
1030 1031 *
1031 1032 * @return the added cell; or null
1032 1033 **/
1033 1034 Notebook.prototype.insert_cell_at_bottom = function (type){
1034 1035 var len = this.ncells();
1035 1036 return this.insert_cell_below(type,len-1);
1036 1037 };
1037 1038
1038 1039 /**
1039 1040 * Turn a cell into a code cell.
1040 1041 *
1041 1042 * @method to_code
1042 1043 * @param {Number} [index] A cell's index
1043 1044 */
1044 1045 Notebook.prototype.to_code = function (index) {
1045 1046 var i = this.index_or_selected(index);
1046 1047 if (this.is_valid_cell_index(i)) {
1047 1048 var source_cell = this.get_cell(i);
1048 1049 if (!(source_cell instanceof codecell.CodeCell)) {
1049 1050 var target_cell = this.insert_cell_below('code',i);
1050 1051 var text = source_cell.get_text();
1051 1052 if (text === source_cell.placeholder) {
1052 1053 text = '';
1053 1054 }
1054 1055 //metadata
1055 1056 target_cell.metadata = source_cell.metadata;
1056 1057
1057 1058 target_cell.set_text(text);
1058 1059 // make this value the starting point, so that we can only undo
1059 1060 // to this state, instead of a blank cell
1060 1061 target_cell.code_mirror.clearHistory();
1061 1062 source_cell.element.remove();
1062 1063 this.select(i);
1063 1064 var cursor = source_cell.code_mirror.getCursor();
1064 1065 target_cell.code_mirror.setCursor(cursor);
1065 1066 this.set_dirty(true);
1066 1067 }
1067 1068 }
1068 1069 };
1069 1070
1070 1071 /**
1071 1072 * Turn a cell into a Markdown cell.
1072 1073 *
1073 1074 * @method to_markdown
1074 1075 * @param {Number} [index] A cell's index
1075 1076 */
1076 1077 Notebook.prototype.to_markdown = function (index) {
1077 1078 var i = this.index_or_selected(index);
1078 1079 if (this.is_valid_cell_index(i)) {
1079 1080 var source_cell = this.get_cell(i);
1080 1081
1081 1082 if (!(source_cell instanceof textcell.MarkdownCell)) {
1082 1083 var target_cell = this.insert_cell_below('markdown',i);
1083 1084 var text = source_cell.get_text();
1084 1085
1085 1086 if (text === source_cell.placeholder) {
1086 1087 text = '';
1087 1088 }
1088 1089 // metadata
1089 1090 target_cell.metadata = source_cell.metadata;
1090 1091 // We must show the editor before setting its contents
1091 1092 target_cell.unrender();
1092 1093 target_cell.set_text(text);
1093 1094 // make this value the starting point, so that we can only undo
1094 1095 // to this state, instead of a blank cell
1095 1096 target_cell.code_mirror.clearHistory();
1096 1097 source_cell.element.remove();
1097 1098 this.select(i);
1098 1099 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1099 1100 target_cell.render();
1100 1101 }
1101 1102 var cursor = source_cell.code_mirror.getCursor();
1102 1103 target_cell.code_mirror.setCursor(cursor);
1103 1104 this.set_dirty(true);
1104 1105 }
1105 1106 }
1106 1107 };
1107 1108
1108 1109 /**
1109 1110 * Turn a cell into a raw text cell.
1110 1111 *
1111 1112 * @method to_raw
1112 1113 * @param {Number} [index] A cell's index
1113 1114 */
1114 1115 Notebook.prototype.to_raw = function (index) {
1115 1116 var i = this.index_or_selected(index);
1116 1117 if (this.is_valid_cell_index(i)) {
1117 1118 var target_cell = null;
1118 1119 var source_cell = this.get_cell(i);
1119 1120
1120 1121 if (!(source_cell instanceof textcell.RawCell)) {
1121 1122 target_cell = this.insert_cell_below('raw',i);
1122 1123 var text = source_cell.get_text();
1123 1124 if (text === source_cell.placeholder) {
1124 1125 text = '';
1125 1126 }
1126 1127 //metadata
1127 1128 target_cell.metadata = source_cell.metadata;
1128 1129 // We must show the editor before setting its contents
1129 1130 target_cell.unrender();
1130 1131 target_cell.set_text(text);
1131 1132 // make this value the starting point, so that we can only undo
1132 1133 // to this state, instead of a blank cell
1133 1134 target_cell.code_mirror.clearHistory();
1134 1135 source_cell.element.remove();
1135 1136 this.select(i);
1136 1137 var cursor = source_cell.code_mirror.getCursor();
1137 1138 target_cell.code_mirror.setCursor(cursor);
1138 1139 this.set_dirty(true);
1139 1140 }
1140 1141 }
1141 1142 };
1142 1143
1143 1144 Notebook.prototype._warn_heading = function () {
1144 1145 // warn about heading cells being removed
1145 1146 dialog.modal({
1146 1147 notebook: this,
1147 1148 keyboard_manager: this.keyboard_manager,
1148 1149 title : "Use markdown headings",
1149 1150 body : $("<p/>").text(
1150 1151 'IPython no longer uses special heading cells. ' +
1151 1152 'Instead, write your headings in Markdown cells using # characters:'
1152 1153 ).append($('<pre/>').text(
1153 1154 '## This is a level 2 heading'
1154 1155 )),
1155 1156 buttons : {
1156 1157 "OK" : {}
1157 1158 }
1158 1159 });
1159 1160 };
1160 1161
1161 1162 /**
1162 1163 * Turn a cell into a markdown cell with a heading.
1163 1164 *
1164 1165 * @method to_heading
1165 1166 * @param {Number} [index] A cell's index
1166 1167 * @param {Number} [level] A heading level (e.g., 1 for h1)
1167 1168 */
1168 1169 Notebook.prototype.to_heading = function (index, level) {
1169 1170 this.to_markdown(index);
1170 1171 level = level || 1;
1171 1172 var i = this.index_or_selected(index);
1172 1173 if (this.is_valid_cell_index(i)) {
1173 1174 var cell = this.get_cell(i);
1174 1175 cell.set_heading_level(level);
1175 1176 this.set_dirty(true);
1176 1177 }
1177 1178 };
1178 1179
1179 1180
1180 1181 // Cut/Copy/Paste
1181 1182
1182 1183 /**
1183 1184 * Enable UI elements for pasting cells.
1184 1185 *
1185 1186 * @method enable_paste
1186 1187 */
1187 1188 Notebook.prototype.enable_paste = function () {
1188 1189 var that = this;
1189 1190 if (!this.paste_enabled) {
1190 1191 $('#paste_cell_replace').removeClass('disabled')
1191 1192 .on('click', function () {that.paste_cell_replace();});
1192 1193 $('#paste_cell_above').removeClass('disabled')
1193 1194 .on('click', function () {that.paste_cell_above();});
1194 1195 $('#paste_cell_below').removeClass('disabled')
1195 1196 .on('click', function () {that.paste_cell_below();});
1196 1197 this.paste_enabled = true;
1197 1198 }
1198 1199 };
1199 1200
1200 1201 /**
1201 1202 * Disable UI elements for pasting cells.
1202 1203 *
1203 1204 * @method disable_paste
1204 1205 */
1205 1206 Notebook.prototype.disable_paste = function () {
1206 1207 if (this.paste_enabled) {
1207 1208 $('#paste_cell_replace').addClass('disabled').off('click');
1208 1209 $('#paste_cell_above').addClass('disabled').off('click');
1209 1210 $('#paste_cell_below').addClass('disabled').off('click');
1210 1211 this.paste_enabled = false;
1211 1212 }
1212 1213 };
1213 1214
1214 1215 /**
1215 1216 * Cut a cell.
1216 1217 *
1217 1218 * @method cut_cell
1218 1219 */
1219 1220 Notebook.prototype.cut_cell = function () {
1220 1221 this.copy_cell();
1221 1222 this.delete_cell();
1222 1223 };
1223 1224
1224 1225 /**
1225 1226 * Copy a cell.
1226 1227 *
1227 1228 * @method copy_cell
1228 1229 */
1229 1230 Notebook.prototype.copy_cell = function () {
1230 1231 var cell = this.get_selected_cell();
1231 1232 this.clipboard = cell.toJSON();
1232 1233 // remove undeletable status from the copied cell
1233 1234 if (this.clipboard.metadata.deletable !== undefined) {
1234 1235 delete this.clipboard.metadata.deletable;
1235 1236 }
1236 1237 this.enable_paste();
1237 1238 };
1238 1239
1239 1240 /**
1240 1241 * Replace the selected cell with a cell in the clipboard.
1241 1242 *
1242 1243 * @method paste_cell_replace
1243 1244 */
1244 1245 Notebook.prototype.paste_cell_replace = function () {
1245 1246 if (this.clipboard !== null && this.paste_enabled) {
1246 1247 var cell_data = this.clipboard;
1247 1248 var new_cell = this.insert_cell_above(cell_data.cell_type);
1248 1249 new_cell.fromJSON(cell_data);
1249 1250 var old_cell = this.get_next_cell(new_cell);
1250 1251 this.delete_cell(this.find_cell_index(old_cell));
1251 1252 this.select(this.find_cell_index(new_cell));
1252 1253 }
1253 1254 };
1254 1255
1255 1256 /**
1256 1257 * Paste a cell from the clipboard above the selected cell.
1257 1258 *
1258 1259 * @method paste_cell_above
1259 1260 */
1260 1261 Notebook.prototype.paste_cell_above = function () {
1261 1262 if (this.clipboard !== null && this.paste_enabled) {
1262 1263 var cell_data = this.clipboard;
1263 1264 var new_cell = this.insert_cell_above(cell_data.cell_type);
1264 1265 new_cell.fromJSON(cell_data);
1265 1266 new_cell.focus_cell();
1266 1267 }
1267 1268 };
1268 1269
1269 1270 /**
1270 1271 * Paste a cell from the clipboard below the selected cell.
1271 1272 *
1272 1273 * @method paste_cell_below
1273 1274 */
1274 1275 Notebook.prototype.paste_cell_below = function () {
1275 1276 if (this.clipboard !== null && this.paste_enabled) {
1276 1277 var cell_data = this.clipboard;
1277 1278 var new_cell = this.insert_cell_below(cell_data.cell_type);
1278 1279 new_cell.fromJSON(cell_data);
1279 1280 new_cell.focus_cell();
1280 1281 }
1281 1282 };
1282 1283
1283 1284 // Split/merge
1284 1285
1285 1286 /**
1286 1287 * Split the selected cell into two, at the cursor.
1287 1288 *
1288 1289 * @method split_cell
1289 1290 */
1290 1291 Notebook.prototype.split_cell = function () {
1291 1292 var cell = this.get_selected_cell();
1292 1293 if (cell.is_splittable()) {
1293 1294 var texta = cell.get_pre_cursor();
1294 1295 var textb = cell.get_post_cursor();
1295 1296 cell.set_text(textb);
1296 1297 var new_cell = this.insert_cell_above(cell.cell_type);
1297 1298 // Unrender the new cell so we can call set_text.
1298 1299 new_cell.unrender();
1299 1300 new_cell.set_text(texta);
1300 1301 }
1301 1302 };
1302 1303
1303 1304 /**
1304 1305 * Combine the selected cell into the cell above it.
1305 1306 *
1306 1307 * @method merge_cell_above
1307 1308 */
1308 1309 Notebook.prototype.merge_cell_above = function () {
1309 1310 var index = this.get_selected_index();
1310 1311 var cell = this.get_cell(index);
1311 1312 var render = cell.rendered;
1312 1313 if (!cell.is_mergeable()) {
1313 1314 return;
1314 1315 }
1315 1316 if (index > 0) {
1316 1317 var upper_cell = this.get_cell(index-1);
1317 1318 if (!upper_cell.is_mergeable()) {
1318 1319 return;
1319 1320 }
1320 1321 var upper_text = upper_cell.get_text();
1321 1322 var text = cell.get_text();
1322 1323 if (cell instanceof codecell.CodeCell) {
1323 1324 cell.set_text(upper_text+'\n'+text);
1324 1325 } else {
1325 1326 cell.unrender(); // Must unrender before we set_text.
1326 1327 cell.set_text(upper_text+'\n\n'+text);
1327 1328 if (render) {
1328 1329 // The rendered state of the final cell should match
1329 1330 // that of the original selected cell;
1330 1331 cell.render();
1331 1332 }
1332 1333 }
1333 1334 this.delete_cell(index-1);
1334 1335 this.select(this.find_cell_index(cell));
1335 1336 }
1336 1337 };
1337 1338
1338 1339 /**
1339 1340 * Combine the selected cell into the cell below it.
1340 1341 *
1341 1342 * @method merge_cell_below
1342 1343 */
1343 1344 Notebook.prototype.merge_cell_below = function () {
1344 1345 var index = this.get_selected_index();
1345 1346 var cell = this.get_cell(index);
1346 1347 var render = cell.rendered;
1347 1348 if (!cell.is_mergeable()) {
1348 1349 return;
1349 1350 }
1350 1351 if (index < this.ncells()-1) {
1351 1352 var lower_cell = this.get_cell(index+1);
1352 1353 if (!lower_cell.is_mergeable()) {
1353 1354 return;
1354 1355 }
1355 1356 var lower_text = lower_cell.get_text();
1356 1357 var text = cell.get_text();
1357 1358 if (cell instanceof codecell.CodeCell) {
1358 1359 cell.set_text(text+'\n'+lower_text);
1359 1360 } else {
1360 1361 cell.unrender(); // Must unrender before we set_text.
1361 1362 cell.set_text(text+'\n\n'+lower_text);
1362 1363 if (render) {
1363 1364 // The rendered state of the final cell should match
1364 1365 // that of the original selected cell;
1365 1366 cell.render();
1366 1367 }
1367 1368 }
1368 1369 this.delete_cell(index+1);
1369 1370 this.select(this.find_cell_index(cell));
1370 1371 }
1371 1372 };
1372 1373
1373 1374
1374 1375 // Cell collapsing and output clearing
1375 1376
1376 1377 /**
1377 1378 * Hide a cell's output.
1378 1379 *
1379 1380 * @method collapse_output
1380 1381 * @param {Number} index A cell's numeric index
1381 1382 */
1382 1383 Notebook.prototype.collapse_output = function (index) {
1383 1384 var i = this.index_or_selected(index);
1384 1385 var cell = this.get_cell(i);
1385 1386 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1386 1387 cell.collapse_output();
1387 1388 this.set_dirty(true);
1388 1389 }
1389 1390 };
1390 1391
1391 1392 /**
1392 1393 * Hide each code cell's output area.
1393 1394 *
1394 1395 * @method collapse_all_output
1395 1396 */
1396 1397 Notebook.prototype.collapse_all_output = function () {
1397 1398 this.get_cells().map(function (cell, i) {
1398 1399 if (cell instanceof codecell.CodeCell) {
1399 1400 cell.collapse_output();
1400 1401 }
1401 1402 });
1402 1403 // this should not be set if the `collapse` key is removed from nbformat
1403 1404 this.set_dirty(true);
1404 1405 };
1405 1406
1406 1407 /**
1407 1408 * Show a cell's output.
1408 1409 *
1409 1410 * @method expand_output
1410 1411 * @param {Number} index A cell's numeric index
1411 1412 */
1412 1413 Notebook.prototype.expand_output = function (index) {
1413 1414 var i = this.index_or_selected(index);
1414 1415 var cell = this.get_cell(i);
1415 1416 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1416 1417 cell.expand_output();
1417 1418 this.set_dirty(true);
1418 1419 }
1419 1420 };
1420 1421
1421 1422 /**
1422 1423 * Expand each code cell's output area, and remove scrollbars.
1423 1424 *
1424 1425 * @method expand_all_output
1425 1426 */
1426 1427 Notebook.prototype.expand_all_output = function () {
1427 1428 this.get_cells().map(function (cell, i) {
1428 1429 if (cell instanceof codecell.CodeCell) {
1429 1430 cell.expand_output();
1430 1431 }
1431 1432 });
1432 1433 // this should not be set if the `collapse` key is removed from nbformat
1433 1434 this.set_dirty(true);
1434 1435 };
1435 1436
1436 1437 /**
1437 1438 * Clear the selected CodeCell's output area.
1438 1439 *
1439 1440 * @method clear_output
1440 1441 * @param {Number} index A cell's numeric index
1441 1442 */
1442 1443 Notebook.prototype.clear_output = function (index) {
1443 1444 var i = this.index_or_selected(index);
1444 1445 var cell = this.get_cell(i);
1445 1446 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1446 1447 cell.clear_output();
1447 1448 this.set_dirty(true);
1448 1449 }
1449 1450 };
1450 1451
1451 1452 /**
1452 1453 * Clear each code cell's output area.
1453 1454 *
1454 1455 * @method clear_all_output
1455 1456 */
1456 1457 Notebook.prototype.clear_all_output = function () {
1457 1458 this.get_cells().map(function (cell, i) {
1458 1459 if (cell instanceof codecell.CodeCell) {
1459 1460 cell.clear_output();
1460 1461 }
1461 1462 });
1462 1463 this.set_dirty(true);
1463 1464 };
1464 1465
1465 1466 /**
1466 1467 * Scroll the selected CodeCell's output area.
1467 1468 *
1468 1469 * @method scroll_output
1469 1470 * @param {Number} index A cell's numeric index
1470 1471 */
1471 1472 Notebook.prototype.scroll_output = function (index) {
1472 1473 var i = this.index_or_selected(index);
1473 1474 var cell = this.get_cell(i);
1474 1475 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1475 1476 cell.scroll_output();
1476 1477 this.set_dirty(true);
1477 1478 }
1478 1479 };
1479 1480
1480 1481 /**
1481 1482 * Expand each code cell's output area, and add a scrollbar for long output.
1482 1483 *
1483 1484 * @method scroll_all_output
1484 1485 */
1485 1486 Notebook.prototype.scroll_all_output = function () {
1486 1487 this.get_cells().map(function (cell, i) {
1487 1488 if (cell instanceof codecell.CodeCell) {
1488 1489 cell.scroll_output();
1489 1490 }
1490 1491 });
1491 1492 // this should not be set if the `collapse` key is removed from nbformat
1492 1493 this.set_dirty(true);
1493 1494 };
1494 1495
1495 1496 /** Toggle whether a cell's output is collapsed or expanded.
1496 1497 *
1497 1498 * @method toggle_output
1498 1499 * @param {Number} index A cell's numeric index
1499 1500 */
1500 1501 Notebook.prototype.toggle_output = function (index) {
1501 1502 var i = this.index_or_selected(index);
1502 1503 var cell = this.get_cell(i);
1503 1504 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1504 1505 cell.toggle_output();
1505 1506 this.set_dirty(true);
1506 1507 }
1507 1508 };
1508 1509
1509 1510 /**
1510 1511 * Hide/show the output of all cells.
1511 1512 *
1512 1513 * @method toggle_all_output
1513 1514 */
1514 1515 Notebook.prototype.toggle_all_output = function () {
1515 1516 this.get_cells().map(function (cell, i) {
1516 1517 if (cell instanceof codecell.CodeCell) {
1517 1518 cell.toggle_output();
1518 1519 }
1519 1520 });
1520 1521 // this should not be set if the `collapse` key is removed from nbformat
1521 1522 this.set_dirty(true);
1522 1523 };
1523 1524
1524 1525 /**
1525 1526 * Toggle a scrollbar for long cell outputs.
1526 1527 *
1527 1528 * @method toggle_output_scroll
1528 1529 * @param {Number} index A cell's numeric index
1529 1530 */
1530 1531 Notebook.prototype.toggle_output_scroll = function (index) {
1531 1532 var i = this.index_or_selected(index);
1532 1533 var cell = this.get_cell(i);
1533 1534 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1534 1535 cell.toggle_output_scroll();
1535 1536 this.set_dirty(true);
1536 1537 }
1537 1538 };
1538 1539
1539 1540 /**
1540 1541 * Toggle the scrolling of long output on all cells.
1541 1542 *
1542 1543 * @method toggle_all_output_scrolling
1543 1544 */
1544 1545 Notebook.prototype.toggle_all_output_scroll = function () {
1545 1546 this.get_cells().map(function (cell, i) {
1546 1547 if (cell instanceof codecell.CodeCell) {
1547 1548 cell.toggle_output_scroll();
1548 1549 }
1549 1550 });
1550 1551 // this should not be set if the `collapse` key is removed from nbformat
1551 1552 this.set_dirty(true);
1552 1553 };
1553 1554
1554 1555 // Other cell functions: line numbers, ...
1555 1556
1556 1557 /**
1557 1558 * Toggle line numbers in the selected cell's input area.
1558 1559 *
1559 1560 * @method cell_toggle_line_numbers
1560 1561 */
1561 1562 Notebook.prototype.cell_toggle_line_numbers = function() {
1562 1563 this.get_selected_cell().toggle_line_numbers();
1563 1564 };
1564 1565
1566 // Support for displaying input and output messages from other iPy clients.
1567
1568 /**
1569 * Toggles the ability to display input/output message events from
1570 * externally connected clients (i.e. other iPython shells, vim-ipython,
1571 * etc).
1572 *
1573 * @method toggle_ignore_unsolicited_msgs
1574 */
1575 Notebook.prototype.toggle_ignore_unsolicited_msgs = function () {
1576 this.ignore_unsolicited_msgs = !this.ignore_unsolicited_msgs;
1577 this.events.trigger('toggle_unsolicited_msgs.Notebook',
1578 [this.ignore_unsolicited_msgs]);
1579 return this.ignore_unsolicited_msgs;
1580 };
1581
1582 /**
1583 * Handles the display of unsolicited messages, i.e. inputs or outputs that
1584 * were generated by a client other than this notebook. New messages are
1585 * displayed at the bottom of the notebook.
1586 *
1587 * @method handle_unsolicited_msg
1588 */
1589 Notebook.prototype.handle_unsolicited_msg = function(msg) {
1590 if (this.ignore_unsolicited_msgs) {
1591 return;
1592 }
1593 if (msg.msg_type == 'execute_input') {
1594 var cell = this.insert_cell_at_bottom('code');
1595 if (cell) {
1596 var cell_index = this.ncells() - 1;
1597 cell.last_msg_id = msg.parent_header.msg_id;
1598 cell.set_text(msg.content.code);
1599 cell._handle_execute_reply(msg);
1600 this.scroll_to_cell(cell_index);
1601 this.select(cell_index);
1602 }
1603 } else {
1604 /* Find the input cell that corresponds with the output, then add
1605 * the contents to the cell's output area.
1606 */
1607 var count = this.ncells();
1608 while (count--) {
1609 var cell = this.get_cell(count);
1610 if (cell && cell.last_msg_id == msg.parent_header.msg_id) {
1611 cell.output_area.handle_output(msg);
1612 this.scroll_to_cell(count);
1613 this.select(count);
1614 break;
1615 }
1616 }
1617 }
1618 };
1619
1565 1620 /**
1566 1621 * Set the codemirror mode for all code cells, including the default for
1567 1622 * new code cells.
1568 1623 *
1569 1624 * @method set_codemirror_mode
1570 1625 */
1571 1626 Notebook.prototype.set_codemirror_mode = function(newmode){
1572 1627 if (newmode === this.codemirror_mode) {
1573 1628 return;
1574 1629 }
1575 1630 this.codemirror_mode = newmode;
1576 1631 codecell.CodeCell.options_default.cm_config.mode = newmode;
1577 1632 var modename = newmode.mode || newmode.name || newmode;
1578 1633
1579 1634 var that = this;
1580 1635 utils.requireCodeMirrorMode(modename, function () {
1581 1636 that.get_cells().map(function(cell, i) {
1582 1637 if (cell.cell_type === 'code'){
1583 1638 cell.code_mirror.setOption('mode', newmode);
1584 1639 // This is currently redundant, because cm_config ends up as
1585 1640 // codemirror's own .options object, but I don't want to
1586 1641 // rely on that.
1587 1642 cell.cm_config.mode = newmode;
1588 1643 }
1589 1644 });
1590 1645 });
1591 1646 };
1592 1647
1593 1648 // Session related things
1594 1649
1595 1650 /**
1596 1651 * Start a new session and set it on each code cell.
1597 1652 *
1598 1653 * @method start_session
1599 1654 */
1600 1655 Notebook.prototype.start_session = function (kernel_name) {
1601 1656 if (this._session_starting) {
1602 1657 throw new session.SessionAlreadyStarting();
1603 1658 }
1604 1659 this._session_starting = true;
1605 1660
1606 1661 var options = {
1607 1662 base_url: this.base_url,
1608 1663 ws_url: this.ws_url,
1609 1664 notebook_path: this.notebook_path,
1610 1665 notebook_name: this.notebook_name,
1611 1666 kernel_name: kernel_name,
1612 1667 notebook: this
1613 1668 };
1614 1669
1615 1670 var success = $.proxy(this._session_started, this);
1616 1671 var failure = $.proxy(this._session_start_failed, this);
1617 1672
1618 1673 if (this.session !== null) {
1619 1674 this.session.restart(options, success, failure);
1620 1675 } else {
1621 1676 this.session = new session.Session(options);
1622 1677 this.session.start(success, failure);
1623 1678 }
1624 1679 };
1625 1680
1626 1681
1627 1682 /**
1628 1683 * Once a session is started, link the code cells to the kernel and pass the
1629 1684 * comm manager to the widget manager
1630 1685 *
1631 1686 */
1632 1687 Notebook.prototype._session_started = function (){
1633 1688 this._session_starting = false;
1634 1689 this.kernel = this.session.kernel;
1635 1690 var ncells = this.ncells();
1636 1691 for (var i=0; i<ncells; i++) {
1637 1692 var cell = this.get_cell(i);
1638 1693 if (cell instanceof codecell.CodeCell) {
1639 1694 cell.set_kernel(this.session.kernel);
1640 1695 }
1641 1696 }
1697 this.kernel.unsolicited_msg_callback = $.proxy(this.handle_unsolicited_msg, this);
1642 1698 };
1643 1699 Notebook.prototype._session_start_failed = function (jqxhr, status, error){
1644 1700 this._session_starting = false;
1645 1701 utils.log_ajax_error(jqxhr, status, error);
1646 1702 };
1647 1703
1648 1704 /**
1649 1705 * Prompt the user to restart the IPython kernel.
1650 1706 *
1651 1707 * @method restart_kernel
1652 1708 */
1653 1709 Notebook.prototype.restart_kernel = function () {
1654 1710 var that = this;
1655 1711 dialog.modal({
1656 1712 notebook: this,
1657 1713 keyboard_manager: this.keyboard_manager,
1658 1714 title : "Restart kernel or continue running?",
1659 1715 body : $("<p/>").text(
1660 1716 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1661 1717 ),
1662 1718 buttons : {
1663 1719 "Continue running" : {},
1664 1720 "Restart" : {
1665 1721 "class" : "btn-danger",
1666 1722 "click" : function() {
1667 1723 that.kernel.restart();
1668 1724 }
1669 1725 }
1670 1726 }
1671 1727 });
1672 1728 };
1673 1729
1674 1730 /**
1675 1731 * Execute or render cell outputs and go into command mode.
1676 1732 *
1677 1733 * @method execute_cell
1678 1734 */
1679 1735 Notebook.prototype.execute_cell = function () {
1680 1736 // mode = shift, ctrl, alt
1681 1737 var cell = this.get_selected_cell();
1682 1738
1683 1739 cell.execute();
1684 1740 this.command_mode();
1685 1741 this.set_dirty(true);
1686 1742 };
1687 1743
1688 1744 /**
1689 1745 * Execute or render cell outputs and insert a new cell below.
1690 1746 *
1691 1747 * @method execute_cell_and_insert_below
1692 1748 */
1693 1749 Notebook.prototype.execute_cell_and_insert_below = function () {
1694 1750 var cell = this.get_selected_cell();
1695 1751 var cell_index = this.find_cell_index(cell);
1696 1752
1697 1753 cell.execute();
1698 1754
1699 1755 // If we are at the end always insert a new cell and return
1700 1756 if (cell_index === (this.ncells()-1)) {
1701 1757 this.command_mode();
1702 1758 this.insert_cell_below();
1703 1759 this.select(cell_index+1);
1704 1760 this.edit_mode();
1705 1761 this.scroll_to_bottom();
1706 1762 this.set_dirty(true);
1707 1763 return;
1708 1764 }
1709 1765
1710 1766 this.command_mode();
1711 1767 this.insert_cell_below();
1712 1768 this.select(cell_index+1);
1713 1769 this.edit_mode();
1714 1770 this.set_dirty(true);
1715 1771 };
1716 1772
1717 1773 /**
1718 1774 * Execute or render cell outputs and select the next cell.
1719 1775 *
1720 1776 * @method execute_cell_and_select_below
1721 1777 */
1722 1778 Notebook.prototype.execute_cell_and_select_below = function () {
1723 1779
1724 1780 var cell = this.get_selected_cell();
1725 1781 var cell_index = this.find_cell_index(cell);
1726 1782
1727 1783 cell.execute();
1728 1784
1729 1785 // If we are at the end always insert a new cell and return
1730 1786 if (cell_index === (this.ncells()-1)) {
1731 1787 this.command_mode();
1732 1788 this.insert_cell_below();
1733 1789 this.select(cell_index+1);
1734 1790 this.edit_mode();
1735 1791 this.scroll_to_bottom();
1736 1792 this.set_dirty(true);
1737 1793 return;
1738 1794 }
1739 1795
1740 1796 this.command_mode();
1741 1797 this.select(cell_index+1);
1742 1798 this.focus_cell();
1743 1799 this.set_dirty(true);
1744 1800 };
1745 1801
1746 1802 /**
1747 1803 * Execute all cells below the selected cell.
1748 1804 *
1749 1805 * @method execute_cells_below
1750 1806 */
1751 1807 Notebook.prototype.execute_cells_below = function () {
1752 1808 this.execute_cell_range(this.get_selected_index(), this.ncells());
1753 1809 this.scroll_to_bottom();
1754 1810 };
1755 1811
1756 1812 /**
1757 1813 * Execute all cells above the selected cell.
1758 1814 *
1759 1815 * @method execute_cells_above
1760 1816 */
1761 1817 Notebook.prototype.execute_cells_above = function () {
1762 1818 this.execute_cell_range(0, this.get_selected_index());
1763 1819 };
1764 1820
1765 1821 /**
1766 1822 * Execute all cells.
1767 1823 *
1768 1824 * @method execute_all_cells
1769 1825 */
1770 1826 Notebook.prototype.execute_all_cells = function () {
1771 1827 this.execute_cell_range(0, this.ncells());
1772 1828 this.scroll_to_bottom();
1773 1829 };
1774 1830
1775 1831 /**
1776 1832 * Execute a contiguous range of cells.
1777 1833 *
1778 1834 * @method execute_cell_range
1779 1835 * @param {Number} start Index of the first cell to execute (inclusive)
1780 1836 * @param {Number} end Index of the last cell to execute (exclusive)
1781 1837 */
1782 1838 Notebook.prototype.execute_cell_range = function (start, end) {
1783 1839 this.command_mode();
1784 1840 for (var i=start; i<end; i++) {
1785 1841 this.select(i);
1786 1842 this.execute_cell();
1787 1843 }
1788 1844 };
1789 1845
1790 1846 // Persistance and loading
1791 1847
1792 1848 /**
1793 1849 * Getter method for this notebook's name.
1794 1850 *
1795 1851 * @method get_notebook_name
1796 1852 * @return {String} This notebook's name (excluding file extension)
1797 1853 */
1798 1854 Notebook.prototype.get_notebook_name = function () {
1799 1855 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1800 1856 return nbname;
1801 1857 };
1802 1858
1803 1859 /**
1804 1860 * Setter method for this notebook's name.
1805 1861 *
1806 1862 * @method set_notebook_name
1807 1863 * @param {String} name A new name for this notebook
1808 1864 */
1809 1865 Notebook.prototype.set_notebook_name = function (name) {
1810 1866 var parent = utils.url_path_split(this.notebook_path)[0];
1811 1867 this.notebook_name = name;
1812 1868 this.notebook_path = utils.url_path_join(parent, name);
1813 1869 };
1814 1870
1815 1871 /**
1816 1872 * Check that a notebook's name is valid.
1817 1873 *
1818 1874 * @method test_notebook_name
1819 1875 * @param {String} nbname A name for this notebook
1820 1876 * @return {Boolean} True if the name is valid, false if invalid
1821 1877 */
1822 1878 Notebook.prototype.test_notebook_name = function (nbname) {
1823 1879 nbname = nbname || '';
1824 1880 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1825 1881 return true;
1826 1882 } else {
1827 1883 return false;
1828 1884 }
1829 1885 };
1830 1886
1831 1887 /**
1832 1888 * Load a notebook from JSON (.ipynb).
1833 1889 *
1834 1890 * @method fromJSON
1835 1891 * @param {Object} data JSON representation of a notebook
1836 1892 */
1837 1893 Notebook.prototype.fromJSON = function (data) {
1838 1894
1839 1895 var content = data.content;
1840 1896 var ncells = this.ncells();
1841 1897 var i;
1842 1898 for (i=0; i<ncells; i++) {
1843 1899 // Always delete cell 0 as they get renumbered as they are deleted.
1844 1900 this.delete_cell(0);
1845 1901 }
1846 1902 // Save the metadata and name.
1847 1903 this.metadata = content.metadata;
1848 1904 this.notebook_name = data.name;
1849 1905 this.notebook_path = data.path;
1850 1906 var trusted = true;
1851 1907
1852 1908 // Trigger an event changing the kernel spec - this will set the default
1853 1909 // codemirror mode
1854 1910 if (this.metadata.kernelspec !== undefined) {
1855 1911 this.events.trigger('spec_changed.Kernel', this.metadata.kernelspec);
1856 1912 }
1857 1913
1858 1914 // Set the codemirror mode from language_info metadata
1859 1915 if (this.metadata.language_info !== undefined) {
1860 1916 var langinfo = this.metadata.language_info;
1861 1917 // Mode 'null' should be plain, unhighlighted text.
1862 1918 var cm_mode = langinfo.codemirror_mode || langinfo.name || 'null';
1863 1919 this.set_codemirror_mode(cm_mode);
1864 1920 }
1865 1921
1866 1922 var new_cells = content.cells;
1867 1923 ncells = new_cells.length;
1868 1924 var cell_data = null;
1869 1925 var new_cell = null;
1870 1926 for (i=0; i<ncells; i++) {
1871 1927 cell_data = new_cells[i];
1872 1928 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1873 1929 new_cell.fromJSON(cell_data);
1874 1930 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1875 1931 trusted = false;
1876 1932 }
1877 1933 }
1878 1934 if (trusted !== this.trusted) {
1879 1935 this.trusted = trusted;
1880 1936 this.events.trigger("trust_changed.Notebook", trusted);
1881 1937 }
1882 1938 };
1883 1939
1884 1940 /**
1885 1941 * Dump this notebook into a JSON-friendly object.
1886 1942 *
1887 1943 * @method toJSON
1888 1944 * @return {Object} A JSON-friendly representation of this notebook.
1889 1945 */
1890 1946 Notebook.prototype.toJSON = function () {
1891 1947 // remove the conversion indicator, which only belongs in-memory
1892 1948 delete this.metadata.orig_nbformat;
1893 1949 delete this.metadata.orig_nbformat_minor;
1894 1950
1895 1951 var cells = this.get_cells();
1896 1952 var ncells = cells.length;
1897 1953 var cell_array = new Array(ncells);
1898 1954 var trusted = true;
1899 1955 for (var i=0; i<ncells; i++) {
1900 1956 var cell = cells[i];
1901 1957 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1902 1958 trusted = false;
1903 1959 }
1904 1960 cell_array[i] = cell.toJSON();
1905 1961 }
1906 1962 var data = {
1907 1963 cells: cell_array,
1908 1964 metadata: this.metadata,
1909 1965 nbformat: this.nbformat,
1910 1966 nbformat_minor: this.nbformat_minor
1911 1967 };
1912 1968 if (trusted != this.trusted) {
1913 1969 this.trusted = trusted;
1914 1970 this.events.trigger("trust_changed.Notebook", trusted);
1915 1971 }
1916 1972 return data;
1917 1973 };
1918 1974
1919 1975 /**
1920 1976 * Start an autosave timer, for periodically saving the notebook.
1921 1977 *
1922 1978 * @method set_autosave_interval
1923 1979 * @param {Integer} interval the autosave interval in milliseconds
1924 1980 */
1925 1981 Notebook.prototype.set_autosave_interval = function (interval) {
1926 1982 var that = this;
1927 1983 // clear previous interval, so we don't get simultaneous timers
1928 1984 if (this.autosave_timer) {
1929 1985 clearInterval(this.autosave_timer);
1930 1986 }
1931 1987 if (!this.writable) {
1932 1988 // disable autosave if not writable
1933 1989 interval = 0;
1934 1990 }
1935 1991
1936 1992 this.autosave_interval = this.minimum_autosave_interval = interval;
1937 1993 if (interval) {
1938 1994 this.autosave_timer = setInterval(function() {
1939 1995 if (that.dirty) {
1940 1996 that.save_notebook();
1941 1997 }
1942 1998 }, interval);
1943 1999 this.events.trigger("autosave_enabled.Notebook", interval);
1944 2000 } else {
1945 2001 this.autosave_timer = null;
1946 2002 this.events.trigger("autosave_disabled.Notebook");
1947 2003 }
1948 2004 };
1949 2005
1950 2006 /**
1951 2007 * Save this notebook on the server. This becomes a notebook instance's
1952 2008 * .save_notebook method *after* the entire notebook has been loaded.
1953 2009 *
1954 2010 * @method save_notebook
1955 2011 */
1956 2012 Notebook.prototype.save_notebook = function () {
1957 2013 if (!this._fully_loaded) {
1958 2014 this.events.trigger('notebook_save_failed.Notebook',
1959 2015 new Error("Load failed, save is disabled")
1960 2016 );
1961 2017 return;
1962 2018 } else if (!this.writable) {
1963 2019 this.events.trigger('notebook_save_failed.Notebook',
1964 2020 new Error("Notebook is read-only")
1965 2021 );
1966 2022 return;
1967 2023 }
1968 2024
1969 2025 // Create a JSON model to be sent to the server.
1970 2026 var model = {
1971 2027 type : "notebook",
1972 2028 content : this.toJSON()
1973 2029 };
1974 2030 // time the ajax call for autosave tuning purposes.
1975 2031 var start = new Date().getTime();
1976 2032
1977 2033 var that = this;
1978 2034 return this.contents.save(this.notebook_path, model).then(
1979 2035 $.proxy(this.save_notebook_success, this, start),
1980 2036 function (error) {
1981 2037 that.events.trigger('notebook_save_failed.Notebook', error);
1982 2038 }
1983 2039 );
1984 2040 };
1985 2041
1986 2042 /**
1987 2043 * Success callback for saving a notebook.
1988 2044 *
1989 2045 * @method save_notebook_success
1990 2046 * @param {Integer} start Time when the save request start
1991 2047 * @param {Object} data JSON representation of a notebook
1992 2048 */
1993 2049 Notebook.prototype.save_notebook_success = function (start, data) {
1994 2050 this.set_dirty(false);
1995 2051 if (data.message) {
1996 2052 // save succeeded, but validation failed.
1997 2053 var body = $("<div>");
1998 2054 var title = "Notebook validation failed";
1999 2055
2000 2056 body.append($("<p>").text(
2001 2057 "The save operation succeeded," +
2002 2058 " but the notebook does not appear to be valid." +
2003 2059 " The validation error was:"
2004 2060 )).append($("<div>").addClass("validation-error").append(
2005 2061 $("<pre>").text(data.message)
2006 2062 ));
2007 2063 dialog.modal({
2008 2064 notebook: this,
2009 2065 keyboard_manager: this.keyboard_manager,
2010 2066 title: title,
2011 2067 body: body,
2012 2068 buttons : {
2013 2069 OK : {
2014 2070 "class" : "btn-primary"
2015 2071 }
2016 2072 }
2017 2073 });
2018 2074 }
2019 2075 this.events.trigger('notebook_saved.Notebook');
2020 2076 this._update_autosave_interval(start);
2021 2077 if (this._checkpoint_after_save) {
2022 2078 this.create_checkpoint();
2023 2079 this._checkpoint_after_save = false;
2024 2080 }
2025 2081 };
2026 2082
2027 2083 /**
2028 2084 * update the autosave interval based on how long the last save took
2029 2085 *
2030 2086 * @method _update_autosave_interval
2031 2087 * @param {Integer} timestamp when the save request started
2032 2088 */
2033 2089 Notebook.prototype._update_autosave_interval = function (start) {
2034 2090 var duration = (new Date().getTime() - start);
2035 2091 if (this.autosave_interval) {
2036 2092 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
2037 2093 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
2038 2094 // round to 10 seconds, otherwise we will be setting a new interval too often
2039 2095 interval = 10000 * Math.round(interval / 10000);
2040 2096 // set new interval, if it's changed
2041 2097 if (interval != this.autosave_interval) {
2042 2098 this.set_autosave_interval(interval);
2043 2099 }
2044 2100 }
2045 2101 };
2046 2102
2047 2103 /**
2048 2104 * Explicitly trust the output of this notebook.
2049 2105 *
2050 2106 * @method trust_notebook
2051 2107 */
2052 2108 Notebook.prototype.trust_notebook = function () {
2053 2109 var body = $("<div>").append($("<p>")
2054 2110 .text("A trusted IPython notebook may execute hidden malicious code ")
2055 2111 .append($("<strong>")
2056 2112 .append(
2057 2113 $("<em>").text("when you open it")
2058 2114 )
2059 2115 ).append(".").append(
2060 2116 " Selecting trust will immediately reload this notebook in a trusted state."
2061 2117 ).append(
2062 2118 " For more information, see the "
2063 2119 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
2064 2120 .text("IPython security documentation")
2065 2121 ).append(".")
2066 2122 );
2067 2123
2068 2124 var nb = this;
2069 2125 dialog.modal({
2070 2126 notebook: this,
2071 2127 keyboard_manager: this.keyboard_manager,
2072 2128 title: "Trust this notebook?",
2073 2129 body: body,
2074 2130
2075 2131 buttons: {
2076 2132 Cancel : {},
2077 2133 Trust : {
2078 2134 class : "btn-danger",
2079 2135 click : function () {
2080 2136 var cells = nb.get_cells();
2081 2137 for (var i = 0; i < cells.length; i++) {
2082 2138 var cell = cells[i];
2083 2139 if (cell.cell_type == 'code') {
2084 2140 cell.output_area.trusted = true;
2085 2141 }
2086 2142 }
2087 2143 nb.events.on('notebook_saved.Notebook', function () {
2088 2144 window.location.reload();
2089 2145 });
2090 2146 nb.save_notebook();
2091 2147 }
2092 2148 }
2093 2149 }
2094 2150 });
2095 2151 };
2096 2152
2097 2153 Notebook.prototype.copy_notebook = function () {
2098 2154 var that = this;
2099 2155 var base_url = this.base_url;
2100 2156 var w = window.open();
2101 2157 var parent = utils.url_path_split(this.notebook_path)[0];
2102 2158 this.contents.copy(this.notebook_path, parent).then(
2103 2159 function (data) {
2104 2160 w.location = utils.url_join_encode(
2105 2161 base_url, 'notebooks', data.path
2106 2162 );
2107 2163 },
2108 2164 function(error) {
2109 2165 w.close();
2110 2166 that.events.trigger('notebook_copy_failed', error);
2111 2167 }
2112 2168 );
2113 2169 };
2114 2170
2115 2171 Notebook.prototype.rename = function (new_name) {
2116 2172 if (!new_name.match(/\.ipynb$/)) {
2117 2173 new_name = new_name + ".ipynb";
2118 2174 }
2119 2175
2120 2176 var that = this;
2121 2177 var parent = utils.url_path_split(this.notebook_path)[0];
2122 2178 var new_path = utils.url_path_join(parent, new_name);
2123 2179 return this.contents.rename(this.notebook_path, new_path).then(
2124 2180 function (json) {
2125 2181 that.notebook_name = json.name;
2126 2182 that.notebook_path = json.path;
2127 2183 that.session.rename_notebook(json.path);
2128 2184 that.events.trigger('notebook_renamed.Notebook', json);
2129 2185 }
2130 2186 );
2131 2187 };
2132 2188
2133 2189 Notebook.prototype.delete = function () {
2134 2190 this.contents.delete(this.notebook_path);
2135 2191 };
2136 2192
2137 2193 /**
2138 2194 * Request a notebook's data from the server.
2139 2195 *
2140 2196 * @method load_notebook
2141 2197 * @param {String} notebook_path A notebook to load
2142 2198 */
2143 2199 Notebook.prototype.load_notebook = function (notebook_path) {
2144 2200 this.notebook_path = notebook_path;
2145 2201 this.notebook_name = utils.url_path_split(this.notebook_path)[1];
2146 2202 this.events.trigger('notebook_loading.Notebook');
2147 2203 this.contents.get(notebook_path, {type: 'notebook'}).then(
2148 2204 $.proxy(this.load_notebook_success, this),
2149 2205 $.proxy(this.load_notebook_error, this)
2150 2206 );
2151 2207 };
2152 2208
2153 2209 /**
2154 2210 * Success callback for loading a notebook from the server.
2155 2211 *
2156 2212 * Load notebook data from the JSON response.
2157 2213 *
2158 2214 * @method load_notebook_success
2159 2215 * @param {Object} data JSON representation of a notebook
2160 2216 */
2161 2217 Notebook.prototype.load_notebook_success = function (data) {
2162 2218 var failed, msg;
2163 2219 try {
2164 2220 this.fromJSON(data);
2165 2221 } catch (e) {
2166 2222 failed = e;
2167 2223 console.log("Notebook failed to load from JSON:", e);
2168 2224 }
2169 2225 if (failed || data.message) {
2170 2226 // *either* fromJSON failed or validation failed
2171 2227 var body = $("<div>");
2172 2228 var title;
2173 2229 if (failed) {
2174 2230 title = "Notebook failed to load";
2175 2231 body.append($("<p>").text(
2176 2232 "The error was: "
2177 2233 )).append($("<div>").addClass("js-error").text(
2178 2234 failed.toString()
2179 2235 )).append($("<p>").text(
2180 2236 "See the error console for details."
2181 2237 ));
2182 2238 } else {
2183 2239 title = "Notebook validation failed";
2184 2240 }
2185 2241
2186 2242 if (data.message) {
2187 2243 if (failed) {
2188 2244 msg = "The notebook also failed validation:";
2189 2245 } else {
2190 2246 msg = "An invalid notebook may not function properly." +
2191 2247 " The validation error was:";
2192 2248 }
2193 2249 body.append($("<p>").text(
2194 2250 msg
2195 2251 )).append($("<div>").addClass("validation-error").append(
2196 2252 $("<pre>").text(data.message)
2197 2253 ));
2198 2254 }
2199 2255
2200 2256 dialog.modal({
2201 2257 notebook: this,
2202 2258 keyboard_manager: this.keyboard_manager,
2203 2259 title: title,
2204 2260 body: body,
2205 2261 buttons : {
2206 2262 OK : {
2207 2263 "class" : "btn-primary"
2208 2264 }
2209 2265 }
2210 2266 });
2211 2267 }
2212 2268 if (this.ncells() === 0) {
2213 2269 this.insert_cell_below('code');
2214 2270 this.edit_mode(0);
2215 2271 } else {
2216 2272 this.select(0);
2217 2273 this.handle_command_mode(this.get_cell(0));
2218 2274 }
2219 2275 this.set_dirty(false);
2220 2276 this.scroll_to_top();
2221 2277 this.writable = data.writable || false;
2222 2278 var nbmodel = data.content;
2223 2279 var orig_nbformat = nbmodel.metadata.orig_nbformat;
2224 2280 var orig_nbformat_minor = nbmodel.metadata.orig_nbformat_minor;
2225 2281 if (orig_nbformat !== undefined && nbmodel.nbformat !== orig_nbformat) {
2226 2282 var src;
2227 2283 if (nbmodel.nbformat > orig_nbformat) {
2228 2284 src = " an older notebook format ";
2229 2285 } else {
2230 2286 src = " a newer notebook format ";
2231 2287 }
2232 2288
2233 2289 msg = "This notebook has been converted from" + src +
2234 2290 "(v"+orig_nbformat+") to the current notebook " +
2235 2291 "format (v"+nbmodel.nbformat+"). The next time you save this notebook, the " +
2236 2292 "current notebook format will be used.";
2237 2293
2238 2294 if (nbmodel.nbformat > orig_nbformat) {
2239 2295 msg += " Older versions of IPython may not be able to read the new format.";
2240 2296 } else {
2241 2297 msg += " Some features of the original notebook may not be available.";
2242 2298 }
2243 2299 msg += " To preserve the original version, close the " +
2244 2300 "notebook without saving it.";
2245 2301 dialog.modal({
2246 2302 notebook: this,
2247 2303 keyboard_manager: this.keyboard_manager,
2248 2304 title : "Notebook converted",
2249 2305 body : msg,
2250 2306 buttons : {
2251 2307 OK : {
2252 2308 class : "btn-primary"
2253 2309 }
2254 2310 }
2255 2311 });
2256 2312 } else if (this.nbformat_minor < nbmodel.nbformat_minor) {
2257 2313 this.nbformat_minor = nbmodel.nbformat_minor;
2258 2314 }
2259 2315
2260 2316 // Create the session after the notebook is completely loaded to prevent
2261 2317 // code execution upon loading, which is a security risk.
2262 2318 if (this.session === null) {
2263 2319 var kernelspec = this.metadata.kernelspec || {};
2264 2320 var kernel_name = kernelspec.name;
2265 2321
2266 2322 this.start_session(kernel_name);
2267 2323 }
2268 2324 // load our checkpoint list
2269 2325 this.list_checkpoints();
2270 2326
2271 2327 // load toolbar state
2272 2328 if (this.metadata.celltoolbar) {
2273 2329 celltoolbar.CellToolbar.global_show();
2274 2330 celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar);
2275 2331 } else {
2276 2332 celltoolbar.CellToolbar.global_hide();
2277 2333 }
2278 2334
2279 2335 if (!this.writable) {
2280 2336 this.set_autosave_interval(0);
2281 2337 this.events.trigger('notebook_read_only.Notebook');
2282 2338 }
2283 2339
2284 2340 // now that we're fully loaded, it is safe to restore save functionality
2285 2341 this._fully_loaded = true;
2286 2342 this.events.trigger('notebook_loaded.Notebook');
2287 2343 };
2288 2344
2289 2345 /**
2290 2346 * Failure callback for loading a notebook from the server.
2291 2347 *
2292 2348 * @method load_notebook_error
2293 2349 * @param {Error} error
2294 2350 */
2295 2351 Notebook.prototype.load_notebook_error = function (error) {
2296 2352 this.events.trigger('notebook_load_failed.Notebook', error);
2297 2353 var msg;
2298 2354 if (error.name === utils.XHR_ERROR && error.xhr.status === 500) {
2299 2355 utils.log_ajax_error(error.xhr, error.xhr_status, error.xhr_error);
2300 2356 msg = "An unknown error occurred while loading this notebook. " +
2301 2357 "This version can load notebook formats " +
2302 2358 "v" + this.nbformat + " or earlier. See the server log for details.";
2303 2359 } else {
2304 2360 msg = error.message;
2305 2361 }
2306 2362 dialog.modal({
2307 2363 notebook: this,
2308 2364 keyboard_manager: this.keyboard_manager,
2309 2365 title: "Error loading notebook",
2310 2366 body : msg,
2311 2367 buttons : {
2312 2368 "OK": {}
2313 2369 }
2314 2370 });
2315 2371 };
2316 2372
2317 2373 /********************* checkpoint-related *********************/
2318 2374
2319 2375 /**
2320 2376 * Save the notebook then immediately create a checkpoint.
2321 2377 *
2322 2378 * @method save_checkpoint
2323 2379 */
2324 2380 Notebook.prototype.save_checkpoint = function () {
2325 2381 this._checkpoint_after_save = true;
2326 2382 this.save_notebook();
2327 2383 };
2328 2384
2329 2385 /**
2330 2386 * Add a checkpoint for this notebook.
2331 2387 * for use as a callback from checkpoint creation.
2332 2388 *
2333 2389 * @method add_checkpoint
2334 2390 */
2335 2391 Notebook.prototype.add_checkpoint = function (checkpoint) {
2336 2392 var found = false;
2337 2393 for (var i = 0; i < this.checkpoints.length; i++) {
2338 2394 var existing = this.checkpoints[i];
2339 2395 if (existing.id == checkpoint.id) {
2340 2396 found = true;
2341 2397 this.checkpoints[i] = checkpoint;
2342 2398 break;
2343 2399 }
2344 2400 }
2345 2401 if (!found) {
2346 2402 this.checkpoints.push(checkpoint);
2347 2403 }
2348 2404 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2349 2405 };
2350 2406
2351 2407 /**
2352 2408 * List checkpoints for this notebook.
2353 2409 *
2354 2410 * @method list_checkpoints
2355 2411 */
2356 2412 Notebook.prototype.list_checkpoints = function () {
2357 2413 var that = this;
2358 2414 this.contents.list_checkpoints(this.notebook_path).then(
2359 2415 $.proxy(this.list_checkpoints_success, this),
2360 2416 function(error) {
2361 2417 that.events.trigger('list_checkpoints_failed.Notebook', error);
2362 2418 }
2363 2419 );
2364 2420 };
2365 2421
2366 2422 /**
2367 2423 * Success callback for listing checkpoints.
2368 2424 *
2369 2425 * @method list_checkpoint_success
2370 2426 * @param {Object} data JSON representation of a checkpoint
2371 2427 */
2372 2428 Notebook.prototype.list_checkpoints_success = function (data) {
2373 2429 this.checkpoints = data;
2374 2430 if (data.length) {
2375 2431 this.last_checkpoint = data[data.length - 1];
2376 2432 } else {
2377 2433 this.last_checkpoint = null;
2378 2434 }
2379 2435 this.events.trigger('checkpoints_listed.Notebook', [data]);
2380 2436 };
2381 2437
2382 2438 /**
2383 2439 * Create a checkpoint of this notebook on the server from the most recent save.
2384 2440 *
2385 2441 * @method create_checkpoint
2386 2442 */
2387 2443 Notebook.prototype.create_checkpoint = function () {
2388 2444 var that = this;
2389 2445 this.contents.create_checkpoint(this.notebook_path).then(
2390 2446 $.proxy(this.create_checkpoint_success, this),
2391 2447 function (error) {
2392 2448 that.events.trigger('checkpoint_failed.Notebook', error);
2393 2449 }
2394 2450 );
2395 2451 };
2396 2452
2397 2453 /**
2398 2454 * Success callback for creating a checkpoint.
2399 2455 *
2400 2456 * @method create_checkpoint_success
2401 2457 * @param {Object} data JSON representation of a checkpoint
2402 2458 */
2403 2459 Notebook.prototype.create_checkpoint_success = function (data) {
2404 2460 this.add_checkpoint(data);
2405 2461 this.events.trigger('checkpoint_created.Notebook', data);
2406 2462 };
2407 2463
2408 2464 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2409 2465 var that = this;
2410 2466 checkpoint = checkpoint || this.last_checkpoint;
2411 2467 if ( ! checkpoint ) {
2412 2468 console.log("restore dialog, but no checkpoint to restore to!");
2413 2469 return;
2414 2470 }
2415 2471 var body = $('<div/>').append(
2416 2472 $('<p/>').addClass("p-space").text(
2417 2473 "Are you sure you want to revert the notebook to " +
2418 2474 "the latest checkpoint?"
2419 2475 ).append(
2420 2476 $("<strong/>").text(
2421 2477 " This cannot be undone."
2422 2478 )
2423 2479 )
2424 2480 ).append(
2425 2481 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2426 2482 ).append(
2427 2483 $('<p/>').addClass("p-space").text(
2428 2484 Date(checkpoint.last_modified)
2429 2485 ).css("text-align", "center")
2430 2486 );
2431 2487
2432 2488 dialog.modal({
2433 2489 notebook: this,
2434 2490 keyboard_manager: this.keyboard_manager,
2435 2491 title : "Revert notebook to checkpoint",
2436 2492 body : body,
2437 2493 buttons : {
2438 2494 Revert : {
2439 2495 class : "btn-danger",
2440 2496 click : function () {
2441 2497 that.restore_checkpoint(checkpoint.id);
2442 2498 }
2443 2499 },
2444 2500 Cancel : {}
2445 2501 }
2446 2502 });
2447 2503 };
2448 2504
2449 2505 /**
2450 2506 * Restore the notebook to a checkpoint state.
2451 2507 *
2452 2508 * @method restore_checkpoint
2453 2509 * @param {String} checkpoint ID
2454 2510 */
2455 2511 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2456 2512 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2457 2513 var that = this;
2458 2514 this.contents.restore_checkpoint(this.notebook_path, checkpoint).then(
2459 2515 $.proxy(this.restore_checkpoint_success, this),
2460 2516 function (error) {
2461 2517 that.events.trigger('checkpoint_restore_failed.Notebook', error);
2462 2518 }
2463 2519 );
2464 2520 };
2465 2521
2466 2522 /**
2467 2523 * Success callback for restoring a notebook to a checkpoint.
2468 2524 *
2469 2525 * @method restore_checkpoint_success
2470 2526 */
2471 2527 Notebook.prototype.restore_checkpoint_success = function () {
2472 2528 this.events.trigger('checkpoint_restored.Notebook');
2473 2529 this.load_notebook(this.notebook_path);
2474 2530 };
2475 2531
2476 2532 /**
2477 2533 * Delete a notebook checkpoint.
2478 2534 *
2479 2535 * @method delete_checkpoint
2480 2536 * @param {String} checkpoint ID
2481 2537 */
2482 2538 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2483 2539 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2484 2540 var that = this;
2485 2541 this.contents.delete_checkpoint(this.notebook_path, checkpoint).then(
2486 2542 $.proxy(this.delete_checkpoint_success, this),
2487 2543 function (error) {
2488 2544 that.events.trigger('checkpoint_delete_failed.Notebook', error);
2489 2545 }
2490 2546 );
2491 2547 };
2492 2548
2493 2549 /**
2494 2550 * Success callback for deleting a notebook checkpoint
2495 2551 *
2496 2552 * @method delete_checkpoint_success
2497 2553 */
2498 2554 Notebook.prototype.delete_checkpoint_success = function () {
2499 2555 this.events.trigger('checkpoint_deleted.Notebook');
2500 2556 this.load_notebook(this.notebook_path);
2501 2557 };
2502 2558
2503 2559
2504 2560 // For backwards compatability.
2505 2561 IPython.Notebook = Notebook;
2506 2562
2507 2563 return {'Notebook': Notebook};
2508 2564 });
@@ -1,320 +1,325 b''
1 1 define([
2 2 'base/js/namespace',
3 3 'jquery',
4 4 'base/js/utils',
5 5 'base/js/dialog',
6 6 'base/js/notificationarea',
7 7 'moment'
8 8 ], function(IPython, $, utils, dialog, notificationarea, moment) {
9 9 "use strict";
10 10 var NotificationArea = notificationarea.NotificationArea;
11 11
12 12 var NotebookNotificationArea = function(selector, options) {
13 13 NotificationArea.apply(this, [selector, options]);
14 14 this.save_widget = options.save_widget;
15 15 this.notebook = options.notebook;
16 16 this.keyboard_manager = options.keyboard_manager;
17 17 }
18 18
19 19 NotebookNotificationArea.prototype = Object.create(NotificationArea.prototype);
20 20
21 21 /**
22 22 * Initialize the default set of notification widgets.
23 23 *
24 24 * @method init_notification_widgets
25 25 */
26 26 NotebookNotificationArea.prototype.init_notification_widgets = function () {
27 27 this.init_kernel_notification_widget();
28 28 this.init_notebook_notification_widget();
29 29 };
30 30
31 31 /**
32 32 * Initialize the notification widget for kernel status messages.
33 33 *
34 34 * @method init_kernel_notification_widget
35 35 */
36 36 NotebookNotificationArea.prototype.init_kernel_notification_widget = function () {
37 37 var that = this;
38 38 var knw = this.new_notification_widget('kernel');
39 39 var $kernel_ind_icon = $("#kernel_indicator_icon");
40 40 var $modal_ind_icon = $("#modal_indicator_icon");
41 41
42 42 // Command/Edit mode
43 43 this.events.on('edit_mode.Notebook', function () {
44 44 that.save_widget.update_document_title();
45 45 $modal_ind_icon.attr('class','edit_mode_icon').attr('title','Edit Mode');
46 46 });
47 47
48 48 this.events.on('command_mode.Notebook', function () {
49 49 that.save_widget.update_document_title();
50 50 $modal_ind_icon.attr('class','command_mode_icon').attr('title','Command Mode');
51 51 });
52 52
53 53 // Implicitly start off in Command mode, switching to Edit mode will trigger event
54 54 $modal_ind_icon.attr('class','command_mode_icon').attr('title','Command Mode');
55 55
56 56 // Kernel events
57 57
58 58 // this can be either kernel_created.Kernel or kernel_created.Session
59 59 this.events.on('kernel_created.Kernel kernel_created.Session', function () {
60 60 knw.info("Kernel Created", 500);
61 61 });
62 62
63 63 this.events.on('kernel_reconnecting.Kernel', function () {
64 64 knw.warning("Connecting to kernel");
65 65 });
66 66
67 67 this.events.on('kernel_connection_dead.Kernel', function (evt, info) {
68 68 knw.danger("Not Connected", undefined, function () {
69 69 // schedule reconnect a short time in the future, don't reconnect immediately
70 70 setTimeout($.proxy(info.kernel.reconnect, info.kernel), 500);
71 71 }, {title: 'click to reconnect'});
72 72 });
73 73
74 74 this.events.on('kernel_connected.Kernel', function () {
75 75 knw.info("Connected", 500);
76 76 });
77 77
78 78 this.events.on('kernel_restarting.Kernel', function () {
79 79 that.save_widget.update_document_title();
80 80 knw.set_message("Restarting kernel", 2000);
81 81 });
82 82
83 83 this.events.on('kernel_autorestarting.Kernel', function (evt, info) {
84 84 // Only show the dialog on the first restart attempt. This
85 85 // number gets tracked by the `Kernel` object and passed
86 86 // along here, because we don't want to show the user 5
87 87 // dialogs saying the same thing (which is the number of
88 88 // times it tries restarting).
89 89 if (info.attempt === 1) {
90 90
91 91 dialog.kernel_modal({
92 92 notebook: that.notebook,
93 93 keyboard_manager: that.keyboard_manager,
94 94 title: "Kernel Restarting",
95 95 body: "The kernel appears to have died. It will restart automatically.",
96 96 buttons: {
97 97 OK : {
98 98 class : "btn-primary"
99 99 }
100 100 }
101 101 });
102 102 };
103 103
104 104 that.save_widget.update_document_title();
105 105 knw.danger("Dead kernel");
106 106 $kernel_ind_icon.attr('class','kernel_dead_icon').attr('title','Kernel Dead');
107 107 });
108 108
109 109 this.events.on('kernel_interrupting.Kernel', function () {
110 110 knw.set_message("Interrupting kernel", 2000);
111 111 });
112 112
113 113 this.events.on('kernel_disconnected.Kernel', function () {
114 114 $kernel_ind_icon
115 115 .attr('class', 'kernel_disconnected_icon')
116 116 .attr('title', 'No Connection to Kernel');
117 117 });
118 118
119 119 this.events.on('kernel_connection_failed.Kernel', function (evt, info) {
120 120 // only show the dialog if this is the first failed
121 121 // connect attempt, because the kernel will continue
122 122 // trying to reconnect and we don't want to spam the user
123 123 // with messages
124 124 if (info.attempt === 1) {
125 125
126 126 var msg = "A connection to the notebook server could not be established." +
127 127 " The notebook will continue trying to reconnect, but" +
128 128 " until it does, you will NOT be able to run code. Check your" +
129 129 " network connection or notebook server configuration.";
130 130
131 131 dialog.kernel_modal({
132 132 title: "Connection failed",
133 133 body: msg,
134 134 keyboard_manager: that.keyboard_manager,
135 135 notebook: that.notebook,
136 136 buttons : {
137 137 "OK": {}
138 138 }
139 139 });
140 140 }
141 141 });
142 142
143 143 this.events.on('kernel_killed.Kernel kernel_killed.Session', function () {
144 144 that.save_widget.update_document_title();
145 145 knw.danger("Dead kernel");
146 146 $kernel_ind_icon.attr('class','kernel_dead_icon').attr('title','Kernel Dead');
147 147 });
148 148
149 149 this.events.on('kernel_dead.Kernel', function () {
150 150
151 151 var showMsg = function () {
152 152
153 153 var msg = 'The kernel has died, and the automatic restart has failed.' +
154 154 ' It is possible the kernel cannot be restarted.' +
155 155 ' If you are not able to restart the kernel, you will still be able to save' +
156 156 ' the notebook, but running code will no longer work until the notebook' +
157 157 ' is reopened.';
158 158
159 159 dialog.kernel_modal({
160 160 title: "Dead kernel",
161 161 body : msg,
162 162 keyboard_manager: that.keyboard_manager,
163 163 notebook: that.notebook,
164 164 buttons : {
165 165 "Manual Restart": {
166 166 class: "btn-danger",
167 167 click: function () {
168 168 that.notebook.start_session();
169 169 }
170 170 },
171 171 "Don't restart": {}
172 172 }
173 173 });
174 174
175 175 return false;
176 176 };
177 177
178 178 that.save_widget.update_document_title();
179 179 knw.danger("Dead kernel", undefined, showMsg);
180 180 $kernel_ind_icon.attr('class','kernel_dead_icon').attr('title','Kernel Dead');
181 181
182 182 showMsg();
183 183 });
184 184
185 185 this.events.on('kernel_dead.Session', function (evt, info) {
186 186 var full = info.xhr.responseJSON.message;
187 187 var short = info.xhr.responseJSON.short_message || 'Kernel error';
188 188 var traceback = info.xhr.responseJSON.traceback;
189 189
190 190 var showMsg = function () {
191 191 var msg = $('<div/>').append($('<p/>').text(full));
192 192 var cm, cm_elem, cm_open;
193 193
194 194 if (traceback) {
195 195 cm_elem = $('<div/>')
196 196 .css('margin-top', '1em')
197 197 .css('padding', '1em')
198 198 .addClass('output_scroll');
199 199 msg.append(cm_elem);
200 200 cm = CodeMirror(cm_elem.get(0), {
201 201 mode: "python",
202 202 readOnly : true
203 203 });
204 204 cm.setValue(traceback);
205 205 cm_open = $.proxy(cm.refresh, cm);
206 206 }
207 207
208 208 dialog.kernel_modal({
209 209 title: "Failed to start the kernel",
210 210 body : msg,
211 211 keyboard_manager: that.keyboard_manager,
212 212 notebook: that.notebook,
213 213 open: cm_open,
214 214 buttons : {
215 215 "Ok": { class: 'btn-primary' }
216 216 }
217 217 });
218 218
219 219 return false;
220 220 };
221 221
222 222 that.save_widget.update_document_title();
223 223 $kernel_ind_icon.attr('class','kernel_dead_icon').attr('title','Kernel Dead');
224 224 knw.danger(short, undefined, showMsg);
225 225 });
226 226
227 227 this.events.on('kernel_starting.Kernel', function () {
228 228 window.document.title='(Starting) '+window.document.title;
229 229 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
230 230 knw.set_message("Kernel starting, please wait...");
231 231 });
232 232
233 233 this.events.on('kernel_ready.Kernel', function () {
234 234 that.save_widget.update_document_title();
235 235 $kernel_ind_icon.attr('class','kernel_idle_icon').attr('title','Kernel Idle');
236 236 knw.info("Kernel ready", 500);
237 237 });
238 238
239 239 this.events.on('kernel_idle.Kernel', function () {
240 240 that.save_widget.update_document_title();
241 241 $kernel_ind_icon.attr('class','kernel_idle_icon').attr('title','Kernel Idle');
242 242 });
243 243
244 244 this.events.on('kernel_busy.Kernel', function () {
245 245 window.document.title='(Busy) '+window.document.title;
246 246 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
247 247 });
248 248
249 249 // Start the kernel indicator in the busy state, and send a kernel_info request.
250 250 // When the kernel_info reply arrives, the kernel is idle.
251 251 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
252 252 };
253 253
254 254 /**
255 255 * Initialize the notification widget for notebook status messages.
256 256 *
257 257 * @method init_notebook_notification_widget
258 258 */
259 259 NotebookNotificationArea.prototype.init_notebook_notification_widget = function () {
260 260 var nnw = this.new_notification_widget('notebook');
261 261
262 262 // Notebook events
263 263 this.events.on('notebook_loading.Notebook', function () {
264 264 nnw.set_message("Loading notebook",500);
265 265 });
266 266 this.events.on('notebook_loaded.Notebook', function () {
267 267 nnw.set_message("Notebook loaded",500);
268 268 });
269 269 this.events.on('notebook_saving.Notebook', function () {
270 270 nnw.set_message("Saving notebook",500);
271 271 });
272 272 this.events.on('notebook_saved.Notebook', function () {
273 273 nnw.set_message("Notebook saved",2000);
274 274 });
275 275 this.events.on('notebook_save_failed.Notebook', function (evt, error) {
276 276 nnw.warning(error.message || "Notebook save failed");
277 277 });
278 278 this.events.on('notebook_copy_failed.Notebook', function (evt, error) {
279 279 nnw.warning(error.message || "Notebook copy failed");
280 280 });
281 281
282 this.events.on('toggle_unsolicited_msgs.Notebook', function(evt, ignored) {
283 var msg = (ignored? "Ignoring": "Showing") + " I/O from external clients";
284 nnw.set_message(msg, 1000);
285 });
286
282 287 // Checkpoint events
283 288 this.events.on('checkpoint_created.Notebook', function (evt, data) {
284 289 var msg = "Checkpoint created";
285 290 if (data.last_modified) {
286 291 var d = new Date(data.last_modified);
287 292 msg = msg + ": " + moment(d).format("HH:mm:ss");
288 293 }
289 294 nnw.set_message(msg, 2000);
290 295 });
291 296 this.events.on('checkpoint_failed.Notebook', function () {
292 297 nnw.warning("Checkpoint failed");
293 298 });
294 299 this.events.on('checkpoint_deleted.Notebook', function () {
295 300 nnw.set_message("Checkpoint deleted", 500);
296 301 });
297 302 this.events.on('checkpoint_delete_failed.Notebook', function () {
298 303 nnw.warning("Checkpoint delete failed");
299 304 });
300 305 this.events.on('checkpoint_restoring.Notebook', function () {
301 306 nnw.set_message("Restoring to checkpoint...", 500);
302 307 });
303 308 this.events.on('checkpoint_restore_failed.Notebook', function () {
304 309 nnw.warning("Checkpoint restore failed");
305 310 });
306 311
307 312 // Autosave events
308 313 this.events.on('autosave_disabled.Notebook', function () {
309 314 nnw.set_message("Autosave disabled", 2000);
310 315 });
311 316 this.events.on('autosave_enabled.Notebook', function (evt, interval) {
312 317 nnw.set_message("Saving every " + interval / 1000 + "s", 1000);
313 318 });
314 319 };
315 320
316 321 // Backwards compatibility.
317 322 IPython.NotificationArea = NotebookNotificationArea;
318 323
319 324 return {'NotebookNotificationArea': NotebookNotificationArea};
320 325 });
@@ -1,1052 +1,1073 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'base/js/utils',
8 8 './comm',
9 9 './serialize',
10 10 'widgets/js/init'
11 11 ], function(IPython, $, utils, comm, serialize, widgetmanager) {
12 12 "use strict";
13 13
14 14 /**
15 15 * A Kernel class to communicate with the Python kernel. This
16 16 * should generally not be constructed directly, but be created
17 17 * by. the `Session` object. Once created, this object should be
18 18 * used to communicate with the kernel.
19 19 *
20 20 * @class Kernel
21 21 * @param {string} kernel_service_url - the URL to access the kernel REST api
22 22 * @param {string} ws_url - the websockets URL
23 23 * @param {Notebook} notebook - notebook object
24 24 * @param {string} name - the kernel type (e.g. python3)
25 25 */
26 26 var Kernel = function (kernel_service_url, ws_url, notebook, name) {
27 27 this.events = notebook.events;
28 28
29 29 this.id = null;
30 30 this.name = name;
31 31
32 32 this.channels = {
33 33 'shell': null,
34 34 'iopub': null,
35 35 'stdin': null
36 36 };
37 37
38 38 this.kernel_service_url = kernel_service_url;
39 39 this.kernel_url = null;
40 40 this.ws_url = ws_url || IPython.utils.get_body_data("wsUrl");
41 41 if (!this.ws_url) {
42 42 // trailing 's' in https will become wss for secure web sockets
43 43 this.ws_url = location.protocol.replace('http', 'ws') + "//" + location.host;
44 44 }
45 45
46 46 this.username = "username";
47 47 this.session_id = utils.uuid();
48 48 this._msg_callbacks = {};
49 49 this.info_reply = {}; // kernel_info_reply stored here after starting
50 this.unsolicited_msg_callback = null;
50 51
51 52 if (typeof(WebSocket) !== 'undefined') {
52 53 this.WebSocket = WebSocket;
53 54 } else if (typeof(MozWebSocket) !== 'undefined') {
54 55 this.WebSocket = MozWebSocket;
55 56 } else {
56 57 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.');
57 58 }
58 59
59 60 this.bind_events();
60 61 this.init_iopub_handlers();
61 62 this.comm_manager = new comm.CommManager(this);
62 63 this.widget_manager = new widgetmanager.WidgetManager(this.comm_manager, notebook);
63 64
64 65 this.last_msg_id = null;
65 66 this.last_msg_callbacks = {};
66 67
67 68 this._autorestart_attempt = 0;
68 69 this._reconnect_attempt = 0;
69 70 this.reconnect_limit = 7;
70 71 };
71 72
72 73 /**
73 74 * @function _get_msg
74 75 */
75 76 Kernel.prototype._get_msg = function (msg_type, content, metadata, buffers) {
76 77 var msg = {
77 78 header : {
78 79 msg_id : utils.uuid(),
79 80 username : this.username,
80 81 session : this.session_id,
81 82 msg_type : msg_type,
82 83 version : "5.0"
83 84 },
84 85 metadata : metadata || {},
85 86 content : content,
86 87 buffers : buffers || [],
87 88 parent_header : {}
88 89 };
89 90 return msg;
90 91 };
91 92
92 93 /**
93 94 * @function bind_events
94 95 */
95 96 Kernel.prototype.bind_events = function () {
96 97 var that = this;
97 98 this.events.on('send_input_reply.Kernel', function(evt, data) {
98 99 that.send_input_reply(data);
99 100 });
100 101
101 102 var record_status = function (evt, info) {
102 103 console.log('Kernel: ' + evt.type + ' (' + info.kernel.id + ')');
103 104 };
104 105
105 106 this.events.on('kernel_created.Kernel', record_status);
106 107 this.events.on('kernel_reconnecting.Kernel', record_status);
107 108 this.events.on('kernel_connected.Kernel', record_status);
108 109 this.events.on('kernel_starting.Kernel', record_status);
109 110 this.events.on('kernel_restarting.Kernel', record_status);
110 111 this.events.on('kernel_autorestarting.Kernel', record_status);
111 112 this.events.on('kernel_interrupting.Kernel', record_status);
112 113 this.events.on('kernel_disconnected.Kernel', record_status);
113 114 // these are commented out because they are triggered a lot, but can
114 115 // be uncommented for debugging purposes
115 116 //this.events.on('kernel_idle.Kernel', record_status);
116 117 //this.events.on('kernel_busy.Kernel', record_status);
117 118 this.events.on('kernel_ready.Kernel', record_status);
118 119 this.events.on('kernel_killed.Kernel', record_status);
119 120 this.events.on('kernel_dead.Kernel', record_status);
120 121
121 122 this.events.on('kernel_ready.Kernel', function () {
122 123 that._autorestart_attempt = 0;
123 124 });
124 125 this.events.on('kernel_connected.Kernel', function () {
125 126 that._reconnect_attempt = 0;
126 127 });
127 128 };
128 129
129 130 /**
130 131 * Initialize the iopub handlers.
131 132 *
132 133 * @function init_iopub_handlers
133 134 */
134 135 Kernel.prototype.init_iopub_handlers = function () {
135 136 var output_msg_types = ['stream', 'display_data', 'execute_result', 'error'];
136 137 this._iopub_handlers = {};
137 138 this.register_iopub_handler('status', $.proxy(this._handle_status_message, this));
138 139 this.register_iopub_handler('clear_output', $.proxy(this._handle_clear_output, this));
140 this.register_iopub_handler('execute_input', $.proxy(this._handle_input_message, this));
139 141
140 142 for (var i=0; i < output_msg_types.length; i++) {
141 143 this.register_iopub_handler(output_msg_types[i], $.proxy(this._handle_output_message, this));
142 144 }
143 145 };
144 146
145 147 /**
146 148 * GET /api/kernels
147 149 *
148 150 * Get the list of running kernels.
149 151 *
150 152 * @function list
151 153 * @param {function} [success] - function executed on ajax success
152 154 * @param {function} [error] - functon executed on ajax error
153 155 */
154 156 Kernel.prototype.list = function (success, error) {
155 157 $.ajax(this.kernel_service_url, {
156 158 processData: false,
157 159 cache: false,
158 160 type: "GET",
159 161 dataType: "json",
160 162 success: success,
161 163 error: this._on_error(error)
162 164 });
163 165 };
164 166
165 167 /**
166 168 * POST /api/kernels
167 169 *
168 170 * Start a new kernel.
169 171 *
170 172 * In general this shouldn't be used -- the kernel should be
171 173 * started through the session API. If you use this function and
172 174 * are also using the session API then your session and kernel
173 175 * WILL be out of sync!
174 176 *
175 177 * @function start
176 178 * @param {params} [Object] - parameters to include in the query string
177 179 * @param {function} [success] - function executed on ajax success
178 180 * @param {function} [error] - functon executed on ajax error
179 181 */
180 182 Kernel.prototype.start = function (params, success, error) {
181 183 var url = this.kernel_service_url;
182 184 var qs = $.param(params || {}); // query string for sage math stuff
183 185 if (qs !== "") {
184 186 url = url + "?" + qs;
185 187 }
186 188
187 189 var that = this;
188 190 var on_success = function (data, status, xhr) {
189 191 that.events.trigger('kernel_created.Kernel', {kernel: that});
190 192 that._kernel_created(data);
191 193 if (success) {
192 194 success(data, status, xhr);
193 195 }
194 196 };
195 197
196 198 $.ajax(url, {
197 199 processData: false,
198 200 cache: false,
199 201 type: "POST",
200 202 data: JSON.stringify({name: this.name}),
201 203 dataType: "json",
202 204 success: this._on_success(on_success),
203 205 error: this._on_error(error)
204 206 });
205 207
206 208 return url;
207 209 };
208 210
209 211 /**
210 212 * GET /api/kernels/[:kernel_id]
211 213 *
212 214 * Get information about the kernel.
213 215 *
214 216 * @function get_info
215 217 * @param {function} [success] - function executed on ajax success
216 218 * @param {function} [error] - functon executed on ajax error
217 219 */
218 220 Kernel.prototype.get_info = function (success, error) {
219 221 $.ajax(this.kernel_url, {
220 222 processData: false,
221 223 cache: false,
222 224 type: "GET",
223 225 dataType: "json",
224 226 success: this._on_success(success),
225 227 error: this._on_error(error)
226 228 });
227 229 };
228 230
229 231 /**
230 232 * DELETE /api/kernels/[:kernel_id]
231 233 *
232 234 * Shutdown the kernel.
233 235 *
234 236 * If you are also using sessions, then this function shoul NOT be
235 237 * used. Instead, use Session.delete. Otherwise, the session and
236 238 * kernel WILL be out of sync.
237 239 *
238 240 * @function kill
239 241 * @param {function} [success] - function executed on ajax success
240 242 * @param {function} [error] - functon executed on ajax error
241 243 */
242 244 Kernel.prototype.kill = function (success, error) {
243 245 this.events.trigger('kernel_killed.Kernel', {kernel: this});
244 246 this._kernel_dead();
245 247 $.ajax(this.kernel_url, {
246 248 processData: false,
247 249 cache: false,
248 250 type: "DELETE",
249 251 dataType: "json",
250 252 success: this._on_success(success),
251 253 error: this._on_error(error)
252 254 });
253 255 };
254 256
255 257 /**
256 258 * POST /api/kernels/[:kernel_id]/interrupt
257 259 *
258 260 * Interrupt the kernel.
259 261 *
260 262 * @function interrupt
261 263 * @param {function} [success] - function executed on ajax success
262 264 * @param {function} [error] - functon executed on ajax error
263 265 */
264 266 Kernel.prototype.interrupt = function (success, error) {
265 267 this.events.trigger('kernel_interrupting.Kernel', {kernel: this});
266 268
267 269 var that = this;
268 270 var on_success = function (data, status, xhr) {
269 271 // get kernel info so we know what state the kernel is in
270 272 that.kernel_info();
271 273 if (success) {
272 274 success(data, status, xhr);
273 275 }
274 276 };
275 277
276 278 var url = utils.url_join_encode(this.kernel_url, 'interrupt');
277 279 $.ajax(url, {
278 280 processData: false,
279 281 cache: false,
280 282 type: "POST",
281 283 dataType: "json",
282 284 success: this._on_success(on_success),
283 285 error: this._on_error(error)
284 286 });
285 287 };
286 288
287 289 /**
288 290 * POST /api/kernels/[:kernel_id]/restart
289 291 *
290 292 * Restart the kernel.
291 293 *
292 294 * @function interrupt
293 295 * @param {function} [success] - function executed on ajax success
294 296 * @param {function} [error] - functon executed on ajax error
295 297 */
296 298 Kernel.prototype.restart = function (success, error) {
297 299 this.events.trigger('kernel_restarting.Kernel', {kernel: this});
298 300 this.stop_channels();
299 301
300 302 var that = this;
301 303 var on_success = function (data, status, xhr) {
302 304 that.events.trigger('kernel_created.Kernel', {kernel: that});
303 305 that._kernel_created(data);
304 306 if (success) {
305 307 success(data, status, xhr);
306 308 }
307 309 };
308 310
309 311 var on_error = function (xhr, status, err) {
310 312 that.events.trigger('kernel_dead.Kernel', {kernel: that});
311 313 that._kernel_dead();
312 314 if (error) {
313 315 error(xhr, status, err);
314 316 }
315 317 };
316 318
317 319 var url = utils.url_join_encode(this.kernel_url, 'restart');
318 320 $.ajax(url, {
319 321 processData: false,
320 322 cache: false,
321 323 type: "POST",
322 324 dataType: "json",
323 325 success: this._on_success(on_success),
324 326 error: this._on_error(on_error)
325 327 });
326 328 };
327 329
328 330 /**
329 331 * Reconnect to a disconnected kernel. This is not actually a
330 332 * standard HTTP request, but useful function nonetheless for
331 333 * reconnecting to the kernel if the connection is somehow lost.
332 334 *
333 335 * @function reconnect
334 336 */
335 337 Kernel.prototype.reconnect = function () {
336 338 if (this.is_connected()) {
337 339 return;
338 340 }
339 341 this._reconnect_attempt = this._reconnect_attempt + 1;
340 342 this.events.trigger('kernel_reconnecting.Kernel', {
341 343 kernel: this,
342 344 attempt: this._reconnect_attempt,
343 345 });
344 346 this.start_channels();
345 347 };
346 348
347 349 /**
348 350 * Handle a successful AJAX request by updating the kernel id and
349 351 * name from the response, and then optionally calling a provided
350 352 * callback.
351 353 *
352 354 * @function _on_success
353 355 * @param {function} success - callback
354 356 */
355 357 Kernel.prototype._on_success = function (success) {
356 358 var that = this;
357 359 return function (data, status, xhr) {
358 360 if (data) {
359 361 that.id = data.id;
360 362 that.name = data.name;
361 363 }
362 364 that.kernel_url = utils.url_join_encode(that.kernel_service_url, that.id);
363 365 if (success) {
364 366 success(data, status, xhr);
365 367 }
366 368 };
367 369 };
368 370
369 371 /**
370 372 * Handle a failed AJAX request by logging the error message, and
371 373 * then optionally calling a provided callback.
372 374 *
373 375 * @function _on_error
374 376 * @param {function} error - callback
375 377 */
376 378 Kernel.prototype._on_error = function (error) {
377 379 return function (xhr, status, err) {
378 380 utils.log_ajax_error(xhr, status, err);
379 381 if (error) {
380 382 error(xhr, status, err);
381 383 }
382 384 };
383 385 };
384 386
385 387 /**
386 388 * Perform necessary tasks once the kernel has been started,
387 389 * including actually connecting to the kernel.
388 390 *
389 391 * @function _kernel_created
390 392 * @param {Object} data - information about the kernel including id
391 393 */
392 394 Kernel.prototype._kernel_created = function (data) {
393 395 this.id = data.id;
394 396 this.kernel_url = utils.url_join_encode(this.kernel_service_url, this.id);
395 397 this.start_channels();
396 398 };
397 399
398 400 /**
399 401 * Perform necessary tasks once the connection to the kernel has
400 402 * been established. This includes requesting information about
401 403 * the kernel.
402 404 *
403 405 * @function _kernel_connected
404 406 */
405 407 Kernel.prototype._kernel_connected = function () {
406 408 this.events.trigger('kernel_connected.Kernel', {kernel: this});
407 409 this.events.trigger('kernel_starting.Kernel', {kernel: this});
408 410 // get kernel info so we know what state the kernel is in
409 411 var that = this;
410 412 this.kernel_info(function (reply) {
411 413 that.info_reply = reply.content;
412 414 that.events.trigger('kernel_ready.Kernel', {kernel: that});
413 415 });
414 416 };
415 417
416 418 /**
417 419 * Perform necessary tasks after the kernel has died. This closing
418 420 * communication channels to the kernel if they are still somehow
419 421 * open.
420 422 *
421 423 * @function _kernel_dead
422 424 */
423 425 Kernel.prototype._kernel_dead = function () {
424 426 this.stop_channels();
425 427 };
426 428
427 429 /**
428 430 * Start the `shell`and `iopub` channels.
429 431 * Will stop and restart them if they already exist.
430 432 *
431 433 * @function start_channels
432 434 */
433 435 Kernel.prototype.start_channels = function () {
434 436 var that = this;
435 437 this.stop_channels();
436 438 var ws_host_url = this.ws_url + this.kernel_url;
437 439
438 440 console.log("Starting WebSockets:", ws_host_url);
439 441
440 442 var channel_url = function(channel) {
441 443 return [
442 444 that.ws_url,
443 445 utils.url_join_encode(that.kernel_url, channel),
444 446 "?session_id=" + that.session_id
445 447 ].join('');
446 448 };
447 449 this.channels.shell = new this.WebSocket(channel_url("shell"));
448 450 this.channels.stdin = new this.WebSocket(channel_url("stdin"));
449 451 this.channels.iopub = new this.WebSocket(channel_url("iopub"));
450 452
451 453 var already_called_onclose = false; // only alert once
452 454 var ws_closed_early = function(evt){
453 455 if (already_called_onclose){
454 456 return;
455 457 }
456 458 already_called_onclose = true;
457 459 if ( ! evt.wasClean ){
458 460 // If the websocket was closed early, that could mean
459 461 // that the kernel is actually dead. Try getting
460 462 // information about the kernel from the API call --
461 463 // if that fails, then assume the kernel is dead,
462 464 // otherwise just follow the typical websocket closed
463 465 // protocol.
464 466 that.get_info(function () {
465 467 that._ws_closed(ws_host_url, false);
466 468 }, function () {
467 469 that.events.trigger('kernel_dead.Kernel', {kernel: that});
468 470 that._kernel_dead();
469 471 });
470 472 }
471 473 };
472 474 var ws_closed_late = function(evt){
473 475 if (already_called_onclose){
474 476 return;
475 477 }
476 478 already_called_onclose = true;
477 479 if ( ! evt.wasClean ){
478 480 that._ws_closed(ws_host_url, false);
479 481 }
480 482 };
481 483 var ws_error = function(evt){
482 484 if (already_called_onclose){
483 485 return;
484 486 }
485 487 already_called_onclose = true;
486 488 that._ws_closed(ws_host_url, true);
487 489 };
488 490
489 491 for (var c in this.channels) {
490 492 this.channels[c].onopen = $.proxy(this._ws_opened, this);
491 493 this.channels[c].onclose = ws_closed_early;
492 494 this.channels[c].onerror = ws_error;
493 495 }
494 496 // switch from early-close to late-close message after 1s
495 497 setTimeout(function() {
496 498 for (var c in that.channels) {
497 499 if (that.channels[c] !== null) {
498 500 that.channels[c].onclose = ws_closed_late;
499 501 }
500 502 }
501 503 }, 1000);
502 504 this.channels.shell.onmessage = $.proxy(this._handle_shell_reply, this);
503 505 this.channels.iopub.onmessage = $.proxy(this._handle_iopub_message, this);
504 506 this.channels.stdin.onmessage = $.proxy(this._handle_input_request, this);
505 507 };
506 508
507 509 /**
508 510 * Handle a websocket entering the open state,
509 511 * signaling that the kernel is connected when all channels are open.
510 512 *
511 513 * @function _ws_opened
512 514 */
513 515 Kernel.prototype._ws_opened = function (evt) {
514 516 if (this.is_connected()) {
515 517 // all events ready, trigger started event.
516 518 this._kernel_connected();
517 519 }
518 520 };
519 521
520 522 /**
521 523 * Handle a websocket entering the closed state. This closes the
522 524 * other communication channels if they are open. If the websocket
523 525 * was not closed due to an error, try to reconnect to the kernel.
524 526 *
525 527 * @function _ws_closed
526 528 * @param {string} ws_url - the websocket url
527 529 * @param {bool} error - whether the connection was closed due to an error
528 530 */
529 531 Kernel.prototype._ws_closed = function(ws_url, error) {
530 532 this.stop_channels();
531 533
532 534 this.events.trigger('kernel_disconnected.Kernel', {kernel: this});
533 535 if (error) {
534 536 console.log('WebSocket connection failed: ', ws_url);
535 537 this.events.trigger('kernel_connection_failed.Kernel', {kernel: this, ws_url: ws_url, attempt: this._reconnect_attempt});
536 538 }
537 539 this._schedule_reconnect();
538 540 };
539 541
540 542 Kernel.prototype._schedule_reconnect = function () {
541 543 // function to call when kernel connection is lost
542 544 // schedules reconnect, or fires 'connection_dead' if reconnect limit is hit
543 545 if (this._reconnect_attempt < this.reconnect_limit) {
544 546 var timeout = Math.pow(2, this._reconnect_attempt);
545 547 console.log("Connection lost, reconnecting in " + timeout + " seconds.");
546 548 setTimeout($.proxy(this.reconnect, this), 1e3 * timeout);
547 549 } else {
548 550 this.events.trigger('kernel_connection_dead.Kernel', {
549 551 kernel: this,
550 552 reconnect_attempt: this._reconnect_attempt,
551 553 });
552 554 console.log("Failed to reconnect, giving up.");
553 555 }
554 556 };
555 557
556 558 /**
557 559 * Close the websocket channels. After successful close, the value
558 560 * in `this.channels[channel_name]` will be null.
559 561 *
560 562 * @function stop_channels
561 563 */
562 564 Kernel.prototype.stop_channels = function () {
563 565 var that = this;
564 566 var close = function (c) {
565 567 return function () {
566 568 if (that.channels[c] && that.channels[c].readyState === WebSocket.CLOSED) {
567 569 that.channels[c] = null;
568 570 }
569 571 };
570 572 };
571 573 for (var c in this.channels) {
572 574 if ( this.channels[c] !== null ) {
573 575 if (this.channels[c].readyState === WebSocket.OPEN) {
574 576 this.channels[c].onclose = close(c);
575 577 this.channels[c].close();
576 578 } else {
577 579 close(c)();
578 580 }
579 581 }
580 582 }
581 583 };
582 584
583 585 /**
584 586 * Check whether there is a connection to the kernel. This
585 587 * function only returns true if all channel objects have been
586 588 * created and have a state of WebSocket.OPEN.
587 589 *
588 590 * @function is_connected
589 591 * @returns {bool} - whether there is a connection
590 592 */
591 593 Kernel.prototype.is_connected = function () {
592 594 for (var c in this.channels) {
593 595 // if any channel is not ready, then we're not connected
594 596 if (this.channels[c] === null) {
595 597 return false;
596 598 }
597 599 if (this.channels[c].readyState !== WebSocket.OPEN) {
598 600 return false;
599 601 }
600 602 }
601 603 return true;
602 604 };
603 605
604 606 /**
605 607 * Check whether the connection to the kernel has been completely
606 608 * severed. This function only returns true if all channel objects
607 609 * are null.
608 610 *
609 611 * @function is_fully_disconnected
610 612 * @returns {bool} - whether the kernel is fully disconnected
611 613 */
612 614 Kernel.prototype.is_fully_disconnected = function () {
613 615 for (var c in this.channels) {
614 616 if (this.channels[c] === null) {
615 617 return true;
616 618 }
617 619 }
618 620 return false;
619 621 };
620 622
621 623 /**
622 624 * Send a message on the Kernel's shell channel
623 625 *
624 626 * @function send_shell_message
625 627 */
626 628 Kernel.prototype.send_shell_message = function (msg_type, content, callbacks, metadata, buffers) {
627 629 if (!this.is_connected()) {
628 630 throw new Error("kernel is not connected");
629 631 }
630 632 var msg = this._get_msg(msg_type, content, metadata, buffers);
631 633 this.channels.shell.send(serialize.serialize(msg));
632 634 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
633 635 return msg.header.msg_id;
634 636 };
635 637
636 638 /**
637 639 * Get kernel info
638 640 *
639 641 * @function kernel_info
640 642 * @param callback {function}
641 643 *
642 644 * When calling this method, pass a callback function that expects one argument.
643 645 * The callback will be passed the complete `kernel_info_reply` message documented
644 646 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#kernel-info)
645 647 */
646 648 Kernel.prototype.kernel_info = function (callback) {
647 649 var callbacks;
648 650 if (callback) {
649 651 callbacks = { shell : { reply : callback } };
650 652 }
651 653 return this.send_shell_message("kernel_info_request", {}, callbacks);
652 654 };
653 655
654 656 /**
655 657 * Get info on an object
656 658 *
657 659 * When calling this method, pass a callback function that expects one argument.
658 660 * The callback will be passed the complete `inspect_reply` message documented
659 661 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#object-information)
660 662 *
661 663 * @function inspect
662 664 * @param code {string}
663 665 * @param cursor_pos {integer}
664 666 * @param callback {function}
665 667 */
666 668 Kernel.prototype.inspect = function (code, cursor_pos, callback) {
667 669 var callbacks;
668 670 if (callback) {
669 671 callbacks = { shell : { reply : callback } };
670 672 }
671 673
672 674 var content = {
673 675 code : code,
674 676 cursor_pos : cursor_pos,
675 677 detail_level : 0
676 678 };
677 679 return this.send_shell_message("inspect_request", content, callbacks);
678 680 };
679 681
680 682 /**
681 683 * Execute given code into kernel, and pass result to callback.
682 684 *
683 685 * @async
684 686 * @function execute
685 687 * @param {string} code
686 688 * @param [callbacks] {Object} With the following keys (all optional)
687 689 * @param callbacks.shell.reply {function}
688 690 * @param callbacks.shell.payload.[payload_name] {function}
689 691 * @param callbacks.iopub.output {function}
690 692 * @param callbacks.iopub.clear_output {function}
691 693 * @param callbacks.input {function}
692 694 * @param {object} [options]
693 695 * @param [options.silent=false] {Boolean}
694 696 * @param [options.user_expressions=empty_dict] {Dict}
695 697 * @param [options.allow_stdin=false] {Boolean} true|false
696 698 *
697 699 * @example
698 700 *
699 701 * The options object should contain the options for the execute
700 702 * call. Its default values are:
701 703 *
702 704 * options = {
703 705 * silent : true,
704 706 * user_expressions : {},
705 707 * allow_stdin : false
706 708 * }
707 709 *
708 710 * When calling this method pass a callbacks structure of the
709 711 * form:
710 712 *
711 713 * callbacks = {
712 714 * shell : {
713 715 * reply : execute_reply_callback,
714 716 * payload : {
715 717 * set_next_input : set_next_input_callback,
716 718 * }
717 719 * },
718 720 * iopub : {
719 721 * output : output_callback,
720 722 * clear_output : clear_output_callback,
721 723 * },
722 724 * input : raw_input_callback
723 725 * }
724 726 *
725 727 * Each callback will be passed the entire message as a single
726 728 * arugment. Payload handlers will be passed the corresponding
727 729 * payload and the execute_reply message.
728 730 */
729 731 Kernel.prototype.execute = function (code, callbacks, options) {
730 732 var content = {
731 733 code : code,
732 734 silent : true,
733 735 store_history : false,
734 736 user_expressions : {},
735 737 allow_stdin : false
736 738 };
737 739 callbacks = callbacks || {};
738 740 if (callbacks.input !== undefined) {
739 741 content.allow_stdin = true;
740 742 }
741 743 $.extend(true, content, options);
742 744 this.events.trigger('execution_request.Kernel', {kernel: this, content: content});
743 745 return this.send_shell_message("execute_request", content, callbacks);
744 746 };
745 747
746 748 /**
747 749 * When calling this method, pass a function to be called with the
748 750 * `complete_reply` message as its only argument when it arrives.
749 751 *
750 752 * `complete_reply` is documented
751 753 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#complete)
752 754 *
753 755 * @function complete
754 756 * @param code {string}
755 757 * @param cursor_pos {integer}
756 758 * @param callback {function}
757 759 */
758 760 Kernel.prototype.complete = function (code, cursor_pos, callback) {
759 761 var callbacks;
760 762 if (callback) {
761 763 callbacks = { shell : { reply : callback } };
762 764 }
763 765 var content = {
764 766 code : code,
765 767 cursor_pos : cursor_pos
766 768 };
767 769 return this.send_shell_message("complete_request", content, callbacks);
768 770 };
769 771
770 772 /**
771 773 * @function send_input_reply
772 774 */
773 775 Kernel.prototype.send_input_reply = function (input) {
774 776 if (!this.is_connected()) {
775 777 throw new Error("kernel is not connected");
776 778 }
777 779 var content = {
778 780 value : input
779 781 };
780 782 this.events.trigger('input_reply.Kernel', {kernel: this, content: content});
781 783 var msg = this._get_msg("input_reply", content);
782 784 this.channels.stdin.send(serialize.serialize(msg));
783 785 return msg.header.msg_id;
784 786 };
785 787
786 788 /**
787 789 * @function register_iopub_handler
788 790 */
789 791 Kernel.prototype.register_iopub_handler = function (msg_type, callback) {
790 792 this._iopub_handlers[msg_type] = callback;
791 793 };
792 794
793 795 /**
794 796 * Get the iopub handler for a specific message type.
795 797 *
796 798 * @function get_iopub_handler
797 799 */
798 800 Kernel.prototype.get_iopub_handler = function (msg_type) {
799 801 return this._iopub_handlers[msg_type];
800 802 };
801 803
802 804 /**
803 805 * Get callbacks for a specific message.
804 806 *
805 807 * @function get_callbacks_for_msg
806 808 */
807 809 Kernel.prototype.get_callbacks_for_msg = function (msg_id) {
808 810 if (msg_id == this.last_msg_id) {
809 811 return this.last_msg_callbacks;
810 812 } else {
811 813 return this._msg_callbacks[msg_id];
812 814 }
813 815 };
814 816
815 817 /**
816 818 * Clear callbacks for a specific message.
817 819 *
818 820 * @function clear_callbacks_for_msg
819 821 */
820 822 Kernel.prototype.clear_callbacks_for_msg = function (msg_id) {
821 823 if (this._msg_callbacks[msg_id] !== undefined ) {
822 824 delete this._msg_callbacks[msg_id];
823 825 }
824 826 };
825 827
826 828 /**
827 829 * @function _finish_shell
828 830 */
829 831 Kernel.prototype._finish_shell = function (msg_id) {
830 832 var callbacks = this._msg_callbacks[msg_id];
831 833 if (callbacks !== undefined) {
832 834 callbacks.shell_done = true;
833 835 if (callbacks.iopub_done) {
834 836 this.clear_callbacks_for_msg(msg_id);
835 837 }
836 838 }
837 839 };
838 840
839 841 /**
840 842 * @function _finish_iopub
841 843 */
842 844 Kernel.prototype._finish_iopub = function (msg_id) {
843 845 var callbacks = this._msg_callbacks[msg_id];
844 846 if (callbacks !== undefined) {
845 847 callbacks.iopub_done = true;
846 848 if (callbacks.shell_done) {
847 849 this.clear_callbacks_for_msg(msg_id);
848 850 }
849 851 }
850 852 };
851 853
852 854 /**
853 855 * Set callbacks for a particular message.
854 856 * Callbacks should be a struct of the following form:
855 857 * shell : {
856 858 *
857 859 * }
858 860 *
859 861 * @function set_callbacks_for_msg
860 862 */
861 863 Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks) {
862 864 this.last_msg_id = msg_id;
863 865 if (callbacks) {
864 866 // shallow-copy mapping, because we will modify it at the top level
865 867 var cbcopy = this._msg_callbacks[msg_id] = this.last_msg_callbacks = {};
866 868 cbcopy.shell = callbacks.shell;
867 869 cbcopy.iopub = callbacks.iopub;
868 870 cbcopy.input = callbacks.input;
869 871 cbcopy.shell_done = (!callbacks.shell);
870 872 cbcopy.iopub_done = (!callbacks.iopub);
871 873 } else {
872 874 this.last_msg_callbacks = {};
873 875 }
874 876 };
875 877
876 878 /**
877 879 * @function _handle_shell_reply
878 880 */
879 881 Kernel.prototype._handle_shell_reply = function (e) {
880 882 serialize.deserialize(e.data, $.proxy(this._finish_shell_reply, this));
881 883 };
882 884
883 885 Kernel.prototype._finish_shell_reply = function (reply) {
884 886 this.events.trigger('shell_reply.Kernel', {kernel: this, reply:reply});
885 887 var content = reply.content;
886 888 var metadata = reply.metadata;
887 889 var parent_id = reply.parent_header.msg_id;
888 890 var callbacks = this.get_callbacks_for_msg(parent_id);
889 891 if (!callbacks || !callbacks.shell) {
890 892 return;
891 893 }
892 894 var shell_callbacks = callbacks.shell;
893 895
894 896 // signal that shell callbacks are done
895 897 this._finish_shell(parent_id);
896 898
897 899 if (shell_callbacks.reply !== undefined) {
898 900 shell_callbacks.reply(reply);
899 901 }
900 902 if (content.payload && shell_callbacks.payload) {
901 903 this._handle_payloads(content.payload, shell_callbacks.payload, reply);
902 904 }
903 905 };
904 906
905 907 /**
906 908 * @function _handle_payloads
907 909 */
908 910 Kernel.prototype._handle_payloads = function (payloads, payload_callbacks, msg) {
909 911 var l = payloads.length;
910 912 // Payloads are handled by triggering events because we don't want the Kernel
911 913 // to depend on the Notebook or Pager classes.
912 914 for (var i=0; i<l; i++) {
913 915 var payload = payloads[i];
914 916 var callback = payload_callbacks[payload.source];
915 917 if (callback) {
916 918 callback(payload, msg);
917 919 }
918 920 }
919 921 };
920 922
921 923 /**
922 924 * @function _handle_status_message
923 925 */
924 926 Kernel.prototype._handle_status_message = function (msg) {
925 927 var execution_state = msg.content.execution_state;
926 928 var parent_id = msg.parent_header.msg_id;
927 929
928 930 // dispatch status msg callbacks, if any
929 931 var callbacks = this.get_callbacks_for_msg(parent_id);
930 932 if (callbacks && callbacks.iopub && callbacks.iopub.status) {
931 933 try {
932 934 callbacks.iopub.status(msg);
933 935 } catch (e) {
934 936 console.log("Exception in status msg handler", e, e.stack);
935 937 }
936 938 }
937 939
938 940 if (execution_state === 'busy') {
939 941 this.events.trigger('kernel_busy.Kernel', {kernel: this});
940 942
941 943 } else if (execution_state === 'idle') {
942 944 // signal that iopub callbacks are (probably) done
943 945 // async output may still arrive,
944 946 // but only for the most recent request
945 947 this._finish_iopub(parent_id);
946 948
947 949 // trigger status_idle event
948 950 this.events.trigger('kernel_idle.Kernel', {kernel: this});
949 951
950 952 } else if (execution_state === 'starting') {
951 953 this.events.trigger('kernel_starting.Kernel', {kernel: this});
952 954 var that = this;
953 955 this.kernel_info(function (reply) {
954 956 that.info_reply = reply.content;
955 957 that.events.trigger('kernel_ready.Kernel', {kernel: that});
956 958 });
957 959
958 960 } else if (execution_state === 'restarting') {
959 961 // autorestarting is distinct from restarting,
960 962 // in that it means the kernel died and the server is restarting it.
961 963 // kernel_restarting sets the notification widget,
962 964 // autorestart shows the more prominent dialog.
963 965 this._autorestart_attempt = this._autorestart_attempt + 1;
964 966 this.events.trigger('kernel_restarting.Kernel', {kernel: this});
965 967 this.events.trigger('kernel_autorestarting.Kernel', {kernel: this, attempt: this._autorestart_attempt});
966 968
967 969 } else if (execution_state === 'dead') {
968 970 this.events.trigger('kernel_dead.Kernel', {kernel: this});
969 971 this._kernel_dead();
970 972 }
971 973 };
972 974
973 975 /**
974 976 * Handle clear_output message
975 977 *
976 978 * @function _handle_clear_output
977 979 */
978 980 Kernel.prototype._handle_clear_output = function (msg) {
979 981 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
980 982 if (!callbacks || !callbacks.iopub) {
981 983 return;
982 984 }
983 985 var callback = callbacks.iopub.clear_output;
984 986 if (callback) {
985 987 callback(msg);
986 988 }
987 989 };
988 990
989 991 /**
990 992 * handle an output message (execute_result, display_data, etc.)
991 993 *
992 994 * @function _handle_output_message
993 995 */
994 996 Kernel.prototype._handle_output_message = function (msg) {
995 997 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
996 998 if (!callbacks || !callbacks.iopub) {
999 if (this.unsolicited_msg_callback) {
1000 // The message came from another client. Let the UI decide what
1001 // to do with it.
1002 this.unsolicited_msg_callback(msg);
1003 }
997 1004 return;
998 1005 }
999 1006 var callback = callbacks.iopub.output;
1000 1007 if (callback) {
1001 1008 callback(msg);
1002 1009 }
1003 1010 };
1004 1011
1005 1012 /**
1013 * Handle an input message (execute_input).
1014 *
1015 * @function _handle_input message
1016 */
1017 Kernel.prototype._handle_input_message = function (msg) {
1018 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
1019 if (!callbacks && this.unsolicited_msg_callback) {
1020 // The message came from another client. Let the UI decide what to
1021 // do with it.
1022 this.unsolicited_msg_callback(msg);
1023 }
1024 };
1025
1026 /**
1006 1027 * Dispatch IOPub messages to respective handlers. Each message
1007 1028 * type should have a handler.
1008 1029 *
1009 1030 * @function _handle_iopub_message
1010 1031 */
1011 1032 Kernel.prototype._handle_iopub_message = function (e) {
1012 1033 serialize.deserialize(e.data, $.proxy(this._finish_iopub_message, this));
1013 1034 };
1014 1035
1015 1036
1016 1037 Kernel.prototype._finish_iopub_message = function (msg) {
1017 1038 var handler = this.get_iopub_handler(msg.header.msg_type);
1018 1039 if (handler !== undefined) {
1019 1040 handler(msg);
1020 1041 }
1021 1042 };
1022 1043
1023 1044 /**
1024 1045 * @function _handle_input_request
1025 1046 */
1026 1047 Kernel.prototype._handle_input_request = function (e) {
1027 1048 serialize.deserialize(e.data, $.proxy(this._finish_input_request, this));
1028 1049 };
1029 1050
1030 1051
1031 1052 Kernel.prototype._finish_input_request = function (request) {
1032 1053 var header = request.header;
1033 1054 var content = request.content;
1034 1055 var metadata = request.metadata;
1035 1056 var msg_type = header.msg_type;
1036 1057 if (msg_type !== 'input_request') {
1037 1058 console.log("Invalid input request!", request);
1038 1059 return;
1039 1060 }
1040 1061 var callbacks = this.get_callbacks_for_msg(request.parent_header.msg_id);
1041 1062 if (callbacks) {
1042 1063 if (callbacks.input) {
1043 1064 callbacks.input(request);
1044 1065 }
1045 1066 }
1046 1067 };
1047 1068
1048 1069 // Backwards compatability.
1049 1070 IPython.Kernel = Kernel;
1050 1071
1051 1072 return {'Kernel': Kernel};
1052 1073 });
@@ -1,328 +1,331 b''
1 1 {% extends "page.html" %}
2 2
3 3 {% block stylesheet %}
4 4
5 5 {% if mathjax_url %}
6 6 <script type="text/javascript" src="{{mathjax_url}}?config=TeX-AMS_HTML-full&delayStartupUntil=configured" charset="utf-8"></script>
7 7 {% endif %}
8 8 <script type="text/javascript">
9 9 // MathJax disabled, set as null to distingish from *missing* MathJax,
10 10 // where it will be undefined, and should prompt a dialog later.
11 11 window.mathjax_url = "{{mathjax_url}}";
12 12 </script>
13 13
14 14 <link rel="stylesheet" href="{{ static_url("components/bootstrap-tour/build/css/bootstrap-tour.min.css") }}" type="text/css" />
15 15 <link rel="stylesheet" href="{{ static_url("components/codemirror/lib/codemirror.css") }}">
16 16
17 17 {{super()}}
18 18
19 19 <link rel="stylesheet" href="{{ static_url("notebook/css/override.css") }}" type="text/css" />
20 20
21 21 {% endblock %}
22 22
23 23 {% block params %}
24 24
25 25 data-project="{{project}}"
26 26 data-base-url="{{base_url}}"
27 27 data-ws-url="{{ws_url}}"
28 28 data-notebook-name="{{notebook_name}}"
29 29 data-notebook-path="{{notebook_path}}"
30 30 class="notebook_app"
31 31
32 32 {% endblock %}
33 33
34 34
35 35 {% block header %}
36 36
37 37
38 38 <span id="save_widget" class="nav pull-left">
39 39 <span id="notebook_name"></span>
40 40 <span id="checkpoint_status"></span>
41 41 <span id="autosave_status"></span>
42 42 </span>
43 43
44 44 <span id="kernel_selector_widget" class="pull-right dropdown">
45 45 <button class="dropdown-toggle" data-toggle="dropdown" type='button' id="current_kernel_spec">
46 46 <span class='kernel_name'>Python</span>
47 47 <span class="caret"></span>
48 48 </button>
49 49 <ul id="kernel_selector" class="dropdown-menu">
50 50 </ul>
51 51 </span>
52 52
53 53 {% endblock %}
54 54
55 55
56 56 {% block site %}
57 57
58 58 <div id="menubar-container" class="container">
59 59 <div id="menubar">
60 60 <div id="menus" class="navbar navbar-default" role="navigation">
61 61 <div class="container-fluid">
62 62 <button type="button" class="btn btn-default navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
63 63 <i class="fa fa-bars"></i>
64 64 <span class="navbar-text">Menu</span>
65 65 </button>
66 66 <ul class="nav navbar-nav navbar-right">
67 67 <li id="kernel_indicator">
68 68 <i id="kernel_indicator_icon"></i>
69 69 </li>
70 70 <li id="modal_indicator">
71 71 <i id="modal_indicator_icon"></i>
72 72 </li>
73 73 <li id="notification_area"></li>
74 74 </ul>
75 75 <div class="navbar-collapse collapse">
76 76 <ul class="nav navbar-nav">
77 77 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">File</a>
78 78 <ul id="file_menu" class="dropdown-menu">
79 79 <li id="new_notebook"
80 80 title="Make a new notebook (Opens a new window)">
81 81 <a href="#">New</a></li>
82 82 <li id="open_notebook"
83 83 title="Opens a new window with the Dashboard view">
84 84 <a href="#">Open...</a></li>
85 85 <!-- <hr/> -->
86 86 <li class="divider"></li>
87 87 <li id="copy_notebook"
88 88 title="Open a copy of this notebook's contents and start a new kernel">
89 89 <a href="#">Make a Copy...</a></li>
90 90 <li id="rename_notebook"><a href="#">Rename...</a></li>
91 91 <li id="save_checkpoint"><a href="#">Save and Checkpoint</a></li>
92 92 <!-- <hr/> -->
93 93 <li class="divider"></li>
94 94 <li id="restore_checkpoint" class="dropdown-submenu"><a href="#">Revert to Checkpoint</a>
95 95 <ul class="dropdown-menu">
96 96 <li><a href="#"></a></li>
97 97 <li><a href="#"></a></li>
98 98 <li><a href="#"></a></li>
99 99 <li><a href="#"></a></li>
100 100 <li><a href="#"></a></li>
101 101 </ul>
102 102 </li>
103 103 <li class="divider"></li>
104 104 <li id="print_preview"><a href="#">Print Preview</a></li>
105 105 <li class="dropdown-submenu"><a href="#">Download as</a>
106 106 <ul class="dropdown-menu">
107 107 <li id="download_ipynb"><a href="#">IPython Notebook (.ipynb)</a></li>
108 108 <li id="download_script"><a href="#">Script</a></li>
109 109 <li id="download_html"><a href="#">HTML (.html)</a></li>
110 110 <li id="download_rst"><a href="#">reST (.rst)</a></li>
111 111 <li id="download_pdf"><a href="#">PDF (.pdf)</a></li>
112 112 </ul>
113 113 </li>
114 114 <li class="divider"></li>
115 115 <li id="trust_notebook"
116 116 title="Trust the output of this notebook">
117 117 <a href="#" >Trust Notebook</a></li>
118 118 <li class="divider"></li>
119 119 <li id="kill_and_exit"
120 120 title="Shutdown this notebook's kernel, and close this window">
121 121 <a href="#" >Close and halt</a></li>
122 122 </ul>
123 123 </li>
124 124 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Edit</a>
125 125 <ul id="edit_menu" class="dropdown-menu">
126 126 <li id="cut_cell"><a href="#">Cut Cell</a></li>
127 127 <li id="copy_cell"><a href="#">Copy Cell</a></li>
128 128 <li id="paste_cell_above" class="disabled"><a href="#">Paste Cell Above</a></li>
129 129 <li id="paste_cell_below" class="disabled"><a href="#">Paste Cell Below</a></li>
130 130 <li id="paste_cell_replace" class="disabled"><a href="#">Paste Cell &amp; Replace</a></li>
131 131 <li id="delete_cell"><a href="#">Delete Cell</a></li>
132 132 <li id="undelete_cell" class="disabled"><a href="#">Undo Delete Cell</a></li>
133 133 <li class="divider"></li>
134 134 <li id="split_cell"><a href="#">Split Cell</a></li>
135 135 <li id="merge_cell_above"><a href="#">Merge Cell Above</a></li>
136 136 <li id="merge_cell_below"><a href="#">Merge Cell Below</a></li>
137 137 <li class="divider"></li>
138 138 <li id="move_cell_up"><a href="#">Move Cell Up</a></li>
139 139 <li id="move_cell_down"><a href="#">Move Cell Down</a></li>
140 140 <li class="divider"></li>
141 141 <li id="edit_nb_metadata"><a href="#">Edit Notebook Metadata</a></li>
142 142 </ul>
143 143 </li>
144 144 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">View</a>
145 145 <ul id="view_menu" class="dropdown-menu">
146 146 <li id="toggle_header"
147 147 title="Show/Hide the IPython Notebook logo and notebook title (above menu bar)">
148 148 <a href="#">Toggle Header</a></li>
149 149 <li id="toggle_toolbar"
150 150 title="Show/Hide the action icons (below menu bar)">
151 151 <a href="#">Toggle Toolbar</a></li>
152 152 </ul>
153 153 </li>
154 154 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Insert</a>
155 155 <ul id="insert_menu" class="dropdown-menu">
156 156 <li id="insert_cell_above"
157 157 title="Insert an empty Code cell above the currently active cell">
158 158 <a href="#">Insert Cell Above</a></li>
159 159 <li id="insert_cell_below"
160 160 title="Insert an empty Code cell below the currently active cell">
161 161 <a href="#">Insert Cell Below</a></li>
162 162 </ul>
163 163 </li>
164 164 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Cell</a>
165 165 <ul id="cell_menu" class="dropdown-menu">
166 166 <li id="run_cell" title="Run this cell, and move cursor to the next one">
167 167 <a href="#">Run</a></li>
168 168 <li id="run_cell_select_below" title="Run this cell, select below">
169 169 <a href="#">Run and Select Below</a></li>
170 170 <li id="run_cell_insert_below" title="Run this cell, insert below">
171 171 <a href="#">Run and Insert Below</a></li>
172 172 <li id="run_all_cells" title="Run all cells in the notebook">
173 173 <a href="#">Run All</a></li>
174 174 <li id="run_all_cells_above" title="Run all cells above (but not including) this cell">
175 175 <a href="#">Run All Above</a></li>
176 176 <li id="run_all_cells_below" title="Run this cell and all cells below it">
177 177 <a href="#">Run All Below</a></li>
178 178 <li class="divider"></li>
179 179 <li id="change_cell_type" class="dropdown-submenu"
180 180 title="All cells in the notebook have a cell type. By default, new cells are created as 'Code' cells">
181 181 <a href="#">Cell Type</a>
182 182 <ul class="dropdown-menu">
183 183 <li id="to_code"
184 184 title="Contents will be sent to the kernel for execution, and output will display in the footer of cell">
185 185 <a href="#">Code</a></li>
186 186 <li id="to_markdown"
187 187 title="Contents will be rendered as HTML and serve as explanatory text">
188 188 <a href="#">Markdown</a></li>
189 189 <li id="to_raw"
190 190 title="Contents will pass through nbconvert unmodified">
191 191 <a href="#">Raw NBConvert</a></li>
192 192 </ul>
193 193 </li>
194 194 <li class="divider"></li>
195 195 <li id="current_outputs" class="dropdown-submenu"><a href="#">Current Output</a>
196 196 <ul class="dropdown-menu">
197 197 <li id="toggle_current_output"
198 198 title="Hide/Show the output of the current cell">
199 199 <a href="#">Toggle</a>
200 200 </li>
201 201 <li id="toggle_current_output_scroll"
202 202 title="Scroll the output of the current cell">
203 203 <a href="#">Toggle Scrolling</a>
204 204 </li>
205 205 <li id="clear_current_output"
206 206 title="Clear the output of the current cell">
207 207 <a href="#">Clear</a>
208 208 </li>
209 209 </ul>
210 210 </li>
211 211 <li id="all_outputs" class="dropdown-submenu"><a href="#">All Output</a>
212 212 <ul class="dropdown-menu">
213 213 <li id="toggle_all_output"
214 214 title="Hide/Show the output of all cells">
215 215 <a href="#">Toggle</a>
216 216 </li>
217 217 <li id="toggle_all_output_scroll"
218 218 title="Scroll the output of all cells">
219 219 <a href="#">Toggle Scrolling</a>
220 220 </li>
221 221 <li id="clear_all_output"
222 222 title="Clear the output of all cells">
223 223 <a href="#">Clear</a>
224 224 </li>
225 225 </ul>
226 226 </li>
227 227 </ul>
228 228 </li>
229 229 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Kernel</a>
230 230 <ul id="kernel_menu" class="dropdown-menu">
231 <li id="toggle_unsolicited"
232 title="Toggle display of unsolicited messages.">
233 <a href="#">Show/ignore unsolicited messages.</a></li>
231 234 <li id="int_kernel"
232 235 title="Send KeyboardInterrupt (CTRL-C) to the Kernel">
233 236 <a href="#">Interrupt</a>
234 237 </li>
235 238 <li id="restart_kernel"
236 239 title="Restart the Kernel">
237 240 <a href="#">Restart</a>
238 241 </li>
239 242 <li id="reconnect_kernel"
240 243 title="Reconnect to the Kernel">
241 244 <a href="#">Reconnect</a>
242 245 </li>
243 246 <li class="divider"></li>
244 247 <li id="menu-change-kernel" class="dropdown-submenu">
245 248 <a href="#">Change kernel</a>
246 249 <ul class="dropdown-menu" id="menu-change-kernel-submenu"></ul>
247 250 </li>
248 251 </ul>
249 252 </li>
250 253 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Help</a>
251 254 <ul id="help_menu" class="dropdown-menu">
252 255 <li id="notebook_tour" title="A quick tour of the notebook user interface"><a href="#">User Interface Tour</a></li>
253 256 <li id="keyboard_shortcuts" title="Opens a tooltip with all keyboard shortcuts"><a href="#">Keyboard Shortcuts</a></li>
254 257 <li class="divider"></li>
255 258 {% set
256 259 sections = (
257 260 (
258 261 ("http://ipython.org/documentation.html","IPython Help",True),
259 262 ("http://nbviewer.ipython.org/github/ipython/ipython/tree/2.x/examples/Index.ipynb", "Notebook Help", True),
260 263 ),(
261 264 ("http://docs.python.org","Python",True),
262 265 ("http://help.github.com/articles/github-flavored-markdown","Markdown",True),
263 266 ("http://docs.scipy.org/doc/numpy/reference/","NumPy",True),
264 267 ("http://docs.scipy.org/doc/scipy/reference/","SciPy",True),
265 268 ("http://matplotlib.org/contents.html","Matplotlib",True),
266 269 ("http://docs.sympy.org/latest/index.html","SymPy",True),
267 270 ("http://pandas.pydata.org/pandas-docs/stable/","pandas", True)
268 271 )
269 272 )
270 273 %}
271 274
272 275 {% for helplinks in sections %}
273 276 {% for link in helplinks %}
274 277 <li><a href="{{link[0]}}" {{'target="_blank" title="Opens in a new window"' if link[2]}}>
275 278 {{'<i class="fa fa-external-link menu-icon pull-right"></i>' if link[2]}}
276 279 {{link[1]}}
277 280 </a></li>
278 281 {% endfor %}
279 282 {% if not loop.last %}
280 283 <li class="divider"></li>
281 284 {% endif %}
282 285 {% endfor %}
283 286 <li class="divider"></li>
284 287 <li title="About IPython Notebook"><a id="notebook_about" href="#">About</a></li>
285 288 </ul>
286 289 </li>
287 290 </ul>
288 291 </div>
289 292 </div>
290 293 </div>
291 294 </div>
292 295 <div id="maintoolbar" class="navbar">
293 296 <div class="toolbar-inner navbar-inner navbar-nobg">
294 297 <div id="maintoolbar-container" class="container"></div>
295 298 </div>
296 299 </div>
297 300 </div>
298 301
299 302 <div id="ipython-main-app">
300 303
301 304 <div id="notebook_panel">
302 305 <div id="notebook"></div>
303 306 <div id="pager_splitter"></div>
304 307 <div id="pager">
305 308 <div id='pager_button_area'>
306 309 </div>
307 310 <div id="pager-container" class="container"></div>
308 311 </div>
309 312 </div>
310 313
311 314 </div>
312 315 <div id='tooltip' class='ipython_tooltip' style='display:none'></div>
313 316
314 317
315 318 {% endblock %}
316 319
317 320
318 321 {% block script %}
319 322 {{super()}}
320 323 <script type="text/javascript">
321 324 sys_info = {{sys_info}};
322 325 </script>
323 326
324 327 <script src="{{ static_url("components/text-encoding/lib/encoding.js") }}" charset="utf-8"></script>
325 328
326 329 <script src="{{ static_url("notebook/js/main.js") }}" charset="utf-8"></script>
327 330
328 331 {% endblock %}
General Comments 0
You need to be logged in to leave comments. Login now