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