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