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