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