##// END OF EJS Templates
Merge pull request #6801 from minrk/runMode...
Thomas Kluyver -
r18874:835269e6 merge
parent child Browse files
Show More
@@ -1,1 +1,1
1 Subproject commit ba94581b824a62ee630dd0b92a5aea8678248a24
1 Subproject commit 563c9e74b153e9509d94fd448353eeda13f0819c
@@ -1,2479 +1,2499
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/textcell',
9 'notebook/js/textcell',
10 'notebook/js/codecell',
10 'notebook/js/codecell',
11 'services/sessions/session',
11 'services/sessions/session',
12 'notebook/js/celltoolbar',
12 'notebook/js/celltoolbar',
13 'components/marked/lib/marked',
13 'components/marked/lib/marked',
14 'highlight',
14 'codemirror/lib/codemirror',
15 'codemirror/addon/runmode/runmode',
15 'notebook/js/mathjaxutils',
16 'notebook/js/mathjaxutils',
16 'base/js/keyboard',
17 'base/js/keyboard',
17 'notebook/js/tooltip',
18 'notebook/js/tooltip',
18 'notebook/js/celltoolbarpresets/default',
19 'notebook/js/celltoolbarpresets/default',
19 'notebook/js/celltoolbarpresets/rawcell',
20 'notebook/js/celltoolbarpresets/rawcell',
20 'notebook/js/celltoolbarpresets/slideshow',
21 'notebook/js/celltoolbarpresets/slideshow',
21 'notebook/js/scrollmanager'
22 'notebook/js/scrollmanager'
22 ], function (
23 ], function (
23 IPython,
24 IPython,
24 $,
25 $,
25 utils,
26 utils,
26 dialog,
27 dialog,
27 textcell,
28 textcell,
28 codecell,
29 codecell,
29 session,
30 session,
30 celltoolbar,
31 celltoolbar,
31 marked,
32 marked,
32 hljs,
33 CodeMirror,
34 runMode,
33 mathjaxutils,
35 mathjaxutils,
34 keyboard,
36 keyboard,
35 tooltip,
37 tooltip,
36 default_celltoolbar,
38 default_celltoolbar,
37 rawcell_celltoolbar,
39 rawcell_celltoolbar,
38 slideshow_celltoolbar,
40 slideshow_celltoolbar,
39 scrollmanager
41 scrollmanager
40 ) {
42 ) {
41
43
42 var Notebook = function (selector, options) {
44 var Notebook = function (selector, options) {
43 // Constructor
45 // Constructor
44 //
46 //
45 // A notebook contains and manages cells.
47 // A notebook contains and manages cells.
46 //
48 //
47 // Parameters:
49 // Parameters:
48 // selector: string
50 // selector: string
49 // options: dictionary
51 // options: dictionary
50 // Dictionary of keyword arguments.
52 // Dictionary of keyword arguments.
51 // events: $(Events) instance
53 // events: $(Events) instance
52 // keyboard_manager: KeyboardManager instance
54 // keyboard_manager: KeyboardManager instance
53 // contents: Contents instance
55 // contents: Contents instance
54 // save_widget: SaveWidget instance
56 // save_widget: SaveWidget instance
55 // config: dictionary
57 // config: dictionary
56 // base_url : string
58 // base_url : string
57 // notebook_path : string
59 // notebook_path : string
58 // notebook_name : string
60 // notebook_name : string
59 this.config = utils.mergeopt(Notebook, options.config);
61 this.config = utils.mergeopt(Notebook, options.config);
60 this.base_url = options.base_url;
62 this.base_url = options.base_url;
61 this.notebook_path = options.notebook_path;
63 this.notebook_path = options.notebook_path;
62 this.notebook_name = options.notebook_name;
64 this.notebook_name = options.notebook_name;
63 this.events = options.events;
65 this.events = options.events;
64 this.keyboard_manager = options.keyboard_manager;
66 this.keyboard_manager = options.keyboard_manager;
65 this.contents = options.contents;
67 this.contents = options.contents;
66 this.save_widget = options.save_widget;
68 this.save_widget = options.save_widget;
67 this.tooltip = new tooltip.Tooltip(this.events);
69 this.tooltip = new tooltip.Tooltip(this.events);
68 this.ws_url = options.ws_url;
70 this.ws_url = options.ws_url;
69 this._session_starting = false;
71 this._session_starting = false;
70 this.default_cell_type = this.config.default_cell_type || 'code';
72 this.default_cell_type = this.config.default_cell_type || 'code';
71
73
72 // Create default scroll manager.
74 // Create default scroll manager.
73 this.scroll_manager = new scrollmanager.ScrollManager(this);
75 this.scroll_manager = new scrollmanager.ScrollManager(this);
74
76
75 // TODO: This code smells (and the other `= this` line a couple lines down)
77 // TODO: This code smells (and the other `= this` line a couple lines down)
76 // We need a better way to deal with circular instance references.
78 // We need a better way to deal with circular instance references.
77 this.keyboard_manager.notebook = this;
79 this.keyboard_manager.notebook = this;
78 this.save_widget.notebook = this;
80 this.save_widget.notebook = this;
79
81
80 mathjaxutils.init();
82 mathjaxutils.init();
81
83
82 if (marked) {
84 if (marked) {
83 marked.setOptions({
85 marked.setOptions({
84 gfm : true,
86 gfm : true,
85 tables: true,
87 tables: true,
86 langPrefix: "language-",
88 // FIXME: probably want central config for CodeMirror theme when we have js config
87 highlight: function(code, lang) {
89 langPrefix: "cm-s-ipython language-",
90 highlight: function(code, lang, callback) {
88 if (!lang) {
91 if (!lang) {
89 // no language, no highlight
92 // no language, no highlight
93 if (callback) {
94 callback(null, code);
95 return;
96 } else {
90 return code;
97 return code;
91 }
98 }
92 var highlighted;
99 }
100 utils.requireCodeMirrorMode(lang, function () {
101 var el = document.createElement("div");
102 mode = CodeMirror.getMode({}, lang);
103 if (!mode) {
104 console.log("No CodeMirror mode: " + lang);
105 callback(null, code);
106 return;
107 }
93 try {
108 try {
94 highlighted = hljs.highlight(lang, code, false);
109 CodeMirror.runMode(code, mode, el);
110 callback(null, el.innerHTML);
95 } catch(err) {
111 } catch (err) {
96 highlighted = hljs.highlightAuto(code);
112 console.log("Failed to highlight " + lang + " code", error);
113 callback(err, code);
97 }
114 }
98 return highlighted.value;
115 }, function (err) {
116 console.log("No CodeMirror mode: " + lang);
117 callback(err, code);
118 });
99 }
119 }
100 });
120 });
101 }
121 }
102
122
103 this.element = $(selector);
123 this.element = $(selector);
104 this.element.scroll();
124 this.element.scroll();
105 this.element.data("notebook", this);
125 this.element.data("notebook", this);
106 this.next_prompt_number = 1;
126 this.next_prompt_number = 1;
107 this.session = null;
127 this.session = null;
108 this.kernel = null;
128 this.kernel = null;
109 this.clipboard = null;
129 this.clipboard = null;
110 this.undelete_backup = null;
130 this.undelete_backup = null;
111 this.undelete_index = null;
131 this.undelete_index = null;
112 this.undelete_below = false;
132 this.undelete_below = false;
113 this.paste_enabled = false;
133 this.paste_enabled = false;
114 // It is important to start out in command mode to match the intial mode
134 // It is important to start out in command mode to match the intial mode
115 // of the KeyboardManager.
135 // of the KeyboardManager.
116 this.mode = 'command';
136 this.mode = 'command';
117 this.set_dirty(false);
137 this.set_dirty(false);
118 this.metadata = {};
138 this.metadata = {};
119 this._checkpoint_after_save = false;
139 this._checkpoint_after_save = false;
120 this.last_checkpoint = null;
140 this.last_checkpoint = null;
121 this.checkpoints = [];
141 this.checkpoints = [];
122 this.autosave_interval = 0;
142 this.autosave_interval = 0;
123 this.autosave_timer = null;
143 this.autosave_timer = null;
124 // autosave *at most* every two minutes
144 // autosave *at most* every two minutes
125 this.minimum_autosave_interval = 120000;
145 this.minimum_autosave_interval = 120000;
126 this.notebook_name_blacklist_re = /[\/\\:]/;
146 this.notebook_name_blacklist_re = /[\/\\:]/;
127 this.nbformat = 4; // Increment this when changing the nbformat
147 this.nbformat = 4; // Increment this when changing the nbformat
128 this.nbformat_minor = 0; // Increment this when changing the nbformat
148 this.nbformat_minor = 0; // Increment this when changing the nbformat
129 this.codemirror_mode = 'ipython';
149 this.codemirror_mode = 'ipython';
130 this.create_elements();
150 this.create_elements();
131 this.bind_events();
151 this.bind_events();
132 this.save_notebook = function() { // don't allow save until notebook_loaded
152 this.save_notebook = function() { // don't allow save until notebook_loaded
133 this.save_notebook_error(null, null, "Load failed, save is disabled");
153 this.save_notebook_error(null, null, "Load failed, save is disabled");
134 };
154 };
135
155
136 // Trigger cell toolbar registration.
156 // Trigger cell toolbar registration.
137 default_celltoolbar.register(this);
157 default_celltoolbar.register(this);
138 rawcell_celltoolbar.register(this);
158 rawcell_celltoolbar.register(this);
139 slideshow_celltoolbar.register(this);
159 slideshow_celltoolbar.register(this);
140 };
160 };
141
161
142 Notebook.options_default = {
162 Notebook.options_default = {
143 // can be any cell type, or the special values of
163 // can be any cell type, or the special values of
144 // 'above', 'below', or 'selected' to get the value from another cell.
164 // 'above', 'below', or 'selected' to get the value from another cell.
145 Notebook: {
165 Notebook: {
146 default_cell_type: 'code',
166 default_cell_type: 'code',
147 }
167 }
148 };
168 };
149
169
150
170
151 /**
171 /**
152 * Create an HTML and CSS representation of the notebook.
172 * Create an HTML and CSS representation of the notebook.
153 *
173 *
154 * @method create_elements
174 * @method create_elements
155 */
175 */
156 Notebook.prototype.create_elements = function () {
176 Notebook.prototype.create_elements = function () {
157 var that = this;
177 var that = this;
158 this.element.attr('tabindex','-1');
178 this.element.attr('tabindex','-1');
159 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
179 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
160 // We add this end_space div to the end of the notebook div to:
180 // We add this end_space div to the end of the notebook div to:
161 // i) provide a margin between the last cell and the end of the notebook
181 // i) provide a margin between the last cell and the end of the notebook
162 // ii) to prevent the div from scrolling up when the last cell is being
182 // ii) to prevent the div from scrolling up when the last cell is being
163 // edited, but is too low on the page, which browsers will do automatically.
183 // edited, but is too low on the page, which browsers will do automatically.
164 var end_space = $('<div/>').addClass('end_space');
184 var end_space = $('<div/>').addClass('end_space');
165 end_space.dblclick(function (e) {
185 end_space.dblclick(function (e) {
166 var ncells = that.ncells();
186 var ncells = that.ncells();
167 that.insert_cell_below('code',ncells-1);
187 that.insert_cell_below('code',ncells-1);
168 });
188 });
169 this.element.append(this.container);
189 this.element.append(this.container);
170 this.container.append(end_space);
190 this.container.append(end_space);
171 };
191 };
172
192
173 /**
193 /**
174 * Bind JavaScript events: key presses and custom IPython events.
194 * Bind JavaScript events: key presses and custom IPython events.
175 *
195 *
176 * @method bind_events
196 * @method bind_events
177 */
197 */
178 Notebook.prototype.bind_events = function () {
198 Notebook.prototype.bind_events = function () {
179 var that = this;
199 var that = this;
180
200
181 this.events.on('set_next_input.Notebook', function (event, data) {
201 this.events.on('set_next_input.Notebook', function (event, data) {
182 var index = that.find_cell_index(data.cell);
202 var index = that.find_cell_index(data.cell);
183 var new_cell = that.insert_cell_below('code',index);
203 var new_cell = that.insert_cell_below('code',index);
184 new_cell.set_text(data.text);
204 new_cell.set_text(data.text);
185 that.dirty = true;
205 that.dirty = true;
186 });
206 });
187
207
188 this.events.on('set_dirty.Notebook', function (event, data) {
208 this.events.on('set_dirty.Notebook', function (event, data) {
189 that.dirty = data.value;
209 that.dirty = data.value;
190 });
210 });
191
211
192 this.events.on('trust_changed.Notebook', function (event, trusted) {
212 this.events.on('trust_changed.Notebook', function (event, trusted) {
193 that.trusted = trusted;
213 that.trusted = trusted;
194 });
214 });
195
215
196 this.events.on('select.Cell', function (event, data) {
216 this.events.on('select.Cell', function (event, data) {
197 var index = that.find_cell_index(data.cell);
217 var index = that.find_cell_index(data.cell);
198 that.select(index);
218 that.select(index);
199 });
219 });
200
220
201 this.events.on('edit_mode.Cell', function (event, data) {
221 this.events.on('edit_mode.Cell', function (event, data) {
202 that.handle_edit_mode(data.cell);
222 that.handle_edit_mode(data.cell);
203 });
223 });
204
224
205 this.events.on('command_mode.Cell', function (event, data) {
225 this.events.on('command_mode.Cell', function (event, data) {
206 that.handle_command_mode(data.cell);
226 that.handle_command_mode(data.cell);
207 });
227 });
208
228
209 this.events.on('spec_changed.Kernel', function(event, data) {
229 this.events.on('spec_changed.Kernel', function(event, data) {
210 that.metadata.kernelspec =
230 that.metadata.kernelspec =
211 {name: data.name, display_name: data.display_name};
231 {name: data.name, display_name: data.display_name};
212 });
232 });
213
233
214 this.events.on('kernel_ready.Kernel', function(event, data) {
234 this.events.on('kernel_ready.Kernel', function(event, data) {
215 var kinfo = data.kernel.info_reply;
235 var kinfo = data.kernel.info_reply;
216 var langinfo = kinfo.language_info || {};
236 var langinfo = kinfo.language_info || {};
217 if (!langinfo.name) langinfo.name = kinfo.language;
237 if (!langinfo.name) langinfo.name = kinfo.language;
218
238
219 that.metadata.language_info = langinfo;
239 that.metadata.language_info = langinfo;
220 // Mode 'null' should be plain, unhighlighted text.
240 // Mode 'null' should be plain, unhighlighted text.
221 var cm_mode = langinfo.codemirror_mode || langinfo.language || 'null';
241 var cm_mode = langinfo.codemirror_mode || langinfo.language || 'null';
222 that.set_codemirror_mode(cm_mode);
242 that.set_codemirror_mode(cm_mode);
223 });
243 });
224
244
225 var collapse_time = function (time) {
245 var collapse_time = function (time) {
226 var app_height = $('#ipython-main-app').height(); // content height
246 var app_height = $('#ipython-main-app').height(); // content height
227 var splitter_height = $('div#pager_splitter').outerHeight(true);
247 var splitter_height = $('div#pager_splitter').outerHeight(true);
228 var new_height = app_height - splitter_height;
248 var new_height = app_height - splitter_height;
229 that.element.animate({height : new_height + 'px'}, time);
249 that.element.animate({height : new_height + 'px'}, time);
230 };
250 };
231
251
232 this.element.bind('collapse_pager', function (event, extrap) {
252 this.element.bind('collapse_pager', function (event, extrap) {
233 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
253 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
234 collapse_time(time);
254 collapse_time(time);
235 });
255 });
236
256
237 var expand_time = function (time) {
257 var expand_time = function (time) {
238 var app_height = $('#ipython-main-app').height(); // content height
258 var app_height = $('#ipython-main-app').height(); // content height
239 var splitter_height = $('div#pager_splitter').outerHeight(true);
259 var splitter_height = $('div#pager_splitter').outerHeight(true);
240 var pager_height = $('div#pager').outerHeight(true);
260 var pager_height = $('div#pager').outerHeight(true);
241 var new_height = app_height - pager_height - splitter_height;
261 var new_height = app_height - pager_height - splitter_height;
242 that.element.animate({height : new_height + 'px'}, time);
262 that.element.animate({height : new_height + 'px'}, time);
243 };
263 };
244
264
245 this.element.bind('expand_pager', function (event, extrap) {
265 this.element.bind('expand_pager', function (event, extrap) {
246 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
266 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
247 expand_time(time);
267 expand_time(time);
248 });
268 });
249
269
250 // Firefox 22 broke $(window).on("beforeunload")
270 // Firefox 22 broke $(window).on("beforeunload")
251 // I'm not sure why or how.
271 // I'm not sure why or how.
252 window.onbeforeunload = function (e) {
272 window.onbeforeunload = function (e) {
253 // TODO: Make killing the kernel configurable.
273 // TODO: Make killing the kernel configurable.
254 var kill_kernel = false;
274 var kill_kernel = false;
255 if (kill_kernel) {
275 if (kill_kernel) {
256 that.session.delete();
276 that.session.delete();
257 }
277 }
258 // if we are autosaving, trigger an autosave on nav-away.
278 // if we are autosaving, trigger an autosave on nav-away.
259 // still warn, because if we don't the autosave may fail.
279 // still warn, because if we don't the autosave may fail.
260 if (that.dirty) {
280 if (that.dirty) {
261 if ( that.autosave_interval ) {
281 if ( that.autosave_interval ) {
262 // schedule autosave in a timeout
282 // schedule autosave in a timeout
263 // this gives you a chance to forcefully discard changes
283 // this gives you a chance to forcefully discard changes
264 // by reloading the page if you *really* want to.
284 // by reloading the page if you *really* want to.
265 // the timer doesn't start until you *dismiss* the dialog.
285 // the timer doesn't start until you *dismiss* the dialog.
266 setTimeout(function () {
286 setTimeout(function () {
267 if (that.dirty) {
287 if (that.dirty) {
268 that.save_notebook();
288 that.save_notebook();
269 }
289 }
270 }, 1000);
290 }, 1000);
271 return "Autosave in progress, latest changes may be lost.";
291 return "Autosave in progress, latest changes may be lost.";
272 } else {
292 } else {
273 return "Unsaved changes will be lost.";
293 return "Unsaved changes will be lost.";
274 }
294 }
275 }
295 }
276 // Null is the *only* return value that will make the browser not
296 // Null is the *only* return value that will make the browser not
277 // pop up the "don't leave" dialog.
297 // pop up the "don't leave" dialog.
278 return null;
298 return null;
279 };
299 };
280 };
300 };
281
301
282 /**
302 /**
283 * Set the dirty flag, and trigger the set_dirty.Notebook event
303 * Set the dirty flag, and trigger the set_dirty.Notebook event
284 *
304 *
285 * @method set_dirty
305 * @method set_dirty
286 */
306 */
287 Notebook.prototype.set_dirty = function (value) {
307 Notebook.prototype.set_dirty = function (value) {
288 if (value === undefined) {
308 if (value === undefined) {
289 value = true;
309 value = true;
290 }
310 }
291 if (this.dirty == value) {
311 if (this.dirty == value) {
292 return;
312 return;
293 }
313 }
294 this.events.trigger('set_dirty.Notebook', {value: value});
314 this.events.trigger('set_dirty.Notebook', {value: value});
295 };
315 };
296
316
297 /**
317 /**
298 * Scroll the top of the page to a given cell.
318 * Scroll the top of the page to a given cell.
299 *
319 *
300 * @method scroll_to_cell
320 * @method scroll_to_cell
301 * @param {Number} cell_number An index of the cell to view
321 * @param {Number} cell_number An index of the cell to view
302 * @param {Number} time Animation time in milliseconds
322 * @param {Number} time Animation time in milliseconds
303 * @return {Number} Pixel offset from the top of the container
323 * @return {Number} Pixel offset from the top of the container
304 */
324 */
305 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
325 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
306 var cells = this.get_cells();
326 var cells = this.get_cells();
307 time = time || 0;
327 time = time || 0;
308 cell_number = Math.min(cells.length-1,cell_number);
328 cell_number = Math.min(cells.length-1,cell_number);
309 cell_number = Math.max(0 ,cell_number);
329 cell_number = Math.max(0 ,cell_number);
310 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
330 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
311 this.element.animate({scrollTop:scroll_value}, time);
331 this.element.animate({scrollTop:scroll_value}, time);
312 return scroll_value;
332 return scroll_value;
313 };
333 };
314
334
315 /**
335 /**
316 * Scroll to the bottom of the page.
336 * Scroll to the bottom of the page.
317 *
337 *
318 * @method scroll_to_bottom
338 * @method scroll_to_bottom
319 */
339 */
320 Notebook.prototype.scroll_to_bottom = function () {
340 Notebook.prototype.scroll_to_bottom = function () {
321 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
341 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
322 };
342 };
323
343
324 /**
344 /**
325 * Scroll to the top of the page.
345 * Scroll to the top of the page.
326 *
346 *
327 * @method scroll_to_top
347 * @method scroll_to_top
328 */
348 */
329 Notebook.prototype.scroll_to_top = function () {
349 Notebook.prototype.scroll_to_top = function () {
330 this.element.animate({scrollTop:0}, 0);
350 this.element.animate({scrollTop:0}, 0);
331 };
351 };
332
352
333 // Edit Notebook metadata
353 // Edit Notebook metadata
334
354
335 Notebook.prototype.edit_metadata = function () {
355 Notebook.prototype.edit_metadata = function () {
336 var that = this;
356 var that = this;
337 dialog.edit_metadata({
357 dialog.edit_metadata({
338 md: this.metadata,
358 md: this.metadata,
339 callback: function (md) {
359 callback: function (md) {
340 that.metadata = md;
360 that.metadata = md;
341 },
361 },
342 name: 'Notebook',
362 name: 'Notebook',
343 notebook: this,
363 notebook: this,
344 keyboard_manager: this.keyboard_manager});
364 keyboard_manager: this.keyboard_manager});
345 };
365 };
346
366
347 // Cell indexing, retrieval, etc.
367 // Cell indexing, retrieval, etc.
348
368
349 /**
369 /**
350 * Get all cell elements in the notebook.
370 * Get all cell elements in the notebook.
351 *
371 *
352 * @method get_cell_elements
372 * @method get_cell_elements
353 * @return {jQuery} A selector of all cell elements
373 * @return {jQuery} A selector of all cell elements
354 */
374 */
355 Notebook.prototype.get_cell_elements = function () {
375 Notebook.prototype.get_cell_elements = function () {
356 return this.container.children("div.cell");
376 return this.container.children("div.cell");
357 };
377 };
358
378
359 /**
379 /**
360 * Get a particular cell element.
380 * Get a particular cell element.
361 *
381 *
362 * @method get_cell_element
382 * @method get_cell_element
363 * @param {Number} index An index of a cell to select
383 * @param {Number} index An index of a cell to select
364 * @return {jQuery} A selector of the given cell.
384 * @return {jQuery} A selector of the given cell.
365 */
385 */
366 Notebook.prototype.get_cell_element = function (index) {
386 Notebook.prototype.get_cell_element = function (index) {
367 var result = null;
387 var result = null;
368 var e = this.get_cell_elements().eq(index);
388 var e = this.get_cell_elements().eq(index);
369 if (e.length !== 0) {
389 if (e.length !== 0) {
370 result = e;
390 result = e;
371 }
391 }
372 return result;
392 return result;
373 };
393 };
374
394
375 /**
395 /**
376 * Try to get a particular cell by msg_id.
396 * Try to get a particular cell by msg_id.
377 *
397 *
378 * @method get_msg_cell
398 * @method get_msg_cell
379 * @param {String} msg_id A message UUID
399 * @param {String} msg_id A message UUID
380 * @return {Cell} Cell or null if no cell was found.
400 * @return {Cell} Cell or null if no cell was found.
381 */
401 */
382 Notebook.prototype.get_msg_cell = function (msg_id) {
402 Notebook.prototype.get_msg_cell = function (msg_id) {
383 return codecell.CodeCell.msg_cells[msg_id] || null;
403 return codecell.CodeCell.msg_cells[msg_id] || null;
384 };
404 };
385
405
386 /**
406 /**
387 * Count the cells in this notebook.
407 * Count the cells in this notebook.
388 *
408 *
389 * @method ncells
409 * @method ncells
390 * @return {Number} The number of cells in this notebook
410 * @return {Number} The number of cells in this notebook
391 */
411 */
392 Notebook.prototype.ncells = function () {
412 Notebook.prototype.ncells = function () {
393 return this.get_cell_elements().length;
413 return this.get_cell_elements().length;
394 };
414 };
395
415
396 /**
416 /**
397 * Get all Cell objects in this notebook.
417 * Get all Cell objects in this notebook.
398 *
418 *
399 * @method get_cells
419 * @method get_cells
400 * @return {Array} This notebook's Cell objects
420 * @return {Array} This notebook's Cell objects
401 */
421 */
402 // TODO: we are often calling cells as cells()[i], which we should optimize
422 // TODO: we are often calling cells as cells()[i], which we should optimize
403 // to cells(i) or a new method.
423 // to cells(i) or a new method.
404 Notebook.prototype.get_cells = function () {
424 Notebook.prototype.get_cells = function () {
405 return this.get_cell_elements().toArray().map(function (e) {
425 return this.get_cell_elements().toArray().map(function (e) {
406 return $(e).data("cell");
426 return $(e).data("cell");
407 });
427 });
408 };
428 };
409
429
410 /**
430 /**
411 * Get a Cell object from this notebook.
431 * Get a Cell object from this notebook.
412 *
432 *
413 * @method get_cell
433 * @method get_cell
414 * @param {Number} index An index of a cell to retrieve
434 * @param {Number} index An index of a cell to retrieve
415 * @return {Cell} Cell or null if no cell was found.
435 * @return {Cell} Cell or null if no cell was found.
416 */
436 */
417 Notebook.prototype.get_cell = function (index) {
437 Notebook.prototype.get_cell = function (index) {
418 var result = null;
438 var result = null;
419 var ce = this.get_cell_element(index);
439 var ce = this.get_cell_element(index);
420 if (ce !== null) {
440 if (ce !== null) {
421 result = ce.data('cell');
441 result = ce.data('cell');
422 }
442 }
423 return result;
443 return result;
424 };
444 };
425
445
426 /**
446 /**
427 * Get the cell below a given cell.
447 * Get the cell below a given cell.
428 *
448 *
429 * @method get_next_cell
449 * @method get_next_cell
430 * @param {Cell} cell The provided cell
450 * @param {Cell} cell The provided cell
431 * @return {Cell} the next cell or null if no cell was found.
451 * @return {Cell} the next cell or null if no cell was found.
432 */
452 */
433 Notebook.prototype.get_next_cell = function (cell) {
453 Notebook.prototype.get_next_cell = function (cell) {
434 var result = null;
454 var result = null;
435 var index = this.find_cell_index(cell);
455 var index = this.find_cell_index(cell);
436 if (this.is_valid_cell_index(index+1)) {
456 if (this.is_valid_cell_index(index+1)) {
437 result = this.get_cell(index+1);
457 result = this.get_cell(index+1);
438 }
458 }
439 return result;
459 return result;
440 };
460 };
441
461
442 /**
462 /**
443 * Get the cell above a given cell.
463 * Get the cell above a given cell.
444 *
464 *
445 * @method get_prev_cell
465 * @method get_prev_cell
446 * @param {Cell} cell The provided cell
466 * @param {Cell} cell The provided cell
447 * @return {Cell} The previous cell or null if no cell was found.
467 * @return {Cell} The previous cell or null if no cell was found.
448 */
468 */
449 Notebook.prototype.get_prev_cell = function (cell) {
469 Notebook.prototype.get_prev_cell = function (cell) {
450 var result = null;
470 var result = null;
451 var index = this.find_cell_index(cell);
471 var index = this.find_cell_index(cell);
452 if (index !== null && index > 0) {
472 if (index !== null && index > 0) {
453 result = this.get_cell(index-1);
473 result = this.get_cell(index-1);
454 }
474 }
455 return result;
475 return result;
456 };
476 };
457
477
458 /**
478 /**
459 * Get the numeric index of a given cell.
479 * Get the numeric index of a given cell.
460 *
480 *
461 * @method find_cell_index
481 * @method find_cell_index
462 * @param {Cell} cell The provided cell
482 * @param {Cell} cell The provided cell
463 * @return {Number} The cell's numeric index or null if no cell was found.
483 * @return {Number} The cell's numeric index or null if no cell was found.
464 */
484 */
465 Notebook.prototype.find_cell_index = function (cell) {
485 Notebook.prototype.find_cell_index = function (cell) {
466 var result = null;
486 var result = null;
467 this.get_cell_elements().filter(function (index) {
487 this.get_cell_elements().filter(function (index) {
468 if ($(this).data("cell") === cell) {
488 if ($(this).data("cell") === cell) {
469 result = index;
489 result = index;
470 }
490 }
471 });
491 });
472 return result;
492 return result;
473 };
493 };
474
494
475 /**
495 /**
476 * Get a given index , or the selected index if none is provided.
496 * Get a given index , or the selected index if none is provided.
477 *
497 *
478 * @method index_or_selected
498 * @method index_or_selected
479 * @param {Number} index A cell's index
499 * @param {Number} index A cell's index
480 * @return {Number} The given index, or selected index if none is provided.
500 * @return {Number} The given index, or selected index if none is provided.
481 */
501 */
482 Notebook.prototype.index_or_selected = function (index) {
502 Notebook.prototype.index_or_selected = function (index) {
483 var i;
503 var i;
484 if (index === undefined || index === null) {
504 if (index === undefined || index === null) {
485 i = this.get_selected_index();
505 i = this.get_selected_index();
486 if (i === null) {
506 if (i === null) {
487 i = 0;
507 i = 0;
488 }
508 }
489 } else {
509 } else {
490 i = index;
510 i = index;
491 }
511 }
492 return i;
512 return i;
493 };
513 };
494
514
495 /**
515 /**
496 * Get the currently selected cell.
516 * Get the currently selected cell.
497 * @method get_selected_cell
517 * @method get_selected_cell
498 * @return {Cell} The selected cell
518 * @return {Cell} The selected cell
499 */
519 */
500 Notebook.prototype.get_selected_cell = function () {
520 Notebook.prototype.get_selected_cell = function () {
501 var index = this.get_selected_index();
521 var index = this.get_selected_index();
502 return this.get_cell(index);
522 return this.get_cell(index);
503 };
523 };
504
524
505 /**
525 /**
506 * Check whether a cell index is valid.
526 * Check whether a cell index is valid.
507 *
527 *
508 * @method is_valid_cell_index
528 * @method is_valid_cell_index
509 * @param {Number} index A cell index
529 * @param {Number} index A cell index
510 * @return True if the index is valid, false otherwise
530 * @return True if the index is valid, false otherwise
511 */
531 */
512 Notebook.prototype.is_valid_cell_index = function (index) {
532 Notebook.prototype.is_valid_cell_index = function (index) {
513 if (index !== null && index >= 0 && index < this.ncells()) {
533 if (index !== null && index >= 0 && index < this.ncells()) {
514 return true;
534 return true;
515 } else {
535 } else {
516 return false;
536 return false;
517 }
537 }
518 };
538 };
519
539
520 /**
540 /**
521 * Get the index of the currently selected cell.
541 * Get the index of the currently selected cell.
522
542
523 * @method get_selected_index
543 * @method get_selected_index
524 * @return {Number} The selected cell's numeric index
544 * @return {Number} The selected cell's numeric index
525 */
545 */
526 Notebook.prototype.get_selected_index = function () {
546 Notebook.prototype.get_selected_index = function () {
527 var result = null;
547 var result = null;
528 this.get_cell_elements().filter(function (index) {
548 this.get_cell_elements().filter(function (index) {
529 if ($(this).data("cell").selected === true) {
549 if ($(this).data("cell").selected === true) {
530 result = index;
550 result = index;
531 }
551 }
532 });
552 });
533 return result;
553 return result;
534 };
554 };
535
555
536
556
537 // Cell selection.
557 // Cell selection.
538
558
539 /**
559 /**
540 * Programmatically select a cell.
560 * Programmatically select a cell.
541 *
561 *
542 * @method select
562 * @method select
543 * @param {Number} index A cell's index
563 * @param {Number} index A cell's index
544 * @return {Notebook} This notebook
564 * @return {Notebook} This notebook
545 */
565 */
546 Notebook.prototype.select = function (index) {
566 Notebook.prototype.select = function (index) {
547 if (this.is_valid_cell_index(index)) {
567 if (this.is_valid_cell_index(index)) {
548 var sindex = this.get_selected_index();
568 var sindex = this.get_selected_index();
549 if (sindex !== null && index !== sindex) {
569 if (sindex !== null && index !== sindex) {
550 // If we are about to select a different cell, make sure we are
570 // If we are about to select a different cell, make sure we are
551 // first in command mode.
571 // first in command mode.
552 if (this.mode !== 'command') {
572 if (this.mode !== 'command') {
553 this.command_mode();
573 this.command_mode();
554 }
574 }
555 this.get_cell(sindex).unselect();
575 this.get_cell(sindex).unselect();
556 }
576 }
557 var cell = this.get_cell(index);
577 var cell = this.get_cell(index);
558 cell.select();
578 cell.select();
559 if (cell.cell_type === 'heading') {
579 if (cell.cell_type === 'heading') {
560 this.events.trigger('selected_cell_type_changed.Notebook',
580 this.events.trigger('selected_cell_type_changed.Notebook',
561 {'cell_type':cell.cell_type,level:cell.level}
581 {'cell_type':cell.cell_type,level:cell.level}
562 );
582 );
563 } else {
583 } else {
564 this.events.trigger('selected_cell_type_changed.Notebook',
584 this.events.trigger('selected_cell_type_changed.Notebook',
565 {'cell_type':cell.cell_type}
585 {'cell_type':cell.cell_type}
566 );
586 );
567 }
587 }
568 }
588 }
569 return this;
589 return this;
570 };
590 };
571
591
572 /**
592 /**
573 * Programmatically select the next cell.
593 * Programmatically select the next cell.
574 *
594 *
575 * @method select_next
595 * @method select_next
576 * @return {Notebook} This notebook
596 * @return {Notebook} This notebook
577 */
597 */
578 Notebook.prototype.select_next = function () {
598 Notebook.prototype.select_next = function () {
579 var index = this.get_selected_index();
599 var index = this.get_selected_index();
580 this.select(index+1);
600 this.select(index+1);
581 return this;
601 return this;
582 };
602 };
583
603
584 /**
604 /**
585 * Programmatically select the previous cell.
605 * Programmatically select the previous cell.
586 *
606 *
587 * @method select_prev
607 * @method select_prev
588 * @return {Notebook} This notebook
608 * @return {Notebook} This notebook
589 */
609 */
590 Notebook.prototype.select_prev = function () {
610 Notebook.prototype.select_prev = function () {
591 var index = this.get_selected_index();
611 var index = this.get_selected_index();
592 this.select(index-1);
612 this.select(index-1);
593 return this;
613 return this;
594 };
614 };
595
615
596
616
597 // Edit/Command mode
617 // Edit/Command mode
598
618
599 /**
619 /**
600 * Gets the index of the cell that is in edit mode.
620 * Gets the index of the cell that is in edit mode.
601 *
621 *
602 * @method get_edit_index
622 * @method get_edit_index
603 *
623 *
604 * @return index {int}
624 * @return index {int}
605 **/
625 **/
606 Notebook.prototype.get_edit_index = function () {
626 Notebook.prototype.get_edit_index = function () {
607 var result = null;
627 var result = null;
608 this.get_cell_elements().filter(function (index) {
628 this.get_cell_elements().filter(function (index) {
609 if ($(this).data("cell").mode === 'edit') {
629 if ($(this).data("cell").mode === 'edit') {
610 result = index;
630 result = index;
611 }
631 }
612 });
632 });
613 return result;
633 return result;
614 };
634 };
615
635
616 /**
636 /**
617 * Handle when a a cell blurs and the notebook should enter command mode.
637 * Handle when a a cell blurs and the notebook should enter command mode.
618 *
638 *
619 * @method handle_command_mode
639 * @method handle_command_mode
620 * @param [cell] {Cell} Cell to enter command mode on.
640 * @param [cell] {Cell} Cell to enter command mode on.
621 **/
641 **/
622 Notebook.prototype.handle_command_mode = function (cell) {
642 Notebook.prototype.handle_command_mode = function (cell) {
623 if (this.mode !== 'command') {
643 if (this.mode !== 'command') {
624 cell.command_mode();
644 cell.command_mode();
625 this.mode = 'command';
645 this.mode = 'command';
626 this.events.trigger('command_mode.Notebook');
646 this.events.trigger('command_mode.Notebook');
627 this.keyboard_manager.command_mode();
647 this.keyboard_manager.command_mode();
628 }
648 }
629 };
649 };
630
650
631 /**
651 /**
632 * Make the notebook enter command mode.
652 * Make the notebook enter command mode.
633 *
653 *
634 * @method command_mode
654 * @method command_mode
635 **/
655 **/
636 Notebook.prototype.command_mode = function () {
656 Notebook.prototype.command_mode = function () {
637 var cell = this.get_cell(this.get_edit_index());
657 var cell = this.get_cell(this.get_edit_index());
638 if (cell && this.mode !== 'command') {
658 if (cell && this.mode !== 'command') {
639 // We don't call cell.command_mode, but rather call cell.focus_cell()
659 // We don't call cell.command_mode, but rather call cell.focus_cell()
640 // which will blur and CM editor and trigger the call to
660 // which will blur and CM editor and trigger the call to
641 // handle_command_mode.
661 // handle_command_mode.
642 cell.focus_cell();
662 cell.focus_cell();
643 }
663 }
644 };
664 };
645
665
646 /**
666 /**
647 * Handle when a cell fires it's edit_mode event.
667 * Handle when a cell fires it's edit_mode event.
648 *
668 *
649 * @method handle_edit_mode
669 * @method handle_edit_mode
650 * @param [cell] {Cell} Cell to enter edit mode on.
670 * @param [cell] {Cell} Cell to enter edit mode on.
651 **/
671 **/
652 Notebook.prototype.handle_edit_mode = function (cell) {
672 Notebook.prototype.handle_edit_mode = function (cell) {
653 if (cell && this.mode !== 'edit') {
673 if (cell && this.mode !== 'edit') {
654 cell.edit_mode();
674 cell.edit_mode();
655 this.mode = 'edit';
675 this.mode = 'edit';
656 this.events.trigger('edit_mode.Notebook');
676 this.events.trigger('edit_mode.Notebook');
657 this.keyboard_manager.edit_mode();
677 this.keyboard_manager.edit_mode();
658 }
678 }
659 };
679 };
660
680
661 /**
681 /**
662 * Make a cell enter edit mode.
682 * Make a cell enter edit mode.
663 *
683 *
664 * @method edit_mode
684 * @method edit_mode
665 **/
685 **/
666 Notebook.prototype.edit_mode = function () {
686 Notebook.prototype.edit_mode = function () {
667 var cell = this.get_selected_cell();
687 var cell = this.get_selected_cell();
668 if (cell && this.mode !== 'edit') {
688 if (cell && this.mode !== 'edit') {
669 cell.unrender();
689 cell.unrender();
670 cell.focus_editor();
690 cell.focus_editor();
671 }
691 }
672 };
692 };
673
693
674 /**
694 /**
675 * Focus the currently selected cell.
695 * Focus the currently selected cell.
676 *
696 *
677 * @method focus_cell
697 * @method focus_cell
678 **/
698 **/
679 Notebook.prototype.focus_cell = function () {
699 Notebook.prototype.focus_cell = function () {
680 var cell = this.get_selected_cell();
700 var cell = this.get_selected_cell();
681 if (cell === null) {return;} // No cell is selected
701 if (cell === null) {return;} // No cell is selected
682 cell.focus_cell();
702 cell.focus_cell();
683 };
703 };
684
704
685 // Cell movement
705 // Cell movement
686
706
687 /**
707 /**
688 * Move given (or selected) cell up and select it.
708 * Move given (or selected) cell up and select it.
689 *
709 *
690 * @method move_cell_up
710 * @method move_cell_up
691 * @param [index] {integer} cell index
711 * @param [index] {integer} cell index
692 * @return {Notebook} This notebook
712 * @return {Notebook} This notebook
693 **/
713 **/
694 Notebook.prototype.move_cell_up = function (index) {
714 Notebook.prototype.move_cell_up = function (index) {
695 var i = this.index_or_selected(index);
715 var i = this.index_or_selected(index);
696 if (this.is_valid_cell_index(i) && i > 0) {
716 if (this.is_valid_cell_index(i) && i > 0) {
697 var pivot = this.get_cell_element(i-1);
717 var pivot = this.get_cell_element(i-1);
698 var tomove = this.get_cell_element(i);
718 var tomove = this.get_cell_element(i);
699 if (pivot !== null && tomove !== null) {
719 if (pivot !== null && tomove !== null) {
700 tomove.detach();
720 tomove.detach();
701 pivot.before(tomove);
721 pivot.before(tomove);
702 this.select(i-1);
722 this.select(i-1);
703 var cell = this.get_selected_cell();
723 var cell = this.get_selected_cell();
704 cell.focus_cell();
724 cell.focus_cell();
705 }
725 }
706 this.set_dirty(true);
726 this.set_dirty(true);
707 }
727 }
708 return this;
728 return this;
709 };
729 };
710
730
711
731
712 /**
732 /**
713 * Move given (or selected) cell down and select it
733 * Move given (or selected) cell down and select it
714 *
734 *
715 * @method move_cell_down
735 * @method move_cell_down
716 * @param [index] {integer} cell index
736 * @param [index] {integer} cell index
717 * @return {Notebook} This notebook
737 * @return {Notebook} This notebook
718 **/
738 **/
719 Notebook.prototype.move_cell_down = function (index) {
739 Notebook.prototype.move_cell_down = function (index) {
720 var i = this.index_or_selected(index);
740 var i = this.index_or_selected(index);
721 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
741 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
722 var pivot = this.get_cell_element(i+1);
742 var pivot = this.get_cell_element(i+1);
723 var tomove = this.get_cell_element(i);
743 var tomove = this.get_cell_element(i);
724 if (pivot !== null && tomove !== null) {
744 if (pivot !== null && tomove !== null) {
725 tomove.detach();
745 tomove.detach();
726 pivot.after(tomove);
746 pivot.after(tomove);
727 this.select(i+1);
747 this.select(i+1);
728 var cell = this.get_selected_cell();
748 var cell = this.get_selected_cell();
729 cell.focus_cell();
749 cell.focus_cell();
730 }
750 }
731 }
751 }
732 this.set_dirty();
752 this.set_dirty();
733 return this;
753 return this;
734 };
754 };
735
755
736
756
737 // Insertion, deletion.
757 // Insertion, deletion.
738
758
739 /**
759 /**
740 * Delete a cell from the notebook.
760 * Delete a cell from the notebook.
741 *
761 *
742 * @method delete_cell
762 * @method delete_cell
743 * @param [index] A cell's numeric index
763 * @param [index] A cell's numeric index
744 * @return {Notebook} This notebook
764 * @return {Notebook} This notebook
745 */
765 */
746 Notebook.prototype.delete_cell = function (index) {
766 Notebook.prototype.delete_cell = function (index) {
747 var i = this.index_or_selected(index);
767 var i = this.index_or_selected(index);
748 var cell = this.get_cell(i);
768 var cell = this.get_cell(i);
749 if (!cell.is_deletable()) {
769 if (!cell.is_deletable()) {
750 return this;
770 return this;
751 }
771 }
752
772
753 this.undelete_backup = cell.toJSON();
773 this.undelete_backup = cell.toJSON();
754 $('#undelete_cell').removeClass('disabled');
774 $('#undelete_cell').removeClass('disabled');
755 if (this.is_valid_cell_index(i)) {
775 if (this.is_valid_cell_index(i)) {
756 var old_ncells = this.ncells();
776 var old_ncells = this.ncells();
757 var ce = this.get_cell_element(i);
777 var ce = this.get_cell_element(i);
758 ce.remove();
778 ce.remove();
759 if (i === 0) {
779 if (i === 0) {
760 // Always make sure we have at least one cell.
780 // Always make sure we have at least one cell.
761 if (old_ncells === 1) {
781 if (old_ncells === 1) {
762 this.insert_cell_below('code');
782 this.insert_cell_below('code');
763 }
783 }
764 this.select(0);
784 this.select(0);
765 this.undelete_index = 0;
785 this.undelete_index = 0;
766 this.undelete_below = false;
786 this.undelete_below = false;
767 } else if (i === old_ncells-1 && i !== 0) {
787 } else if (i === old_ncells-1 && i !== 0) {
768 this.select(i-1);
788 this.select(i-1);
769 this.undelete_index = i - 1;
789 this.undelete_index = i - 1;
770 this.undelete_below = true;
790 this.undelete_below = true;
771 } else {
791 } else {
772 this.select(i);
792 this.select(i);
773 this.undelete_index = i;
793 this.undelete_index = i;
774 this.undelete_below = false;
794 this.undelete_below = false;
775 }
795 }
776 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
796 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
777 this.set_dirty(true);
797 this.set_dirty(true);
778 }
798 }
779 return this;
799 return this;
780 };
800 };
781
801
782 /**
802 /**
783 * Restore the most recently deleted cell.
803 * Restore the most recently deleted cell.
784 *
804 *
785 * @method undelete
805 * @method undelete
786 */
806 */
787 Notebook.prototype.undelete_cell = function() {
807 Notebook.prototype.undelete_cell = function() {
788 if (this.undelete_backup !== null && this.undelete_index !== null) {
808 if (this.undelete_backup !== null && this.undelete_index !== null) {
789 var current_index = this.get_selected_index();
809 var current_index = this.get_selected_index();
790 if (this.undelete_index < current_index) {
810 if (this.undelete_index < current_index) {
791 current_index = current_index + 1;
811 current_index = current_index + 1;
792 }
812 }
793 if (this.undelete_index >= this.ncells()) {
813 if (this.undelete_index >= this.ncells()) {
794 this.select(this.ncells() - 1);
814 this.select(this.ncells() - 1);
795 }
815 }
796 else {
816 else {
797 this.select(this.undelete_index);
817 this.select(this.undelete_index);
798 }
818 }
799 var cell_data = this.undelete_backup;
819 var cell_data = this.undelete_backup;
800 var new_cell = null;
820 var new_cell = null;
801 if (this.undelete_below) {
821 if (this.undelete_below) {
802 new_cell = this.insert_cell_below(cell_data.cell_type);
822 new_cell = this.insert_cell_below(cell_data.cell_type);
803 } else {
823 } else {
804 new_cell = this.insert_cell_above(cell_data.cell_type);
824 new_cell = this.insert_cell_above(cell_data.cell_type);
805 }
825 }
806 new_cell.fromJSON(cell_data);
826 new_cell.fromJSON(cell_data);
807 if (this.undelete_below) {
827 if (this.undelete_below) {
808 this.select(current_index+1);
828 this.select(current_index+1);
809 } else {
829 } else {
810 this.select(current_index);
830 this.select(current_index);
811 }
831 }
812 this.undelete_backup = null;
832 this.undelete_backup = null;
813 this.undelete_index = null;
833 this.undelete_index = null;
814 }
834 }
815 $('#undelete_cell').addClass('disabled');
835 $('#undelete_cell').addClass('disabled');
816 };
836 };
817
837
818 /**
838 /**
819 * Insert a cell so that after insertion the cell is at given index.
839 * Insert a cell so that after insertion the cell is at given index.
820 *
840 *
821 * If cell type is not provided, it will default to the type of the
841 * If cell type is not provided, it will default to the type of the
822 * currently active cell.
842 * currently active cell.
823 *
843 *
824 * Similar to insert_above, but index parameter is mandatory
844 * Similar to insert_above, but index parameter is mandatory
825 *
845 *
826 * Index will be brought back into the accessible range [0,n]
846 * Index will be brought back into the accessible range [0,n]
827 *
847 *
828 * @method insert_cell_at_index
848 * @method insert_cell_at_index
829 * @param [type] {string} in ['code','markdown', 'raw'], defaults to 'code'
849 * @param [type] {string} in ['code','markdown', 'raw'], defaults to 'code'
830 * @param [index] {int} a valid index where to insert cell
850 * @param [index] {int} a valid index where to insert cell
831 *
851 *
832 * @return cell {cell|null} created cell or null
852 * @return cell {cell|null} created cell or null
833 **/
853 **/
834 Notebook.prototype.insert_cell_at_index = function(type, index){
854 Notebook.prototype.insert_cell_at_index = function(type, index){
835
855
836 var ncells = this.ncells();
856 var ncells = this.ncells();
837 index = Math.min(index, ncells);
857 index = Math.min(index, ncells);
838 index = Math.max(index, 0);
858 index = Math.max(index, 0);
839 var cell = null;
859 var cell = null;
840 type = type || this.default_cell_type;
860 type = type || this.default_cell_type;
841 if (type === 'above') {
861 if (type === 'above') {
842 if (index > 0) {
862 if (index > 0) {
843 type = this.get_cell(index-1).cell_type;
863 type = this.get_cell(index-1).cell_type;
844 } else {
864 } else {
845 type = 'code';
865 type = 'code';
846 }
866 }
847 } else if (type === 'below') {
867 } else if (type === 'below') {
848 if (index < ncells) {
868 if (index < ncells) {
849 type = this.get_cell(index).cell_type;
869 type = this.get_cell(index).cell_type;
850 } else {
870 } else {
851 type = 'code';
871 type = 'code';
852 }
872 }
853 } else if (type === 'selected') {
873 } else if (type === 'selected') {
854 type = this.get_selected_cell().cell_type;
874 type = this.get_selected_cell().cell_type;
855 }
875 }
856
876
857 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
877 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
858 var cell_options = {
878 var cell_options = {
859 events: this.events,
879 events: this.events,
860 config: this.config,
880 config: this.config,
861 keyboard_manager: this.keyboard_manager,
881 keyboard_manager: this.keyboard_manager,
862 notebook: this,
882 notebook: this,
863 tooltip: this.tooltip,
883 tooltip: this.tooltip,
864 };
884 };
865 switch(type) {
885 switch(type) {
866 case 'code':
886 case 'code':
867 cell = new codecell.CodeCell(this.kernel, cell_options);
887 cell = new codecell.CodeCell(this.kernel, cell_options);
868 cell.set_input_prompt();
888 cell.set_input_prompt();
869 break;
889 break;
870 case 'markdown':
890 case 'markdown':
871 cell = new textcell.MarkdownCell(cell_options);
891 cell = new textcell.MarkdownCell(cell_options);
872 break;
892 break;
873 case 'raw':
893 case 'raw':
874 cell = new textcell.RawCell(cell_options);
894 cell = new textcell.RawCell(cell_options);
875 break;
895 break;
876 default:
896 default:
877 console.log("invalid cell type: ", type);
897 console.log("invalid cell type: ", type);
878 }
898 }
879
899
880 if(this._insert_element_at_index(cell.element,index)) {
900 if(this._insert_element_at_index(cell.element,index)) {
881 cell.render();
901 cell.render();
882 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
902 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
883 cell.refresh();
903 cell.refresh();
884 // We used to select the cell after we refresh it, but there
904 // We used to select the cell after we refresh it, but there
885 // are now cases were this method is called where select is
905 // are now cases were this method is called where select is
886 // not appropriate. The selection logic should be handled by the
906 // not appropriate. The selection logic should be handled by the
887 // caller of the the top level insert_cell methods.
907 // caller of the the top level insert_cell methods.
888 this.set_dirty(true);
908 this.set_dirty(true);
889 }
909 }
890 }
910 }
891 return cell;
911 return cell;
892
912
893 };
913 };
894
914
895 /**
915 /**
896 * Insert an element at given cell index.
916 * Insert an element at given cell index.
897 *
917 *
898 * @method _insert_element_at_index
918 * @method _insert_element_at_index
899 * @param element {dom_element} a cell element
919 * @param element {dom_element} a cell element
900 * @param [index] {int} a valid index where to inser cell
920 * @param [index] {int} a valid index where to inser cell
901 * @private
921 * @private
902 *
922 *
903 * return true if everything whent fine.
923 * return true if everything whent fine.
904 **/
924 **/
905 Notebook.prototype._insert_element_at_index = function(element, index){
925 Notebook.prototype._insert_element_at_index = function(element, index){
906 if (element === undefined){
926 if (element === undefined){
907 return false;
927 return false;
908 }
928 }
909
929
910 var ncells = this.ncells();
930 var ncells = this.ncells();
911
931
912 if (ncells === 0) {
932 if (ncells === 0) {
913 // special case append if empty
933 // special case append if empty
914 this.element.find('div.end_space').before(element);
934 this.element.find('div.end_space').before(element);
915 } else if ( ncells === index ) {
935 } else if ( ncells === index ) {
916 // special case append it the end, but not empty
936 // special case append it the end, but not empty
917 this.get_cell_element(index-1).after(element);
937 this.get_cell_element(index-1).after(element);
918 } else if (this.is_valid_cell_index(index)) {
938 } else if (this.is_valid_cell_index(index)) {
919 // otherwise always somewhere to append to
939 // otherwise always somewhere to append to
920 this.get_cell_element(index).before(element);
940 this.get_cell_element(index).before(element);
921 } else {
941 } else {
922 return false;
942 return false;
923 }
943 }
924
944
925 if (this.undelete_index !== null && index <= this.undelete_index) {
945 if (this.undelete_index !== null && index <= this.undelete_index) {
926 this.undelete_index = this.undelete_index + 1;
946 this.undelete_index = this.undelete_index + 1;
927 this.set_dirty(true);
947 this.set_dirty(true);
928 }
948 }
929 return true;
949 return true;
930 };
950 };
931
951
932 /**
952 /**
933 * Insert a cell of given type above given index, or at top
953 * Insert a cell of given type above given index, or at top
934 * of notebook if index smaller than 0.
954 * of notebook if index smaller than 0.
935 *
955 *
936 * default index value is the one of currently selected cell
956 * default index value is the one of currently selected cell
937 *
957 *
938 * @method insert_cell_above
958 * @method insert_cell_above
939 * @param [type] {string} cell type
959 * @param [type] {string} cell type
940 * @param [index] {integer}
960 * @param [index] {integer}
941 *
961 *
942 * @return handle to created cell or null
962 * @return handle to created cell or null
943 **/
963 **/
944 Notebook.prototype.insert_cell_above = function (type, index) {
964 Notebook.prototype.insert_cell_above = function (type, index) {
945 index = this.index_or_selected(index);
965 index = this.index_or_selected(index);
946 return this.insert_cell_at_index(type, index);
966 return this.insert_cell_at_index(type, index);
947 };
967 };
948
968
949 /**
969 /**
950 * Insert a cell of given type below given index, or at bottom
970 * Insert a cell of given type below given index, or at bottom
951 * of notebook if index greater than number of cells
971 * of notebook if index greater than number of cells
952 *
972 *
953 * default index value is the one of currently selected cell
973 * default index value is the one of currently selected cell
954 *
974 *
955 * @method insert_cell_below
975 * @method insert_cell_below
956 * @param [type] {string} cell type
976 * @param [type] {string} cell type
957 * @param [index] {integer}
977 * @param [index] {integer}
958 *
978 *
959 * @return handle to created cell or null
979 * @return handle to created cell or null
960 *
980 *
961 **/
981 **/
962 Notebook.prototype.insert_cell_below = function (type, index) {
982 Notebook.prototype.insert_cell_below = function (type, index) {
963 index = this.index_or_selected(index);
983 index = this.index_or_selected(index);
964 return this.insert_cell_at_index(type, index+1);
984 return this.insert_cell_at_index(type, index+1);
965 };
985 };
966
986
967
987
968 /**
988 /**
969 * Insert cell at end of notebook
989 * Insert cell at end of notebook
970 *
990 *
971 * @method insert_cell_at_bottom
991 * @method insert_cell_at_bottom
972 * @param {String} type cell type
992 * @param {String} type cell type
973 *
993 *
974 * @return the added cell; or null
994 * @return the added cell; or null
975 **/
995 **/
976 Notebook.prototype.insert_cell_at_bottom = function (type){
996 Notebook.prototype.insert_cell_at_bottom = function (type){
977 var len = this.ncells();
997 var len = this.ncells();
978 return this.insert_cell_below(type,len-1);
998 return this.insert_cell_below(type,len-1);
979 };
999 };
980
1000
981 /**
1001 /**
982 * Turn a cell into a code cell.
1002 * Turn a cell into a code cell.
983 *
1003 *
984 * @method to_code
1004 * @method to_code
985 * @param {Number} [index] A cell's index
1005 * @param {Number} [index] A cell's index
986 */
1006 */
987 Notebook.prototype.to_code = function (index) {
1007 Notebook.prototype.to_code = function (index) {
988 var i = this.index_or_selected(index);
1008 var i = this.index_or_selected(index);
989 if (this.is_valid_cell_index(i)) {
1009 if (this.is_valid_cell_index(i)) {
990 var source_cell = this.get_cell(i);
1010 var source_cell = this.get_cell(i);
991 if (!(source_cell instanceof codecell.CodeCell)) {
1011 if (!(source_cell instanceof codecell.CodeCell)) {
992 var target_cell = this.insert_cell_below('code',i);
1012 var target_cell = this.insert_cell_below('code',i);
993 var text = source_cell.get_text();
1013 var text = source_cell.get_text();
994 if (text === source_cell.placeholder) {
1014 if (text === source_cell.placeholder) {
995 text = '';
1015 text = '';
996 }
1016 }
997 //metadata
1017 //metadata
998 target_cell.metadata = source_cell.metadata;
1018 target_cell.metadata = source_cell.metadata;
999
1019
1000 target_cell.set_text(text);
1020 target_cell.set_text(text);
1001 // make this value the starting point, so that we can only undo
1021 // make this value the starting point, so that we can only undo
1002 // to this state, instead of a blank cell
1022 // to this state, instead of a blank cell
1003 target_cell.code_mirror.clearHistory();
1023 target_cell.code_mirror.clearHistory();
1004 source_cell.element.remove();
1024 source_cell.element.remove();
1005 this.select(i);
1025 this.select(i);
1006 var cursor = source_cell.code_mirror.getCursor();
1026 var cursor = source_cell.code_mirror.getCursor();
1007 target_cell.code_mirror.setCursor(cursor);
1027 target_cell.code_mirror.setCursor(cursor);
1008 this.set_dirty(true);
1028 this.set_dirty(true);
1009 }
1029 }
1010 }
1030 }
1011 };
1031 };
1012
1032
1013 /**
1033 /**
1014 * Turn a cell into a Markdown cell.
1034 * Turn a cell into a Markdown cell.
1015 *
1035 *
1016 * @method to_markdown
1036 * @method to_markdown
1017 * @param {Number} [index] A cell's index
1037 * @param {Number} [index] A cell's index
1018 */
1038 */
1019 Notebook.prototype.to_markdown = function (index) {
1039 Notebook.prototype.to_markdown = function (index) {
1020 var i = this.index_or_selected(index);
1040 var i = this.index_or_selected(index);
1021 if (this.is_valid_cell_index(i)) {
1041 if (this.is_valid_cell_index(i)) {
1022 var source_cell = this.get_cell(i);
1042 var source_cell = this.get_cell(i);
1023
1043
1024 if (!(source_cell instanceof textcell.MarkdownCell)) {
1044 if (!(source_cell instanceof textcell.MarkdownCell)) {
1025 var target_cell = this.insert_cell_below('markdown',i);
1045 var target_cell = this.insert_cell_below('markdown',i);
1026 var text = source_cell.get_text();
1046 var text = source_cell.get_text();
1027
1047
1028 if (text === source_cell.placeholder) {
1048 if (text === source_cell.placeholder) {
1029 text = '';
1049 text = '';
1030 }
1050 }
1031 // metadata
1051 // metadata
1032 target_cell.metadata = source_cell.metadata;
1052 target_cell.metadata = source_cell.metadata;
1033 // We must show the editor before setting its contents
1053 // We must show the editor before setting its contents
1034 target_cell.unrender();
1054 target_cell.unrender();
1035 target_cell.set_text(text);
1055 target_cell.set_text(text);
1036 // make this value the starting point, so that we can only undo
1056 // make this value the starting point, so that we can only undo
1037 // to this state, instead of a blank cell
1057 // to this state, instead of a blank cell
1038 target_cell.code_mirror.clearHistory();
1058 target_cell.code_mirror.clearHistory();
1039 source_cell.element.remove();
1059 source_cell.element.remove();
1040 this.select(i);
1060 this.select(i);
1041 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1061 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1042 target_cell.render();
1062 target_cell.render();
1043 }
1063 }
1044 var cursor = source_cell.code_mirror.getCursor();
1064 var cursor = source_cell.code_mirror.getCursor();
1045 target_cell.code_mirror.setCursor(cursor);
1065 target_cell.code_mirror.setCursor(cursor);
1046 this.set_dirty(true);
1066 this.set_dirty(true);
1047 }
1067 }
1048 }
1068 }
1049 };
1069 };
1050
1070
1051 /**
1071 /**
1052 * Turn a cell into a raw text cell.
1072 * Turn a cell into a raw text cell.
1053 *
1073 *
1054 * @method to_raw
1074 * @method to_raw
1055 * @param {Number} [index] A cell's index
1075 * @param {Number} [index] A cell's index
1056 */
1076 */
1057 Notebook.prototype.to_raw = function (index) {
1077 Notebook.prototype.to_raw = function (index) {
1058 var i = this.index_or_selected(index);
1078 var i = this.index_or_selected(index);
1059 if (this.is_valid_cell_index(i)) {
1079 if (this.is_valid_cell_index(i)) {
1060 var target_cell = null;
1080 var target_cell = null;
1061 var source_cell = this.get_cell(i);
1081 var source_cell = this.get_cell(i);
1062
1082
1063 if (!(source_cell instanceof textcell.RawCell)) {
1083 if (!(source_cell instanceof textcell.RawCell)) {
1064 target_cell = this.insert_cell_below('raw',i);
1084 target_cell = this.insert_cell_below('raw',i);
1065 var text = source_cell.get_text();
1085 var text = source_cell.get_text();
1066 if (text === source_cell.placeholder) {
1086 if (text === source_cell.placeholder) {
1067 text = '';
1087 text = '';
1068 }
1088 }
1069 //metadata
1089 //metadata
1070 target_cell.metadata = source_cell.metadata;
1090 target_cell.metadata = source_cell.metadata;
1071 // We must show the editor before setting its contents
1091 // We must show the editor before setting its contents
1072 target_cell.unrender();
1092 target_cell.unrender();
1073 target_cell.set_text(text);
1093 target_cell.set_text(text);
1074 // 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
1075 // to this state, instead of a blank cell
1095 // to this state, instead of a blank cell
1076 target_cell.code_mirror.clearHistory();
1096 target_cell.code_mirror.clearHistory();
1077 source_cell.element.remove();
1097 source_cell.element.remove();
1078 this.select(i);
1098 this.select(i);
1079 var cursor = source_cell.code_mirror.getCursor();
1099 var cursor = source_cell.code_mirror.getCursor();
1080 target_cell.code_mirror.setCursor(cursor);
1100 target_cell.code_mirror.setCursor(cursor);
1081 this.set_dirty(true);
1101 this.set_dirty(true);
1082 }
1102 }
1083 }
1103 }
1084 };
1104 };
1085
1105
1086 Notebook.prototype._warn_heading = function () {
1106 Notebook.prototype._warn_heading = function () {
1087 // warn about heading cells being removed
1107 // warn about heading cells being removed
1088 dialog.modal({
1108 dialog.modal({
1089 notebook: this,
1109 notebook: this,
1090 keyboard_manager: this.keyboard_manager,
1110 keyboard_manager: this.keyboard_manager,
1091 title : "Use markdown headings",
1111 title : "Use markdown headings",
1092 body : $("<p/>").text(
1112 body : $("<p/>").text(
1093 'IPython no longer uses special heading cells. ' +
1113 'IPython no longer uses special heading cells. ' +
1094 'Instead, write your headings in Markdown cells using # characters:'
1114 'Instead, write your headings in Markdown cells using # characters:'
1095 ).append($('<pre/>').text(
1115 ).append($('<pre/>').text(
1096 '## This is a level 2 heading'
1116 '## This is a level 2 heading'
1097 )),
1117 )),
1098 buttons : {
1118 buttons : {
1099 "OK" : {},
1119 "OK" : {},
1100 }
1120 }
1101 });
1121 });
1102 };
1122 };
1103
1123
1104 /**
1124 /**
1105 * Turn a cell into a markdown cell with a heading.
1125 * Turn a cell into a markdown cell with a heading.
1106 *
1126 *
1107 * @method to_heading
1127 * @method to_heading
1108 * @param {Number} [index] A cell's index
1128 * @param {Number} [index] A cell's index
1109 * @param {Number} [level] A heading level (e.g., 1 for h1)
1129 * @param {Number} [level] A heading level (e.g., 1 for h1)
1110 */
1130 */
1111 Notebook.prototype.to_heading = function (index, level) {
1131 Notebook.prototype.to_heading = function (index, level) {
1112 this.to_markdown(index);
1132 this.to_markdown(index);
1113 level = level || 1;
1133 level = level || 1;
1114 var i = this.index_or_selected(index);
1134 var i = this.index_or_selected(index);
1115 if (this.is_valid_cell_index(i)) {
1135 if (this.is_valid_cell_index(i)) {
1116 var cell = this.get_cell(i);
1136 var cell = this.get_cell(i);
1117 cell.set_heading_level(level);
1137 cell.set_heading_level(level);
1118 this.set_dirty(true);
1138 this.set_dirty(true);
1119 }
1139 }
1120 };
1140 };
1121
1141
1122
1142
1123 // Cut/Copy/Paste
1143 // Cut/Copy/Paste
1124
1144
1125 /**
1145 /**
1126 * Enable UI elements for pasting cells.
1146 * Enable UI elements for pasting cells.
1127 *
1147 *
1128 * @method enable_paste
1148 * @method enable_paste
1129 */
1149 */
1130 Notebook.prototype.enable_paste = function () {
1150 Notebook.prototype.enable_paste = function () {
1131 var that = this;
1151 var that = this;
1132 if (!this.paste_enabled) {
1152 if (!this.paste_enabled) {
1133 $('#paste_cell_replace').removeClass('disabled')
1153 $('#paste_cell_replace').removeClass('disabled')
1134 .on('click', function () {that.paste_cell_replace();});
1154 .on('click', function () {that.paste_cell_replace();});
1135 $('#paste_cell_above').removeClass('disabled')
1155 $('#paste_cell_above').removeClass('disabled')
1136 .on('click', function () {that.paste_cell_above();});
1156 .on('click', function () {that.paste_cell_above();});
1137 $('#paste_cell_below').removeClass('disabled')
1157 $('#paste_cell_below').removeClass('disabled')
1138 .on('click', function () {that.paste_cell_below();});
1158 .on('click', function () {that.paste_cell_below();});
1139 this.paste_enabled = true;
1159 this.paste_enabled = true;
1140 }
1160 }
1141 };
1161 };
1142
1162
1143 /**
1163 /**
1144 * Disable UI elements for pasting cells.
1164 * Disable UI elements for pasting cells.
1145 *
1165 *
1146 * @method disable_paste
1166 * @method disable_paste
1147 */
1167 */
1148 Notebook.prototype.disable_paste = function () {
1168 Notebook.prototype.disable_paste = function () {
1149 if (this.paste_enabled) {
1169 if (this.paste_enabled) {
1150 $('#paste_cell_replace').addClass('disabled').off('click');
1170 $('#paste_cell_replace').addClass('disabled').off('click');
1151 $('#paste_cell_above').addClass('disabled').off('click');
1171 $('#paste_cell_above').addClass('disabled').off('click');
1152 $('#paste_cell_below').addClass('disabled').off('click');
1172 $('#paste_cell_below').addClass('disabled').off('click');
1153 this.paste_enabled = false;
1173 this.paste_enabled = false;
1154 }
1174 }
1155 };
1175 };
1156
1176
1157 /**
1177 /**
1158 * Cut a cell.
1178 * Cut a cell.
1159 *
1179 *
1160 * @method cut_cell
1180 * @method cut_cell
1161 */
1181 */
1162 Notebook.prototype.cut_cell = function () {
1182 Notebook.prototype.cut_cell = function () {
1163 this.copy_cell();
1183 this.copy_cell();
1164 this.delete_cell();
1184 this.delete_cell();
1165 };
1185 };
1166
1186
1167 /**
1187 /**
1168 * Copy a cell.
1188 * Copy a cell.
1169 *
1189 *
1170 * @method copy_cell
1190 * @method copy_cell
1171 */
1191 */
1172 Notebook.prototype.copy_cell = function () {
1192 Notebook.prototype.copy_cell = function () {
1173 var cell = this.get_selected_cell();
1193 var cell = this.get_selected_cell();
1174 this.clipboard = cell.toJSON();
1194 this.clipboard = cell.toJSON();
1175 // remove undeletable status from the copied cell
1195 // remove undeletable status from the copied cell
1176 if (this.clipboard.metadata.deletable !== undefined) {
1196 if (this.clipboard.metadata.deletable !== undefined) {
1177 delete this.clipboard.metadata.deletable;
1197 delete this.clipboard.metadata.deletable;
1178 }
1198 }
1179 this.enable_paste();
1199 this.enable_paste();
1180 };
1200 };
1181
1201
1182 /**
1202 /**
1183 * Replace the selected cell with a cell in the clipboard.
1203 * Replace the selected cell with a cell in the clipboard.
1184 *
1204 *
1185 * @method paste_cell_replace
1205 * @method paste_cell_replace
1186 */
1206 */
1187 Notebook.prototype.paste_cell_replace = function () {
1207 Notebook.prototype.paste_cell_replace = function () {
1188 if (this.clipboard !== null && this.paste_enabled) {
1208 if (this.clipboard !== null && this.paste_enabled) {
1189 var cell_data = this.clipboard;
1209 var cell_data = this.clipboard;
1190 var new_cell = this.insert_cell_above(cell_data.cell_type);
1210 var new_cell = this.insert_cell_above(cell_data.cell_type);
1191 new_cell.fromJSON(cell_data);
1211 new_cell.fromJSON(cell_data);
1192 var old_cell = this.get_next_cell(new_cell);
1212 var old_cell = this.get_next_cell(new_cell);
1193 this.delete_cell(this.find_cell_index(old_cell));
1213 this.delete_cell(this.find_cell_index(old_cell));
1194 this.select(this.find_cell_index(new_cell));
1214 this.select(this.find_cell_index(new_cell));
1195 }
1215 }
1196 };
1216 };
1197
1217
1198 /**
1218 /**
1199 * Paste a cell from the clipboard above the selected cell.
1219 * Paste a cell from the clipboard above the selected cell.
1200 *
1220 *
1201 * @method paste_cell_above
1221 * @method paste_cell_above
1202 */
1222 */
1203 Notebook.prototype.paste_cell_above = function () {
1223 Notebook.prototype.paste_cell_above = function () {
1204 if (this.clipboard !== null && this.paste_enabled) {
1224 if (this.clipboard !== null && this.paste_enabled) {
1205 var cell_data = this.clipboard;
1225 var cell_data = this.clipboard;
1206 var new_cell = this.insert_cell_above(cell_data.cell_type);
1226 var new_cell = this.insert_cell_above(cell_data.cell_type);
1207 new_cell.fromJSON(cell_data);
1227 new_cell.fromJSON(cell_data);
1208 new_cell.focus_cell();
1228 new_cell.focus_cell();
1209 }
1229 }
1210 };
1230 };
1211
1231
1212 /**
1232 /**
1213 * Paste a cell from the clipboard below the selected cell.
1233 * Paste a cell from the clipboard below the selected cell.
1214 *
1234 *
1215 * @method paste_cell_below
1235 * @method paste_cell_below
1216 */
1236 */
1217 Notebook.prototype.paste_cell_below = function () {
1237 Notebook.prototype.paste_cell_below = function () {
1218 if (this.clipboard !== null && this.paste_enabled) {
1238 if (this.clipboard !== null && this.paste_enabled) {
1219 var cell_data = this.clipboard;
1239 var cell_data = this.clipboard;
1220 var new_cell = this.insert_cell_below(cell_data.cell_type);
1240 var new_cell = this.insert_cell_below(cell_data.cell_type);
1221 new_cell.fromJSON(cell_data);
1241 new_cell.fromJSON(cell_data);
1222 new_cell.focus_cell();
1242 new_cell.focus_cell();
1223 }
1243 }
1224 };
1244 };
1225
1245
1226 // Split/merge
1246 // Split/merge
1227
1247
1228 /**
1248 /**
1229 * Split the selected cell into two, at the cursor.
1249 * Split the selected cell into two, at the cursor.
1230 *
1250 *
1231 * @method split_cell
1251 * @method split_cell
1232 */
1252 */
1233 Notebook.prototype.split_cell = function () {
1253 Notebook.prototype.split_cell = function () {
1234 var cell = this.get_selected_cell();
1254 var cell = this.get_selected_cell();
1235 if (cell.is_splittable()) {
1255 if (cell.is_splittable()) {
1236 var texta = cell.get_pre_cursor();
1256 var texta = cell.get_pre_cursor();
1237 var textb = cell.get_post_cursor();
1257 var textb = cell.get_post_cursor();
1238 cell.set_text(textb);
1258 cell.set_text(textb);
1239 var new_cell = this.insert_cell_above(cell.cell_type);
1259 var new_cell = this.insert_cell_above(cell.cell_type);
1240 // Unrender the new cell so we can call set_text.
1260 // Unrender the new cell so we can call set_text.
1241 new_cell.unrender();
1261 new_cell.unrender();
1242 new_cell.set_text(texta);
1262 new_cell.set_text(texta);
1243 }
1263 }
1244 };
1264 };
1245
1265
1246 /**
1266 /**
1247 * Combine the selected cell into the cell above it.
1267 * Combine the selected cell into the cell above it.
1248 *
1268 *
1249 * @method merge_cell_above
1269 * @method merge_cell_above
1250 */
1270 */
1251 Notebook.prototype.merge_cell_above = function () {
1271 Notebook.prototype.merge_cell_above = function () {
1252 var index = this.get_selected_index();
1272 var index = this.get_selected_index();
1253 var cell = this.get_cell(index);
1273 var cell = this.get_cell(index);
1254 var render = cell.rendered;
1274 var render = cell.rendered;
1255 if (!cell.is_mergeable()) {
1275 if (!cell.is_mergeable()) {
1256 return;
1276 return;
1257 }
1277 }
1258 if (index > 0) {
1278 if (index > 0) {
1259 var upper_cell = this.get_cell(index-1);
1279 var upper_cell = this.get_cell(index-1);
1260 if (!upper_cell.is_mergeable()) {
1280 if (!upper_cell.is_mergeable()) {
1261 return;
1281 return;
1262 }
1282 }
1263 var upper_text = upper_cell.get_text();
1283 var upper_text = upper_cell.get_text();
1264 var text = cell.get_text();
1284 var text = cell.get_text();
1265 if (cell instanceof codecell.CodeCell) {
1285 if (cell instanceof codecell.CodeCell) {
1266 cell.set_text(upper_text+'\n'+text);
1286 cell.set_text(upper_text+'\n'+text);
1267 } else {
1287 } else {
1268 cell.unrender(); // Must unrender before we set_text.
1288 cell.unrender(); // Must unrender before we set_text.
1269 cell.set_text(upper_text+'\n\n'+text);
1289 cell.set_text(upper_text+'\n\n'+text);
1270 if (render) {
1290 if (render) {
1271 // The rendered state of the final cell should match
1291 // The rendered state of the final cell should match
1272 // that of the original selected cell;
1292 // that of the original selected cell;
1273 cell.render();
1293 cell.render();
1274 }
1294 }
1275 }
1295 }
1276 this.delete_cell(index-1);
1296 this.delete_cell(index-1);
1277 this.select(this.find_cell_index(cell));
1297 this.select(this.find_cell_index(cell));
1278 }
1298 }
1279 };
1299 };
1280
1300
1281 /**
1301 /**
1282 * Combine the selected cell into the cell below it.
1302 * Combine the selected cell into the cell below it.
1283 *
1303 *
1284 * @method merge_cell_below
1304 * @method merge_cell_below
1285 */
1305 */
1286 Notebook.prototype.merge_cell_below = function () {
1306 Notebook.prototype.merge_cell_below = function () {
1287 var index = this.get_selected_index();
1307 var index = this.get_selected_index();
1288 var cell = this.get_cell(index);
1308 var cell = this.get_cell(index);
1289 var render = cell.rendered;
1309 var render = cell.rendered;
1290 if (!cell.is_mergeable()) {
1310 if (!cell.is_mergeable()) {
1291 return;
1311 return;
1292 }
1312 }
1293 if (index < this.ncells()-1) {
1313 if (index < this.ncells()-1) {
1294 var lower_cell = this.get_cell(index+1);
1314 var lower_cell = this.get_cell(index+1);
1295 if (!lower_cell.is_mergeable()) {
1315 if (!lower_cell.is_mergeable()) {
1296 return;
1316 return;
1297 }
1317 }
1298 var lower_text = lower_cell.get_text();
1318 var lower_text = lower_cell.get_text();
1299 var text = cell.get_text();
1319 var text = cell.get_text();
1300 if (cell instanceof codecell.CodeCell) {
1320 if (cell instanceof codecell.CodeCell) {
1301 cell.set_text(text+'\n'+lower_text);
1321 cell.set_text(text+'\n'+lower_text);
1302 } else {
1322 } else {
1303 cell.unrender(); // Must unrender before we set_text.
1323 cell.unrender(); // Must unrender before we set_text.
1304 cell.set_text(text+'\n\n'+lower_text);
1324 cell.set_text(text+'\n\n'+lower_text);
1305 if (render) {
1325 if (render) {
1306 // The rendered state of the final cell should match
1326 // The rendered state of the final cell should match
1307 // that of the original selected cell;
1327 // that of the original selected cell;
1308 cell.render();
1328 cell.render();
1309 }
1329 }
1310 }
1330 }
1311 this.delete_cell(index+1);
1331 this.delete_cell(index+1);
1312 this.select(this.find_cell_index(cell));
1332 this.select(this.find_cell_index(cell));
1313 }
1333 }
1314 };
1334 };
1315
1335
1316
1336
1317 // Cell collapsing and output clearing
1337 // Cell collapsing and output clearing
1318
1338
1319 /**
1339 /**
1320 * Hide a cell's output.
1340 * Hide a cell's output.
1321 *
1341 *
1322 * @method collapse_output
1342 * @method collapse_output
1323 * @param {Number} index A cell's numeric index
1343 * @param {Number} index A cell's numeric index
1324 */
1344 */
1325 Notebook.prototype.collapse_output = function (index) {
1345 Notebook.prototype.collapse_output = function (index) {
1326 var i = this.index_or_selected(index);
1346 var i = this.index_or_selected(index);
1327 var cell = this.get_cell(i);
1347 var cell = this.get_cell(i);
1328 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1348 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1329 cell.collapse_output();
1349 cell.collapse_output();
1330 this.set_dirty(true);
1350 this.set_dirty(true);
1331 }
1351 }
1332 };
1352 };
1333
1353
1334 /**
1354 /**
1335 * Hide each code cell's output area.
1355 * Hide each code cell's output area.
1336 *
1356 *
1337 * @method collapse_all_output
1357 * @method collapse_all_output
1338 */
1358 */
1339 Notebook.prototype.collapse_all_output = function () {
1359 Notebook.prototype.collapse_all_output = function () {
1340 this.get_cells().map(function (cell, i) {
1360 this.get_cells().map(function (cell, i) {
1341 if (cell instanceof codecell.CodeCell) {
1361 if (cell instanceof codecell.CodeCell) {
1342 cell.collapse_output();
1362 cell.collapse_output();
1343 }
1363 }
1344 });
1364 });
1345 // this should not be set if the `collapse` key is removed from nbformat
1365 // this should not be set if the `collapse` key is removed from nbformat
1346 this.set_dirty(true);
1366 this.set_dirty(true);
1347 };
1367 };
1348
1368
1349 /**
1369 /**
1350 * Show a cell's output.
1370 * Show a cell's output.
1351 *
1371 *
1352 * @method expand_output
1372 * @method expand_output
1353 * @param {Number} index A cell's numeric index
1373 * @param {Number} index A cell's numeric index
1354 */
1374 */
1355 Notebook.prototype.expand_output = function (index) {
1375 Notebook.prototype.expand_output = function (index) {
1356 var i = this.index_or_selected(index);
1376 var i = this.index_or_selected(index);
1357 var cell = this.get_cell(i);
1377 var cell = this.get_cell(i);
1358 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1378 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1359 cell.expand_output();
1379 cell.expand_output();
1360 this.set_dirty(true);
1380 this.set_dirty(true);
1361 }
1381 }
1362 };
1382 };
1363
1383
1364 /**
1384 /**
1365 * Expand each code cell's output area, and remove scrollbars.
1385 * Expand each code cell's output area, and remove scrollbars.
1366 *
1386 *
1367 * @method expand_all_output
1387 * @method expand_all_output
1368 */
1388 */
1369 Notebook.prototype.expand_all_output = function () {
1389 Notebook.prototype.expand_all_output = function () {
1370 this.get_cells().map(function (cell, i) {
1390 this.get_cells().map(function (cell, i) {
1371 if (cell instanceof codecell.CodeCell) {
1391 if (cell instanceof codecell.CodeCell) {
1372 cell.expand_output();
1392 cell.expand_output();
1373 }
1393 }
1374 });
1394 });
1375 // this should not be set if the `collapse` key is removed from nbformat
1395 // this should not be set if the `collapse` key is removed from nbformat
1376 this.set_dirty(true);
1396 this.set_dirty(true);
1377 };
1397 };
1378
1398
1379 /**
1399 /**
1380 * Clear the selected CodeCell's output area.
1400 * Clear the selected CodeCell's output area.
1381 *
1401 *
1382 * @method clear_output
1402 * @method clear_output
1383 * @param {Number} index A cell's numeric index
1403 * @param {Number} index A cell's numeric index
1384 */
1404 */
1385 Notebook.prototype.clear_output = function (index) {
1405 Notebook.prototype.clear_output = function (index) {
1386 var i = this.index_or_selected(index);
1406 var i = this.index_or_selected(index);
1387 var cell = this.get_cell(i);
1407 var cell = this.get_cell(i);
1388 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1408 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1389 cell.clear_output();
1409 cell.clear_output();
1390 this.set_dirty(true);
1410 this.set_dirty(true);
1391 }
1411 }
1392 };
1412 };
1393
1413
1394 /**
1414 /**
1395 * Clear each code cell's output area.
1415 * Clear each code cell's output area.
1396 *
1416 *
1397 * @method clear_all_output
1417 * @method clear_all_output
1398 */
1418 */
1399 Notebook.prototype.clear_all_output = function () {
1419 Notebook.prototype.clear_all_output = function () {
1400 this.get_cells().map(function (cell, i) {
1420 this.get_cells().map(function (cell, i) {
1401 if (cell instanceof codecell.CodeCell) {
1421 if (cell instanceof codecell.CodeCell) {
1402 cell.clear_output();
1422 cell.clear_output();
1403 }
1423 }
1404 });
1424 });
1405 this.set_dirty(true);
1425 this.set_dirty(true);
1406 };
1426 };
1407
1427
1408 /**
1428 /**
1409 * Scroll the selected CodeCell's output area.
1429 * Scroll the selected CodeCell's output area.
1410 *
1430 *
1411 * @method scroll_output
1431 * @method scroll_output
1412 * @param {Number} index A cell's numeric index
1432 * @param {Number} index A cell's numeric index
1413 */
1433 */
1414 Notebook.prototype.scroll_output = function (index) {
1434 Notebook.prototype.scroll_output = function (index) {
1415 var i = this.index_or_selected(index);
1435 var i = this.index_or_selected(index);
1416 var cell = this.get_cell(i);
1436 var cell = this.get_cell(i);
1417 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1437 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1418 cell.scroll_output();
1438 cell.scroll_output();
1419 this.set_dirty(true);
1439 this.set_dirty(true);
1420 }
1440 }
1421 };
1441 };
1422
1442
1423 /**
1443 /**
1424 * Expand each code cell's output area, and add a scrollbar for long output.
1444 * Expand each code cell's output area, and add a scrollbar for long output.
1425 *
1445 *
1426 * @method scroll_all_output
1446 * @method scroll_all_output
1427 */
1447 */
1428 Notebook.prototype.scroll_all_output = function () {
1448 Notebook.prototype.scroll_all_output = function () {
1429 this.get_cells().map(function (cell, i) {
1449 this.get_cells().map(function (cell, i) {
1430 if (cell instanceof codecell.CodeCell) {
1450 if (cell instanceof codecell.CodeCell) {
1431 cell.scroll_output();
1451 cell.scroll_output();
1432 }
1452 }
1433 });
1453 });
1434 // this should not be set if the `collapse` key is removed from nbformat
1454 // this should not be set if the `collapse` key is removed from nbformat
1435 this.set_dirty(true);
1455 this.set_dirty(true);
1436 };
1456 };
1437
1457
1438 /** Toggle whether a cell's output is collapsed or expanded.
1458 /** Toggle whether a cell's output is collapsed or expanded.
1439 *
1459 *
1440 * @method toggle_output
1460 * @method toggle_output
1441 * @param {Number} index A cell's numeric index
1461 * @param {Number} index A cell's numeric index
1442 */
1462 */
1443 Notebook.prototype.toggle_output = function (index) {
1463 Notebook.prototype.toggle_output = function (index) {
1444 var i = this.index_or_selected(index);
1464 var i = this.index_or_selected(index);
1445 var cell = this.get_cell(i);
1465 var cell = this.get_cell(i);
1446 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1466 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1447 cell.toggle_output();
1467 cell.toggle_output();
1448 this.set_dirty(true);
1468 this.set_dirty(true);
1449 }
1469 }
1450 };
1470 };
1451
1471
1452 /**
1472 /**
1453 * Hide/show the output of all cells.
1473 * Hide/show the output of all cells.
1454 *
1474 *
1455 * @method toggle_all_output
1475 * @method toggle_all_output
1456 */
1476 */
1457 Notebook.prototype.toggle_all_output = function () {
1477 Notebook.prototype.toggle_all_output = function () {
1458 this.get_cells().map(function (cell, i) {
1478 this.get_cells().map(function (cell, i) {
1459 if (cell instanceof codecell.CodeCell) {
1479 if (cell instanceof codecell.CodeCell) {
1460 cell.toggle_output();
1480 cell.toggle_output();
1461 }
1481 }
1462 });
1482 });
1463 // this should not be set if the `collapse` key is removed from nbformat
1483 // this should not be set if the `collapse` key is removed from nbformat
1464 this.set_dirty(true);
1484 this.set_dirty(true);
1465 };
1485 };
1466
1486
1467 /**
1487 /**
1468 * Toggle a scrollbar for long cell outputs.
1488 * Toggle a scrollbar for long cell outputs.
1469 *
1489 *
1470 * @method toggle_output_scroll
1490 * @method toggle_output_scroll
1471 * @param {Number} index A cell's numeric index
1491 * @param {Number} index A cell's numeric index
1472 */
1492 */
1473 Notebook.prototype.toggle_output_scroll = function (index) {
1493 Notebook.prototype.toggle_output_scroll = function (index) {
1474 var i = this.index_or_selected(index);
1494 var i = this.index_or_selected(index);
1475 var cell = this.get_cell(i);
1495 var cell = this.get_cell(i);
1476 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1496 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1477 cell.toggle_output_scroll();
1497 cell.toggle_output_scroll();
1478 this.set_dirty(true);
1498 this.set_dirty(true);
1479 }
1499 }
1480 };
1500 };
1481
1501
1482 /**
1502 /**
1483 * Toggle the scrolling of long output on all cells.
1503 * Toggle the scrolling of long output on all cells.
1484 *
1504 *
1485 * @method toggle_all_output_scrolling
1505 * @method toggle_all_output_scrolling
1486 */
1506 */
1487 Notebook.prototype.toggle_all_output_scroll = function () {
1507 Notebook.prototype.toggle_all_output_scroll = function () {
1488 this.get_cells().map(function (cell, i) {
1508 this.get_cells().map(function (cell, i) {
1489 if (cell instanceof codecell.CodeCell) {
1509 if (cell instanceof codecell.CodeCell) {
1490 cell.toggle_output_scroll();
1510 cell.toggle_output_scroll();
1491 }
1511 }
1492 });
1512 });
1493 // this should not be set if the `collapse` key is removed from nbformat
1513 // this should not be set if the `collapse` key is removed from nbformat
1494 this.set_dirty(true);
1514 this.set_dirty(true);
1495 };
1515 };
1496
1516
1497 // Other cell functions: line numbers, ...
1517 // Other cell functions: line numbers, ...
1498
1518
1499 /**
1519 /**
1500 * Toggle line numbers in the selected cell's input area.
1520 * Toggle line numbers in the selected cell's input area.
1501 *
1521 *
1502 * @method cell_toggle_line_numbers
1522 * @method cell_toggle_line_numbers
1503 */
1523 */
1504 Notebook.prototype.cell_toggle_line_numbers = function() {
1524 Notebook.prototype.cell_toggle_line_numbers = function() {
1505 this.get_selected_cell().toggle_line_numbers();
1525 this.get_selected_cell().toggle_line_numbers();
1506 };
1526 };
1507
1527
1508 /**
1528 /**
1509 * Set the codemirror mode for all code cells, including the default for
1529 * Set the codemirror mode for all code cells, including the default for
1510 * new code cells.
1530 * new code cells.
1511 *
1531 *
1512 * @method set_codemirror_mode
1532 * @method set_codemirror_mode
1513 */
1533 */
1514 Notebook.prototype.set_codemirror_mode = function(newmode){
1534 Notebook.prototype.set_codemirror_mode = function(newmode){
1515 if (newmode === this.codemirror_mode) {
1535 if (newmode === this.codemirror_mode) {
1516 return;
1536 return;
1517 }
1537 }
1518 this.codemirror_mode = newmode;
1538 this.codemirror_mode = newmode;
1519 codecell.CodeCell.options_default.cm_config.mode = newmode;
1539 codecell.CodeCell.options_default.cm_config.mode = newmode;
1520 var modename = newmode.mode || newmode.name || newmode;
1540 var modename = newmode.mode || newmode.name || newmode;
1521
1541
1522 var that = this;
1542 var that = this;
1523 utils.requireCodeMirrorMode(modename, function () {
1543 utils.requireCodeMirrorMode(modename, function () {
1524 that.get_cells().map(function(cell, i) {
1544 that.get_cells().map(function(cell, i) {
1525 if (cell.cell_type === 'code'){
1545 if (cell.cell_type === 'code'){
1526 cell.code_mirror.setOption('mode', newmode);
1546 cell.code_mirror.setOption('mode', newmode);
1527 // This is currently redundant, because cm_config ends up as
1547 // This is currently redundant, because cm_config ends up as
1528 // codemirror's own .options object, but I don't want to
1548 // codemirror's own .options object, but I don't want to
1529 // rely on that.
1549 // rely on that.
1530 cell.cm_config.mode = newmode;
1550 cell.cm_config.mode = newmode;
1531 }
1551 }
1532 });
1552 });
1533 });
1553 });
1534 };
1554 };
1535
1555
1536 // Session related things
1556 // Session related things
1537
1557
1538 /**
1558 /**
1539 * Start a new session and set it on each code cell.
1559 * Start a new session and set it on each code cell.
1540 *
1560 *
1541 * @method start_session
1561 * @method start_session
1542 */
1562 */
1543 Notebook.prototype.start_session = function (kernel_name) {
1563 Notebook.prototype.start_session = function (kernel_name) {
1544 if (this._session_starting) {
1564 if (this._session_starting) {
1545 throw new session.SessionAlreadyStarting();
1565 throw new session.SessionAlreadyStarting();
1546 }
1566 }
1547 this._session_starting = true;
1567 this._session_starting = true;
1548
1568
1549 var options = {
1569 var options = {
1550 base_url: this.base_url,
1570 base_url: this.base_url,
1551 ws_url: this.ws_url,
1571 ws_url: this.ws_url,
1552 notebook_path: this.notebook_path,
1572 notebook_path: this.notebook_path,
1553 notebook_name: this.notebook_name,
1573 notebook_name: this.notebook_name,
1554 kernel_name: kernel_name,
1574 kernel_name: kernel_name,
1555 notebook: this
1575 notebook: this
1556 };
1576 };
1557
1577
1558 var success = $.proxy(this._session_started, this);
1578 var success = $.proxy(this._session_started, this);
1559 var failure = $.proxy(this._session_start_failed, this);
1579 var failure = $.proxy(this._session_start_failed, this);
1560
1580
1561 if (this.session !== null) {
1581 if (this.session !== null) {
1562 this.session.restart(options, success, failure);
1582 this.session.restart(options, success, failure);
1563 } else {
1583 } else {
1564 this.session = new session.Session(options);
1584 this.session = new session.Session(options);
1565 this.session.start(success, failure);
1585 this.session.start(success, failure);
1566 }
1586 }
1567 };
1587 };
1568
1588
1569
1589
1570 /**
1590 /**
1571 * Once a session is started, link the code cells to the kernel and pass the
1591 * Once a session is started, link the code cells to the kernel and pass the
1572 * comm manager to the widget manager
1592 * comm manager to the widget manager
1573 *
1593 *
1574 */
1594 */
1575 Notebook.prototype._session_started = function (){
1595 Notebook.prototype._session_started = function (){
1576 this._session_starting = false;
1596 this._session_starting = false;
1577 this.kernel = this.session.kernel;
1597 this.kernel = this.session.kernel;
1578 var ncells = this.ncells();
1598 var ncells = this.ncells();
1579 for (var i=0; i<ncells; i++) {
1599 for (var i=0; i<ncells; i++) {
1580 var cell = this.get_cell(i);
1600 var cell = this.get_cell(i);
1581 if (cell instanceof codecell.CodeCell) {
1601 if (cell instanceof codecell.CodeCell) {
1582 cell.set_kernel(this.session.kernel);
1602 cell.set_kernel(this.session.kernel);
1583 }
1603 }
1584 }
1604 }
1585 };
1605 };
1586 Notebook.prototype._session_start_failed = function (jqxhr, status, error){
1606 Notebook.prototype._session_start_failed = function (jqxhr, status, error){
1587 this._session_starting = false;
1607 this._session_starting = false;
1588 utils.log_ajax_error(jqxhr, status, error);
1608 utils.log_ajax_error(jqxhr, status, error);
1589 };
1609 };
1590
1610
1591 /**
1611 /**
1592 * Prompt the user to restart the IPython kernel.
1612 * Prompt the user to restart the IPython kernel.
1593 *
1613 *
1594 * @method restart_kernel
1614 * @method restart_kernel
1595 */
1615 */
1596 Notebook.prototype.restart_kernel = function () {
1616 Notebook.prototype.restart_kernel = function () {
1597 var that = this;
1617 var that = this;
1598 dialog.modal({
1618 dialog.modal({
1599 notebook: this,
1619 notebook: this,
1600 keyboard_manager: this.keyboard_manager,
1620 keyboard_manager: this.keyboard_manager,
1601 title : "Restart kernel or continue running?",
1621 title : "Restart kernel or continue running?",
1602 body : $("<p/>").text(
1622 body : $("<p/>").text(
1603 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1623 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1604 ),
1624 ),
1605 buttons : {
1625 buttons : {
1606 "Continue running" : {},
1626 "Continue running" : {},
1607 "Restart" : {
1627 "Restart" : {
1608 "class" : "btn-danger",
1628 "class" : "btn-danger",
1609 "click" : function() {
1629 "click" : function() {
1610 that.kernel.restart();
1630 that.kernel.restart();
1611 }
1631 }
1612 }
1632 }
1613 }
1633 }
1614 });
1634 });
1615 };
1635 };
1616
1636
1617 /**
1637 /**
1618 * Execute or render cell outputs and go into command mode.
1638 * Execute or render cell outputs and go into command mode.
1619 *
1639 *
1620 * @method execute_cell
1640 * @method execute_cell
1621 */
1641 */
1622 Notebook.prototype.execute_cell = function () {
1642 Notebook.prototype.execute_cell = function () {
1623 // mode = shift, ctrl, alt
1643 // mode = shift, ctrl, alt
1624 var cell = this.get_selected_cell();
1644 var cell = this.get_selected_cell();
1625
1645
1626 cell.execute();
1646 cell.execute();
1627 this.command_mode();
1647 this.command_mode();
1628 this.set_dirty(true);
1648 this.set_dirty(true);
1629 };
1649 };
1630
1650
1631 /**
1651 /**
1632 * Execute or render cell outputs and insert a new cell below.
1652 * Execute or render cell outputs and insert a new cell below.
1633 *
1653 *
1634 * @method execute_cell_and_insert_below
1654 * @method execute_cell_and_insert_below
1635 */
1655 */
1636 Notebook.prototype.execute_cell_and_insert_below = function () {
1656 Notebook.prototype.execute_cell_and_insert_below = function () {
1637 var cell = this.get_selected_cell();
1657 var cell = this.get_selected_cell();
1638 var cell_index = this.find_cell_index(cell);
1658 var cell_index = this.find_cell_index(cell);
1639
1659
1640 cell.execute();
1660 cell.execute();
1641
1661
1642 // If we are at the end always insert a new cell and return
1662 // If we are at the end always insert a new cell and return
1643 if (cell_index === (this.ncells()-1)) {
1663 if (cell_index === (this.ncells()-1)) {
1644 this.command_mode();
1664 this.command_mode();
1645 this.insert_cell_below();
1665 this.insert_cell_below();
1646 this.select(cell_index+1);
1666 this.select(cell_index+1);
1647 this.edit_mode();
1667 this.edit_mode();
1648 this.scroll_to_bottom();
1668 this.scroll_to_bottom();
1649 this.set_dirty(true);
1669 this.set_dirty(true);
1650 return;
1670 return;
1651 }
1671 }
1652
1672
1653 this.command_mode();
1673 this.command_mode();
1654 this.insert_cell_below();
1674 this.insert_cell_below();
1655 this.select(cell_index+1);
1675 this.select(cell_index+1);
1656 this.edit_mode();
1676 this.edit_mode();
1657 this.set_dirty(true);
1677 this.set_dirty(true);
1658 };
1678 };
1659
1679
1660 /**
1680 /**
1661 * Execute or render cell outputs and select the next cell.
1681 * Execute or render cell outputs and select the next cell.
1662 *
1682 *
1663 * @method execute_cell_and_select_below
1683 * @method execute_cell_and_select_below
1664 */
1684 */
1665 Notebook.prototype.execute_cell_and_select_below = function () {
1685 Notebook.prototype.execute_cell_and_select_below = function () {
1666
1686
1667 var cell = this.get_selected_cell();
1687 var cell = this.get_selected_cell();
1668 var cell_index = this.find_cell_index(cell);
1688 var cell_index = this.find_cell_index(cell);
1669
1689
1670 cell.execute();
1690 cell.execute();
1671
1691
1672 // If we are at the end always insert a new cell and return
1692 // If we are at the end always insert a new cell and return
1673 if (cell_index === (this.ncells()-1)) {
1693 if (cell_index === (this.ncells()-1)) {
1674 this.command_mode();
1694 this.command_mode();
1675 this.insert_cell_below();
1695 this.insert_cell_below();
1676 this.select(cell_index+1);
1696 this.select(cell_index+1);
1677 this.edit_mode();
1697 this.edit_mode();
1678 this.scroll_to_bottom();
1698 this.scroll_to_bottom();
1679 this.set_dirty(true);
1699 this.set_dirty(true);
1680 return;
1700 return;
1681 }
1701 }
1682
1702
1683 this.command_mode();
1703 this.command_mode();
1684 this.select(cell_index+1);
1704 this.select(cell_index+1);
1685 this.focus_cell();
1705 this.focus_cell();
1686 this.set_dirty(true);
1706 this.set_dirty(true);
1687 };
1707 };
1688
1708
1689 /**
1709 /**
1690 * Execute all cells below the selected cell.
1710 * Execute all cells below the selected cell.
1691 *
1711 *
1692 * @method execute_cells_below
1712 * @method execute_cells_below
1693 */
1713 */
1694 Notebook.prototype.execute_cells_below = function () {
1714 Notebook.prototype.execute_cells_below = function () {
1695 this.execute_cell_range(this.get_selected_index(), this.ncells());
1715 this.execute_cell_range(this.get_selected_index(), this.ncells());
1696 this.scroll_to_bottom();
1716 this.scroll_to_bottom();
1697 };
1717 };
1698
1718
1699 /**
1719 /**
1700 * Execute all cells above the selected cell.
1720 * Execute all cells above the selected cell.
1701 *
1721 *
1702 * @method execute_cells_above
1722 * @method execute_cells_above
1703 */
1723 */
1704 Notebook.prototype.execute_cells_above = function () {
1724 Notebook.prototype.execute_cells_above = function () {
1705 this.execute_cell_range(0, this.get_selected_index());
1725 this.execute_cell_range(0, this.get_selected_index());
1706 };
1726 };
1707
1727
1708 /**
1728 /**
1709 * Execute all cells.
1729 * Execute all cells.
1710 *
1730 *
1711 * @method execute_all_cells
1731 * @method execute_all_cells
1712 */
1732 */
1713 Notebook.prototype.execute_all_cells = function () {
1733 Notebook.prototype.execute_all_cells = function () {
1714 this.execute_cell_range(0, this.ncells());
1734 this.execute_cell_range(0, this.ncells());
1715 this.scroll_to_bottom();
1735 this.scroll_to_bottom();
1716 };
1736 };
1717
1737
1718 /**
1738 /**
1719 * Execute a contiguous range of cells.
1739 * Execute a contiguous range of cells.
1720 *
1740 *
1721 * @method execute_cell_range
1741 * @method execute_cell_range
1722 * @param {Number} start Index of the first cell to execute (inclusive)
1742 * @param {Number} start Index of the first cell to execute (inclusive)
1723 * @param {Number} end Index of the last cell to execute (exclusive)
1743 * @param {Number} end Index of the last cell to execute (exclusive)
1724 */
1744 */
1725 Notebook.prototype.execute_cell_range = function (start, end) {
1745 Notebook.prototype.execute_cell_range = function (start, end) {
1726 this.command_mode();
1746 this.command_mode();
1727 for (var i=start; i<end; i++) {
1747 for (var i=start; i<end; i++) {
1728 this.select(i);
1748 this.select(i);
1729 this.execute_cell();
1749 this.execute_cell();
1730 }
1750 }
1731 };
1751 };
1732
1752
1733 // Persistance and loading
1753 // Persistance and loading
1734
1754
1735 /**
1755 /**
1736 * Getter method for this notebook's name.
1756 * Getter method for this notebook's name.
1737 *
1757 *
1738 * @method get_notebook_name
1758 * @method get_notebook_name
1739 * @return {String} This notebook's name (excluding file extension)
1759 * @return {String} This notebook's name (excluding file extension)
1740 */
1760 */
1741 Notebook.prototype.get_notebook_name = function () {
1761 Notebook.prototype.get_notebook_name = function () {
1742 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1762 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1743 return nbname;
1763 return nbname;
1744 };
1764 };
1745
1765
1746 /**
1766 /**
1747 * Setter method for this notebook's name.
1767 * Setter method for this notebook's name.
1748 *
1768 *
1749 * @method set_notebook_name
1769 * @method set_notebook_name
1750 * @param {String} name A new name for this notebook
1770 * @param {String} name A new name for this notebook
1751 */
1771 */
1752 Notebook.prototype.set_notebook_name = function (name) {
1772 Notebook.prototype.set_notebook_name = function (name) {
1753 var parent = utils.url_path_split(this.notebook_path)[0];
1773 var parent = utils.url_path_split(this.notebook_path)[0];
1754 this.notebook_name = name;
1774 this.notebook_name = name;
1755 this.notebook_path = utils.url_path_join(parent, name);
1775 this.notebook_path = utils.url_path_join(parent, name);
1756 };
1776 };
1757
1777
1758 /**
1778 /**
1759 * Check that a notebook's name is valid.
1779 * Check that a notebook's name is valid.
1760 *
1780 *
1761 * @method test_notebook_name
1781 * @method test_notebook_name
1762 * @param {String} nbname A name for this notebook
1782 * @param {String} nbname A name for this notebook
1763 * @return {Boolean} True if the name is valid, false if invalid
1783 * @return {Boolean} True if the name is valid, false if invalid
1764 */
1784 */
1765 Notebook.prototype.test_notebook_name = function (nbname) {
1785 Notebook.prototype.test_notebook_name = function (nbname) {
1766 nbname = nbname || '';
1786 nbname = nbname || '';
1767 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1787 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1768 return true;
1788 return true;
1769 } else {
1789 } else {
1770 return false;
1790 return false;
1771 }
1791 }
1772 };
1792 };
1773
1793
1774 /**
1794 /**
1775 * Load a notebook from JSON (.ipynb).
1795 * Load a notebook from JSON (.ipynb).
1776 *
1796 *
1777 * @method fromJSON
1797 * @method fromJSON
1778 * @param {Object} data JSON representation of a notebook
1798 * @param {Object} data JSON representation of a notebook
1779 */
1799 */
1780 Notebook.prototype.fromJSON = function (data) {
1800 Notebook.prototype.fromJSON = function (data) {
1781
1801
1782 var content = data.content;
1802 var content = data.content;
1783 var ncells = this.ncells();
1803 var ncells = this.ncells();
1784 var i;
1804 var i;
1785 for (i=0; i<ncells; i++) {
1805 for (i=0; i<ncells; i++) {
1786 // Always delete cell 0 as they get renumbered as they are deleted.
1806 // Always delete cell 0 as they get renumbered as they are deleted.
1787 this.delete_cell(0);
1807 this.delete_cell(0);
1788 }
1808 }
1789 // Save the metadata and name.
1809 // Save the metadata and name.
1790 this.metadata = content.metadata;
1810 this.metadata = content.metadata;
1791 this.notebook_name = data.name;
1811 this.notebook_name = data.name;
1792 this.notebook_path = data.path;
1812 this.notebook_path = data.path;
1793 var trusted = true;
1813 var trusted = true;
1794
1814
1795 // Trigger an event changing the kernel spec - this will set the default
1815 // Trigger an event changing the kernel spec - this will set the default
1796 // codemirror mode
1816 // codemirror mode
1797 if (this.metadata.kernelspec !== undefined) {
1817 if (this.metadata.kernelspec !== undefined) {
1798 this.events.trigger('spec_changed.Kernel', this.metadata.kernelspec);
1818 this.events.trigger('spec_changed.Kernel', this.metadata.kernelspec);
1799 }
1819 }
1800
1820
1801 // Set the codemirror mode from language_info metadata
1821 // Set the codemirror mode from language_info metadata
1802 if (this.metadata.language_info !== undefined) {
1822 if (this.metadata.language_info !== undefined) {
1803 var langinfo = this.metadata.language_info;
1823 var langinfo = this.metadata.language_info;
1804 // Mode 'null' should be plain, unhighlighted text.
1824 // Mode 'null' should be plain, unhighlighted text.
1805 var cm_mode = langinfo.codemirror_mode || langinfo.language || 'null';
1825 var cm_mode = langinfo.codemirror_mode || langinfo.language || 'null';
1806 this.set_codemirror_mode(cm_mode);
1826 this.set_codemirror_mode(cm_mode);
1807 }
1827 }
1808
1828
1809 var new_cells = content.cells;
1829 var new_cells = content.cells;
1810 ncells = new_cells.length;
1830 ncells = new_cells.length;
1811 var cell_data = null;
1831 var cell_data = null;
1812 var new_cell = null;
1832 var new_cell = null;
1813 for (i=0; i<ncells; i++) {
1833 for (i=0; i<ncells; i++) {
1814 cell_data = new_cells[i];
1834 cell_data = new_cells[i];
1815 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1835 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1816 new_cell.fromJSON(cell_data);
1836 new_cell.fromJSON(cell_data);
1817 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1837 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1818 trusted = false;
1838 trusted = false;
1819 }
1839 }
1820 }
1840 }
1821 if (trusted !== this.trusted) {
1841 if (trusted !== this.trusted) {
1822 this.trusted = trusted;
1842 this.trusted = trusted;
1823 this.events.trigger("trust_changed.Notebook", trusted);
1843 this.events.trigger("trust_changed.Notebook", trusted);
1824 }
1844 }
1825 };
1845 };
1826
1846
1827 /**
1847 /**
1828 * Dump this notebook into a JSON-friendly object.
1848 * Dump this notebook into a JSON-friendly object.
1829 *
1849 *
1830 * @method toJSON
1850 * @method toJSON
1831 * @return {Object} A JSON-friendly representation of this notebook.
1851 * @return {Object} A JSON-friendly representation of this notebook.
1832 */
1852 */
1833 Notebook.prototype.toJSON = function () {
1853 Notebook.prototype.toJSON = function () {
1834 // remove the conversion indicator, which only belongs in-memory
1854 // remove the conversion indicator, which only belongs in-memory
1835 delete this.metadata.orig_nbformat;
1855 delete this.metadata.orig_nbformat;
1836 delete this.metadata.orig_nbformat_minor;
1856 delete this.metadata.orig_nbformat_minor;
1837
1857
1838 var cells = this.get_cells();
1858 var cells = this.get_cells();
1839 var ncells = cells.length;
1859 var ncells = cells.length;
1840 var cell_array = new Array(ncells);
1860 var cell_array = new Array(ncells);
1841 var trusted = true;
1861 var trusted = true;
1842 for (var i=0; i<ncells; i++) {
1862 for (var i=0; i<ncells; i++) {
1843 var cell = cells[i];
1863 var cell = cells[i];
1844 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1864 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1845 trusted = false;
1865 trusted = false;
1846 }
1866 }
1847 cell_array[i] = cell.toJSON();
1867 cell_array[i] = cell.toJSON();
1848 }
1868 }
1849 var data = {
1869 var data = {
1850 cells: cell_array,
1870 cells: cell_array,
1851 metadata: this.metadata,
1871 metadata: this.metadata,
1852 nbformat: this.nbformat,
1872 nbformat: this.nbformat,
1853 nbformat_minor: this.nbformat_minor
1873 nbformat_minor: this.nbformat_minor
1854 };
1874 };
1855 if (trusted != this.trusted) {
1875 if (trusted != this.trusted) {
1856 this.trusted = trusted;
1876 this.trusted = trusted;
1857 this.events.trigger("trust_changed.Notebook", trusted);
1877 this.events.trigger("trust_changed.Notebook", trusted);
1858 }
1878 }
1859 return data;
1879 return data;
1860 };
1880 };
1861
1881
1862 /**
1882 /**
1863 * Start an autosave timer, for periodically saving the notebook.
1883 * Start an autosave timer, for periodically saving the notebook.
1864 *
1884 *
1865 * @method set_autosave_interval
1885 * @method set_autosave_interval
1866 * @param {Integer} interval the autosave interval in milliseconds
1886 * @param {Integer} interval the autosave interval in milliseconds
1867 */
1887 */
1868 Notebook.prototype.set_autosave_interval = function (interval) {
1888 Notebook.prototype.set_autosave_interval = function (interval) {
1869 var that = this;
1889 var that = this;
1870 // clear previous interval, so we don't get simultaneous timers
1890 // clear previous interval, so we don't get simultaneous timers
1871 if (this.autosave_timer) {
1891 if (this.autosave_timer) {
1872 clearInterval(this.autosave_timer);
1892 clearInterval(this.autosave_timer);
1873 }
1893 }
1874
1894
1875 this.autosave_interval = this.minimum_autosave_interval = interval;
1895 this.autosave_interval = this.minimum_autosave_interval = interval;
1876 if (interval) {
1896 if (interval) {
1877 this.autosave_timer = setInterval(function() {
1897 this.autosave_timer = setInterval(function() {
1878 if (that.dirty) {
1898 if (that.dirty) {
1879 that.save_notebook();
1899 that.save_notebook();
1880 }
1900 }
1881 }, interval);
1901 }, interval);
1882 this.events.trigger("autosave_enabled.Notebook", interval);
1902 this.events.trigger("autosave_enabled.Notebook", interval);
1883 } else {
1903 } else {
1884 this.autosave_timer = null;
1904 this.autosave_timer = null;
1885 this.events.trigger("autosave_disabled.Notebook");
1905 this.events.trigger("autosave_disabled.Notebook");
1886 }
1906 }
1887 };
1907 };
1888
1908
1889 /**
1909 /**
1890 * Save this notebook on the server. This becomes a notebook instance's
1910 * Save this notebook on the server. This becomes a notebook instance's
1891 * .save_notebook method *after* the entire notebook has been loaded.
1911 * .save_notebook method *after* the entire notebook has been loaded.
1892 *
1912 *
1893 * @method save_notebook
1913 * @method save_notebook
1894 */
1914 */
1895 Notebook.prototype.save_notebook = function () {
1915 Notebook.prototype.save_notebook = function () {
1896 // Create a JSON model to be sent to the server.
1916 // Create a JSON model to be sent to the server.
1897 var model = {
1917 var model = {
1898 type : "notebook",
1918 type : "notebook",
1899 content : this.toJSON()
1919 content : this.toJSON()
1900 };
1920 };
1901 // time the ajax call for autosave tuning purposes.
1921 // time the ajax call for autosave tuning purposes.
1902 var start = new Date().getTime();
1922 var start = new Date().getTime();
1903
1923
1904 var that = this;
1924 var that = this;
1905 this.contents.save(this.notebook_path, model).then(
1925 this.contents.save(this.notebook_path, model).then(
1906 $.proxy(this.save_notebook_success, this, start),
1926 $.proxy(this.save_notebook_success, this, start),
1907 function (error) {
1927 function (error) {
1908 that.events.trigger('notebook_save_failed.Notebook', error);
1928 that.events.trigger('notebook_save_failed.Notebook', error);
1909 }
1929 }
1910 );
1930 );
1911 };
1931 };
1912
1932
1913 /**
1933 /**
1914 * Success callback for saving a notebook.
1934 * Success callback for saving a notebook.
1915 *
1935 *
1916 * @method save_notebook_success
1936 * @method save_notebook_success
1917 * @param {Integer} start Time when the save request start
1937 * @param {Integer} start Time when the save request start
1918 * @param {Object} data JSON representation of a notebook
1938 * @param {Object} data JSON representation of a notebook
1919 */
1939 */
1920 Notebook.prototype.save_notebook_success = function (start, data) {
1940 Notebook.prototype.save_notebook_success = function (start, data) {
1921 this.set_dirty(false);
1941 this.set_dirty(false);
1922 if (data.message) {
1942 if (data.message) {
1923 // save succeeded, but validation failed.
1943 // save succeeded, but validation failed.
1924 var body = $("<div>");
1944 var body = $("<div>");
1925 var title = "Notebook validation failed";
1945 var title = "Notebook validation failed";
1926
1946
1927 body.append($("<p>").text(
1947 body.append($("<p>").text(
1928 "The save operation succeeded," +
1948 "The save operation succeeded," +
1929 " but the notebook does not appear to be valid." +
1949 " but the notebook does not appear to be valid." +
1930 " The validation error was:"
1950 " The validation error was:"
1931 )).append($("<div>").addClass("validation-error").append(
1951 )).append($("<div>").addClass("validation-error").append(
1932 $("<pre>").text(data.message)
1952 $("<pre>").text(data.message)
1933 ));
1953 ));
1934 dialog.modal({
1954 dialog.modal({
1935 notebook: this,
1955 notebook: this,
1936 keyboard_manager: this.keyboard_manager,
1956 keyboard_manager: this.keyboard_manager,
1937 title: title,
1957 title: title,
1938 body: body,
1958 body: body,
1939 buttons : {
1959 buttons : {
1940 OK : {
1960 OK : {
1941 "class" : "btn-primary"
1961 "class" : "btn-primary"
1942 }
1962 }
1943 }
1963 }
1944 });
1964 });
1945 }
1965 }
1946 this.events.trigger('notebook_saved.Notebook');
1966 this.events.trigger('notebook_saved.Notebook');
1947 this._update_autosave_interval(start);
1967 this._update_autosave_interval(start);
1948 if (this._checkpoint_after_save) {
1968 if (this._checkpoint_after_save) {
1949 this.create_checkpoint();
1969 this.create_checkpoint();
1950 this._checkpoint_after_save = false;
1970 this._checkpoint_after_save = false;
1951 }
1971 }
1952 };
1972 };
1953
1973
1954 /**
1974 /**
1955 * update the autosave interval based on how long the last save took
1975 * update the autosave interval based on how long the last save took
1956 *
1976 *
1957 * @method _update_autosave_interval
1977 * @method _update_autosave_interval
1958 * @param {Integer} timestamp when the save request started
1978 * @param {Integer} timestamp when the save request started
1959 */
1979 */
1960 Notebook.prototype._update_autosave_interval = function (start) {
1980 Notebook.prototype._update_autosave_interval = function (start) {
1961 var duration = (new Date().getTime() - start);
1981 var duration = (new Date().getTime() - start);
1962 if (this.autosave_interval) {
1982 if (this.autosave_interval) {
1963 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1983 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1964 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1984 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1965 // round to 10 seconds, otherwise we will be setting a new interval too often
1985 // round to 10 seconds, otherwise we will be setting a new interval too often
1966 interval = 10000 * Math.round(interval / 10000);
1986 interval = 10000 * Math.round(interval / 10000);
1967 // set new interval, if it's changed
1987 // set new interval, if it's changed
1968 if (interval != this.autosave_interval) {
1988 if (interval != this.autosave_interval) {
1969 this.set_autosave_interval(interval);
1989 this.set_autosave_interval(interval);
1970 }
1990 }
1971 }
1991 }
1972 };
1992 };
1973
1993
1974 /**
1994 /**
1975 * Explicitly trust the output of this notebook.
1995 * Explicitly trust the output of this notebook.
1976 *
1996 *
1977 * @method trust_notebook
1997 * @method trust_notebook
1978 */
1998 */
1979 Notebook.prototype.trust_notebook = function () {
1999 Notebook.prototype.trust_notebook = function () {
1980 var body = $("<div>").append($("<p>")
2000 var body = $("<div>").append($("<p>")
1981 .text("A trusted IPython notebook may execute hidden malicious code ")
2001 .text("A trusted IPython notebook may execute hidden malicious code ")
1982 .append($("<strong>")
2002 .append($("<strong>")
1983 .append(
2003 .append(
1984 $("<em>").text("when you open it")
2004 $("<em>").text("when you open it")
1985 )
2005 )
1986 ).append(".").append(
2006 ).append(".").append(
1987 " Selecting trust will immediately reload this notebook in a trusted state."
2007 " Selecting trust will immediately reload this notebook in a trusted state."
1988 ).append(
2008 ).append(
1989 " For more information, see the "
2009 " For more information, see the "
1990 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
2010 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
1991 .text("IPython security documentation")
2011 .text("IPython security documentation")
1992 ).append(".")
2012 ).append(".")
1993 );
2013 );
1994
2014
1995 var nb = this;
2015 var nb = this;
1996 dialog.modal({
2016 dialog.modal({
1997 notebook: this,
2017 notebook: this,
1998 keyboard_manager: this.keyboard_manager,
2018 keyboard_manager: this.keyboard_manager,
1999 title: "Trust this notebook?",
2019 title: "Trust this notebook?",
2000 body: body,
2020 body: body,
2001
2021
2002 buttons: {
2022 buttons: {
2003 Cancel : {},
2023 Cancel : {},
2004 Trust : {
2024 Trust : {
2005 class : "btn-danger",
2025 class : "btn-danger",
2006 click : function () {
2026 click : function () {
2007 var cells = nb.get_cells();
2027 var cells = nb.get_cells();
2008 for (var i = 0; i < cells.length; i++) {
2028 for (var i = 0; i < cells.length; i++) {
2009 var cell = cells[i];
2029 var cell = cells[i];
2010 if (cell.cell_type == 'code') {
2030 if (cell.cell_type == 'code') {
2011 cell.output_area.trusted = true;
2031 cell.output_area.trusted = true;
2012 }
2032 }
2013 }
2033 }
2014 nb.events.on('notebook_saved.Notebook', function () {
2034 nb.events.on('notebook_saved.Notebook', function () {
2015 window.location.reload();
2035 window.location.reload();
2016 });
2036 });
2017 nb.save_notebook();
2037 nb.save_notebook();
2018 }
2038 }
2019 }
2039 }
2020 }
2040 }
2021 });
2041 });
2022 };
2042 };
2023
2043
2024 Notebook.prototype.copy_notebook = function(){
2044 Notebook.prototype.copy_notebook = function(){
2025 var base_url = this.base_url;
2045 var base_url = this.base_url;
2026 var w = window.open();
2046 var w = window.open();
2027 var parent = utils.url_path_split(this.notebook_path)[0];
2047 var parent = utils.url_path_split(this.notebook_path)[0];
2028 this.contents.copy(this.notebook_path, parent).then(
2048 this.contents.copy(this.notebook_path, parent).then(
2029 function (data) {
2049 function (data) {
2030 w.location = utils.url_join_encode(
2050 w.location = utils.url_join_encode(
2031 base_url, 'notebooks', data.path
2051 base_url, 'notebooks', data.path
2032 );
2052 );
2033 },
2053 },
2034 function(error) {
2054 function(error) {
2035 w.close();
2055 w.close();
2036 console.log(error);
2056 console.log(error);
2037 }
2057 }
2038 );
2058 );
2039 };
2059 };
2040
2060
2041 Notebook.prototype.rename = function (new_name) {
2061 Notebook.prototype.rename = function (new_name) {
2042 if (!new_name.match(/\.ipynb$/)) {
2062 if (!new_name.match(/\.ipynb$/)) {
2043 new_name = new_name + ".ipynb";
2063 new_name = new_name + ".ipynb";
2044 }
2064 }
2045
2065
2046 var that = this;
2066 var that = this;
2047 var parent = utils.url_path_split(this.notebook_path)[0];
2067 var parent = utils.url_path_split(this.notebook_path)[0];
2048 var new_path = utils.url_path_join(parent, new_name);
2068 var new_path = utils.url_path_join(parent, new_name);
2049 this.contents.rename(this.notebook_path, new_path).then(
2069 this.contents.rename(this.notebook_path, new_path).then(
2050 function (json) {
2070 function (json) {
2051 that.notebook_name = json.name;
2071 that.notebook_name = json.name;
2052 that.notebook_path = json.path;
2072 that.notebook_path = json.path;
2053 that.session.rename_notebook(json.path);
2073 that.session.rename_notebook(json.path);
2054 that.events.trigger('notebook_renamed.Notebook', json);
2074 that.events.trigger('notebook_renamed.Notebook', json);
2055 },
2075 },
2056 $.proxy(this.rename_error, this)
2076 $.proxy(this.rename_error, this)
2057 );
2077 );
2058 };
2078 };
2059
2079
2060 Notebook.prototype.delete = function () {
2080 Notebook.prototype.delete = function () {
2061 this.contents.delete(this.notebook_path);
2081 this.contents.delete(this.notebook_path);
2062 };
2082 };
2063
2083
2064 Notebook.prototype.rename_error = function (error) {
2084 Notebook.prototype.rename_error = function (error) {
2065 var that = this;
2085 var that = this;
2066 var dialog_body = $('<div/>').append(
2086 var dialog_body = $('<div/>').append(
2067 $("<p/>").text('This notebook name already exists.')
2087 $("<p/>").text('This notebook name already exists.')
2068 );
2088 );
2069 this.events.trigger('notebook_rename_failed.Notebook', error);
2089 this.events.trigger('notebook_rename_failed.Notebook', error);
2070 dialog.modal({
2090 dialog.modal({
2071 notebook: this,
2091 notebook: this,
2072 keyboard_manager: this.keyboard_manager,
2092 keyboard_manager: this.keyboard_manager,
2073 title: "Notebook Rename Error!",
2093 title: "Notebook Rename Error!",
2074 body: dialog_body,
2094 body: dialog_body,
2075 buttons : {
2095 buttons : {
2076 "Cancel": {},
2096 "Cancel": {},
2077 "OK": {
2097 "OK": {
2078 class: "btn-primary",
2098 class: "btn-primary",
2079 click: function () {
2099 click: function () {
2080 that.save_widget.rename_notebook({notebook:that});
2100 that.save_widget.rename_notebook({notebook:that});
2081 }}
2101 }}
2082 },
2102 },
2083 open : function (event, ui) {
2103 open : function (event, ui) {
2084 var that = $(this);
2104 var that = $(this);
2085 // Upon ENTER, click the OK button.
2105 // Upon ENTER, click the OK button.
2086 that.find('input[type="text"]').keydown(function (event, ui) {
2106 that.find('input[type="text"]').keydown(function (event, ui) {
2087 if (event.which === this.keyboard.keycodes.enter) {
2107 if (event.which === this.keyboard.keycodes.enter) {
2088 that.find('.btn-primary').first().click();
2108 that.find('.btn-primary').first().click();
2089 }
2109 }
2090 });
2110 });
2091 that.find('input[type="text"]').focus();
2111 that.find('input[type="text"]').focus();
2092 }
2112 }
2093 });
2113 });
2094 };
2114 };
2095
2115
2096 /**
2116 /**
2097 * Request a notebook's data from the server.
2117 * Request a notebook's data from the server.
2098 *
2118 *
2099 * @method load_notebook
2119 * @method load_notebook
2100 * @param {String} notebook_path A notebook to load
2120 * @param {String} notebook_path A notebook to load
2101 */
2121 */
2102 Notebook.prototype.load_notebook = function (notebook_path) {
2122 Notebook.prototype.load_notebook = function (notebook_path) {
2103 this.notebook_path = notebook_path;
2123 this.notebook_path = notebook_path;
2104 this.notebook_name = utils.url_path_split(this.notebook_path)[1];
2124 this.notebook_name = utils.url_path_split(this.notebook_path)[1];
2105 this.events.trigger('notebook_loading.Notebook');
2125 this.events.trigger('notebook_loading.Notebook');
2106 this.contents.get(notebook_path, {type: 'notebook'}).then(
2126 this.contents.get(notebook_path, {type: 'notebook'}).then(
2107 $.proxy(this.load_notebook_success, this),
2127 $.proxy(this.load_notebook_success, this),
2108 $.proxy(this.load_notebook_error, this)
2128 $.proxy(this.load_notebook_error, this)
2109 );
2129 );
2110 };
2130 };
2111
2131
2112 /**
2132 /**
2113 * Success callback for loading a notebook from the server.
2133 * Success callback for loading a notebook from the server.
2114 *
2134 *
2115 * Load notebook data from the JSON response.
2135 * Load notebook data from the JSON response.
2116 *
2136 *
2117 * @method load_notebook_success
2137 * @method load_notebook_success
2118 * @param {Object} data JSON representation of a notebook
2138 * @param {Object} data JSON representation of a notebook
2119 */
2139 */
2120 Notebook.prototype.load_notebook_success = function (data) {
2140 Notebook.prototype.load_notebook_success = function (data) {
2121 var failed, msg;
2141 var failed, msg;
2122 try {
2142 try {
2123 this.fromJSON(data);
2143 this.fromJSON(data);
2124 } catch (e) {
2144 } catch (e) {
2125 failed = e;
2145 failed = e;
2126 console.log("Notebook failed to load from JSON:", e);
2146 console.log("Notebook failed to load from JSON:", e);
2127 }
2147 }
2128 if (failed || data.message) {
2148 if (failed || data.message) {
2129 // *either* fromJSON failed or validation failed
2149 // *either* fromJSON failed or validation failed
2130 var body = $("<div>");
2150 var body = $("<div>");
2131 var title;
2151 var title;
2132 if (failed) {
2152 if (failed) {
2133 title = "Notebook failed to load";
2153 title = "Notebook failed to load";
2134 body.append($("<p>").text(
2154 body.append($("<p>").text(
2135 "The error was: "
2155 "The error was: "
2136 )).append($("<div>").addClass("js-error").text(
2156 )).append($("<div>").addClass("js-error").text(
2137 failed.toString()
2157 failed.toString()
2138 )).append($("<p>").text(
2158 )).append($("<p>").text(
2139 "See the error console for details."
2159 "See the error console for details."
2140 ));
2160 ));
2141 } else {
2161 } else {
2142 title = "Notebook validation failed";
2162 title = "Notebook validation failed";
2143 }
2163 }
2144
2164
2145 if (data.message) {
2165 if (data.message) {
2146 if (failed) {
2166 if (failed) {
2147 msg = "The notebook also failed validation:";
2167 msg = "The notebook also failed validation:";
2148 } else {
2168 } else {
2149 msg = "An invalid notebook may not function properly." +
2169 msg = "An invalid notebook may not function properly." +
2150 " The validation error was:";
2170 " The validation error was:";
2151 }
2171 }
2152 body.append($("<p>").text(
2172 body.append($("<p>").text(
2153 msg
2173 msg
2154 )).append($("<div>").addClass("validation-error").append(
2174 )).append($("<div>").addClass("validation-error").append(
2155 $("<pre>").text(data.message)
2175 $("<pre>").text(data.message)
2156 ));
2176 ));
2157 }
2177 }
2158
2178
2159 dialog.modal({
2179 dialog.modal({
2160 notebook: this,
2180 notebook: this,
2161 keyboard_manager: this.keyboard_manager,
2181 keyboard_manager: this.keyboard_manager,
2162 title: title,
2182 title: title,
2163 body: body,
2183 body: body,
2164 buttons : {
2184 buttons : {
2165 OK : {
2185 OK : {
2166 "class" : "btn-primary"
2186 "class" : "btn-primary"
2167 }
2187 }
2168 }
2188 }
2169 });
2189 });
2170 }
2190 }
2171 if (this.ncells() === 0) {
2191 if (this.ncells() === 0) {
2172 this.insert_cell_below('code');
2192 this.insert_cell_below('code');
2173 this.edit_mode(0);
2193 this.edit_mode(0);
2174 } else {
2194 } else {
2175 this.select(0);
2195 this.select(0);
2176 this.handle_command_mode(this.get_cell(0));
2196 this.handle_command_mode(this.get_cell(0));
2177 }
2197 }
2178 this.set_dirty(false);
2198 this.set_dirty(false);
2179 this.scroll_to_top();
2199 this.scroll_to_top();
2180 var nbmodel = data.content;
2200 var nbmodel = data.content;
2181 var orig_nbformat = nbmodel.metadata.orig_nbformat;
2201 var orig_nbformat = nbmodel.metadata.orig_nbformat;
2182 var orig_nbformat_minor = nbmodel.metadata.orig_nbformat_minor;
2202 var orig_nbformat_minor = nbmodel.metadata.orig_nbformat_minor;
2183 if (orig_nbformat !== undefined && nbmodel.nbformat !== orig_nbformat) {
2203 if (orig_nbformat !== undefined && nbmodel.nbformat !== orig_nbformat) {
2184 var src;
2204 var src;
2185 if (nbmodel.nbformat > orig_nbformat) {
2205 if (nbmodel.nbformat > orig_nbformat) {
2186 src = " an older notebook format ";
2206 src = " an older notebook format ";
2187 } else {
2207 } else {
2188 src = " a newer notebook format ";
2208 src = " a newer notebook format ";
2189 }
2209 }
2190
2210
2191 msg = "This notebook has been converted from" + src +
2211 msg = "This notebook has been converted from" + src +
2192 "(v"+orig_nbformat+") to the current notebook " +
2212 "(v"+orig_nbformat+") to the current notebook " +
2193 "format (v"+nbmodel.nbformat+"). The next time you save this notebook, the " +
2213 "format (v"+nbmodel.nbformat+"). The next time you save this notebook, the " +
2194 "current notebook format will be used.";
2214 "current notebook format will be used.";
2195
2215
2196 if (nbmodel.nbformat > orig_nbformat) {
2216 if (nbmodel.nbformat > orig_nbformat) {
2197 msg += " Older versions of IPython may not be able to read the new format.";
2217 msg += " Older versions of IPython may not be able to read the new format.";
2198 } else {
2218 } else {
2199 msg += " Some features of the original notebook may not be available.";
2219 msg += " Some features of the original notebook may not be available.";
2200 }
2220 }
2201 msg += " To preserve the original version, close the " +
2221 msg += " To preserve the original version, close the " +
2202 "notebook without saving it.";
2222 "notebook without saving it.";
2203 dialog.modal({
2223 dialog.modal({
2204 notebook: this,
2224 notebook: this,
2205 keyboard_manager: this.keyboard_manager,
2225 keyboard_manager: this.keyboard_manager,
2206 title : "Notebook converted",
2226 title : "Notebook converted",
2207 body : msg,
2227 body : msg,
2208 buttons : {
2228 buttons : {
2209 OK : {
2229 OK : {
2210 class : "btn-primary"
2230 class : "btn-primary"
2211 }
2231 }
2212 }
2232 }
2213 });
2233 });
2214 } else if (orig_nbformat_minor !== undefined && nbmodel.nbformat_minor < orig_nbformat_minor) {
2234 } else if (orig_nbformat_minor !== undefined && nbmodel.nbformat_minor < orig_nbformat_minor) {
2215 var that = this;
2235 var that = this;
2216 var orig_vs = 'v' + nbmodel.nbformat + '.' + orig_nbformat_minor;
2236 var orig_vs = 'v' + nbmodel.nbformat + '.' + orig_nbformat_minor;
2217 var this_vs = 'v' + nbmodel.nbformat + '.' + this.nbformat_minor;
2237 var this_vs = 'v' + nbmodel.nbformat + '.' + this.nbformat_minor;
2218 msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
2238 msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
2219 this_vs + ". You can still work with this notebook, but some features " +
2239 this_vs + ". You can still work with this notebook, but some features " +
2220 "introduced in later notebook versions may not be available.";
2240 "introduced in later notebook versions may not be available.";
2221
2241
2222 dialog.modal({
2242 dialog.modal({
2223 notebook: this,
2243 notebook: this,
2224 keyboard_manager: this.keyboard_manager,
2244 keyboard_manager: this.keyboard_manager,
2225 title : "Newer Notebook",
2245 title : "Newer Notebook",
2226 body : msg,
2246 body : msg,
2227 buttons : {
2247 buttons : {
2228 OK : {
2248 OK : {
2229 class : "btn-danger"
2249 class : "btn-danger"
2230 }
2250 }
2231 }
2251 }
2232 });
2252 });
2233
2253
2234 }
2254 }
2235
2255
2236 // Create the session after the notebook is completely loaded to prevent
2256 // Create the session after the notebook is completely loaded to prevent
2237 // code execution upon loading, which is a security risk.
2257 // code execution upon loading, which is a security risk.
2238 if (this.session === null) {
2258 if (this.session === null) {
2239 var kernelspec = this.metadata.kernelspec || {};
2259 var kernelspec = this.metadata.kernelspec || {};
2240 var kernel_name = kernelspec.name;
2260 var kernel_name = kernelspec.name;
2241
2261
2242 this.start_session(kernel_name);
2262 this.start_session(kernel_name);
2243 }
2263 }
2244 // load our checkpoint list
2264 // load our checkpoint list
2245 this.list_checkpoints();
2265 this.list_checkpoints();
2246
2266
2247 // load toolbar state
2267 // load toolbar state
2248 if (this.metadata.celltoolbar) {
2268 if (this.metadata.celltoolbar) {
2249 celltoolbar.CellToolbar.global_show();
2269 celltoolbar.CellToolbar.global_show();
2250 celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar);
2270 celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar);
2251 } else {
2271 } else {
2252 celltoolbar.CellToolbar.global_hide();
2272 celltoolbar.CellToolbar.global_hide();
2253 }
2273 }
2254
2274
2255 // now that we're fully loaded, it is safe to restore save functionality
2275 // now that we're fully loaded, it is safe to restore save functionality
2256 delete(this.save_notebook);
2276 delete(this.save_notebook);
2257 this.events.trigger('notebook_loaded.Notebook');
2277 this.events.trigger('notebook_loaded.Notebook');
2258 };
2278 };
2259
2279
2260 /**
2280 /**
2261 * Failure callback for loading a notebook from the server.
2281 * Failure callback for loading a notebook from the server.
2262 *
2282 *
2263 * @method load_notebook_error
2283 * @method load_notebook_error
2264 * @param {Error} error
2284 * @param {Error} error
2265 */
2285 */
2266 Notebook.prototype.load_notebook_error = function (error) {
2286 Notebook.prototype.load_notebook_error = function (error) {
2267 this.events.trigger('notebook_load_failed.Notebook', error);
2287 this.events.trigger('notebook_load_failed.Notebook', error);
2268 var msg;
2288 var msg;
2269 if (error.name === utils.XHR_ERROR && error.xhr.status === 500) {
2289 if (error.name === utils.XHR_ERROR && error.xhr.status === 500) {
2270 utils.log_ajax_error(error.xhr, error.xhr_status, error.xhr_error);
2290 utils.log_ajax_error(error.xhr, error.xhr_status, error.xhr_error);
2271 msg = "An unknown error occurred while loading this notebook. " +
2291 msg = "An unknown error occurred while loading this notebook. " +
2272 "This version can load notebook formats " +
2292 "This version can load notebook formats " +
2273 "v" + this.nbformat + " or earlier. See the server log for details.";
2293 "v" + this.nbformat + " or earlier. See the server log for details.";
2274 } else {
2294 } else {
2275 msg = error.message;
2295 msg = error.message;
2276 }
2296 }
2277 dialog.modal({
2297 dialog.modal({
2278 notebook: this,
2298 notebook: this,
2279 keyboard_manager: this.keyboard_manager,
2299 keyboard_manager: this.keyboard_manager,
2280 title: "Error loading notebook",
2300 title: "Error loading notebook",
2281 body : msg,
2301 body : msg,
2282 buttons : {
2302 buttons : {
2283 "OK": {}
2303 "OK": {}
2284 }
2304 }
2285 });
2305 });
2286 };
2306 };
2287
2307
2288 /********************* checkpoint-related *********************/
2308 /********************* checkpoint-related *********************/
2289
2309
2290 /**
2310 /**
2291 * Save the notebook then immediately create a checkpoint.
2311 * Save the notebook then immediately create a checkpoint.
2292 *
2312 *
2293 * @method save_checkpoint
2313 * @method save_checkpoint
2294 */
2314 */
2295 Notebook.prototype.save_checkpoint = function () {
2315 Notebook.prototype.save_checkpoint = function () {
2296 this._checkpoint_after_save = true;
2316 this._checkpoint_after_save = true;
2297 this.save_notebook();
2317 this.save_notebook();
2298 };
2318 };
2299
2319
2300 /**
2320 /**
2301 * Add a checkpoint for this notebook.
2321 * Add a checkpoint for this notebook.
2302 * for use as a callback from checkpoint creation.
2322 * for use as a callback from checkpoint creation.
2303 *
2323 *
2304 * @method add_checkpoint
2324 * @method add_checkpoint
2305 */
2325 */
2306 Notebook.prototype.add_checkpoint = function (checkpoint) {
2326 Notebook.prototype.add_checkpoint = function (checkpoint) {
2307 var found = false;
2327 var found = false;
2308 for (var i = 0; i < this.checkpoints.length; i++) {
2328 for (var i = 0; i < this.checkpoints.length; i++) {
2309 var existing = this.checkpoints[i];
2329 var existing = this.checkpoints[i];
2310 if (existing.id == checkpoint.id) {
2330 if (existing.id == checkpoint.id) {
2311 found = true;
2331 found = true;
2312 this.checkpoints[i] = checkpoint;
2332 this.checkpoints[i] = checkpoint;
2313 break;
2333 break;
2314 }
2334 }
2315 }
2335 }
2316 if (!found) {
2336 if (!found) {
2317 this.checkpoints.push(checkpoint);
2337 this.checkpoints.push(checkpoint);
2318 }
2338 }
2319 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2339 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2320 };
2340 };
2321
2341
2322 /**
2342 /**
2323 * List checkpoints for this notebook.
2343 * List checkpoints for this notebook.
2324 *
2344 *
2325 * @method list_checkpoints
2345 * @method list_checkpoints
2326 */
2346 */
2327 Notebook.prototype.list_checkpoints = function () {
2347 Notebook.prototype.list_checkpoints = function () {
2328 var that = this;
2348 var that = this;
2329 this.contents.list_checkpoints(this.notebook_path).then(
2349 this.contents.list_checkpoints(this.notebook_path).then(
2330 $.proxy(this.list_checkpoints_success, this),
2350 $.proxy(this.list_checkpoints_success, this),
2331 function(error) {
2351 function(error) {
2332 that.events.trigger('list_checkpoints_failed.Notebook', error);
2352 that.events.trigger('list_checkpoints_failed.Notebook', error);
2333 }
2353 }
2334 );
2354 );
2335 };
2355 };
2336
2356
2337 /**
2357 /**
2338 * Success callback for listing checkpoints.
2358 * Success callback for listing checkpoints.
2339 *
2359 *
2340 * @method list_checkpoint_success
2360 * @method list_checkpoint_success
2341 * @param {Object} data JSON representation of a checkpoint
2361 * @param {Object} data JSON representation of a checkpoint
2342 */
2362 */
2343 Notebook.prototype.list_checkpoints_success = function (data) {
2363 Notebook.prototype.list_checkpoints_success = function (data) {
2344 this.checkpoints = data;
2364 this.checkpoints = data;
2345 if (data.length) {
2365 if (data.length) {
2346 this.last_checkpoint = data[data.length - 1];
2366 this.last_checkpoint = data[data.length - 1];
2347 } else {
2367 } else {
2348 this.last_checkpoint = null;
2368 this.last_checkpoint = null;
2349 }
2369 }
2350 this.events.trigger('checkpoints_listed.Notebook', [data]);
2370 this.events.trigger('checkpoints_listed.Notebook', [data]);
2351 };
2371 };
2352
2372
2353 /**
2373 /**
2354 * Create a checkpoint of this notebook on the server from the most recent save.
2374 * Create a checkpoint of this notebook on the server from the most recent save.
2355 *
2375 *
2356 * @method create_checkpoint
2376 * @method create_checkpoint
2357 */
2377 */
2358 Notebook.prototype.create_checkpoint = function () {
2378 Notebook.prototype.create_checkpoint = function () {
2359 var that = this;
2379 var that = this;
2360 this.contents.create_checkpoint(this.notebook_path).then(
2380 this.contents.create_checkpoint(this.notebook_path).then(
2361 $.proxy(this.create_checkpoint_success, this),
2381 $.proxy(this.create_checkpoint_success, this),
2362 function (error) {
2382 function (error) {
2363 that.events.trigger('checkpoint_failed.Notebook', error);
2383 that.events.trigger('checkpoint_failed.Notebook', error);
2364 }
2384 }
2365 );
2385 );
2366 };
2386 };
2367
2387
2368 /**
2388 /**
2369 * Success callback for creating a checkpoint.
2389 * Success callback for creating a checkpoint.
2370 *
2390 *
2371 * @method create_checkpoint_success
2391 * @method create_checkpoint_success
2372 * @param {Object} data JSON representation of a checkpoint
2392 * @param {Object} data JSON representation of a checkpoint
2373 */
2393 */
2374 Notebook.prototype.create_checkpoint_success = function (data) {
2394 Notebook.prototype.create_checkpoint_success = function (data) {
2375 this.add_checkpoint(data);
2395 this.add_checkpoint(data);
2376 this.events.trigger('checkpoint_created.Notebook', data);
2396 this.events.trigger('checkpoint_created.Notebook', data);
2377 };
2397 };
2378
2398
2379 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2399 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2380 var that = this;
2400 var that = this;
2381 checkpoint = checkpoint || this.last_checkpoint;
2401 checkpoint = checkpoint || this.last_checkpoint;
2382 if ( ! checkpoint ) {
2402 if ( ! checkpoint ) {
2383 console.log("restore dialog, but no checkpoint to restore to!");
2403 console.log("restore dialog, but no checkpoint to restore to!");
2384 return;
2404 return;
2385 }
2405 }
2386 var body = $('<div/>').append(
2406 var body = $('<div/>').append(
2387 $('<p/>').addClass("p-space").text(
2407 $('<p/>').addClass("p-space").text(
2388 "Are you sure you want to revert the notebook to " +
2408 "Are you sure you want to revert the notebook to " +
2389 "the latest checkpoint?"
2409 "the latest checkpoint?"
2390 ).append(
2410 ).append(
2391 $("<strong/>").text(
2411 $("<strong/>").text(
2392 " This cannot be undone."
2412 " This cannot be undone."
2393 )
2413 )
2394 )
2414 )
2395 ).append(
2415 ).append(
2396 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2416 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2397 ).append(
2417 ).append(
2398 $('<p/>').addClass("p-space").text(
2418 $('<p/>').addClass("p-space").text(
2399 Date(checkpoint.last_modified)
2419 Date(checkpoint.last_modified)
2400 ).css("text-align", "center")
2420 ).css("text-align", "center")
2401 );
2421 );
2402
2422
2403 dialog.modal({
2423 dialog.modal({
2404 notebook: this,
2424 notebook: this,
2405 keyboard_manager: this.keyboard_manager,
2425 keyboard_manager: this.keyboard_manager,
2406 title : "Revert notebook to checkpoint",
2426 title : "Revert notebook to checkpoint",
2407 body : body,
2427 body : body,
2408 buttons : {
2428 buttons : {
2409 Revert : {
2429 Revert : {
2410 class : "btn-danger",
2430 class : "btn-danger",
2411 click : function () {
2431 click : function () {
2412 that.restore_checkpoint(checkpoint.id);
2432 that.restore_checkpoint(checkpoint.id);
2413 }
2433 }
2414 },
2434 },
2415 Cancel : {}
2435 Cancel : {}
2416 }
2436 }
2417 });
2437 });
2418 };
2438 };
2419
2439
2420 /**
2440 /**
2421 * Restore the notebook to a checkpoint state.
2441 * Restore the notebook to a checkpoint state.
2422 *
2442 *
2423 * @method restore_checkpoint
2443 * @method restore_checkpoint
2424 * @param {String} checkpoint ID
2444 * @param {String} checkpoint ID
2425 */
2445 */
2426 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2446 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2427 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2447 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2428 var that = this;
2448 var that = this;
2429 this.contents.restore_checkpoint(this.notebook_path, checkpoint).then(
2449 this.contents.restore_checkpoint(this.notebook_path, checkpoint).then(
2430 $.proxy(this.restore_checkpoint_success, this),
2450 $.proxy(this.restore_checkpoint_success, this),
2431 function (error) {
2451 function (error) {
2432 that.events.trigger('checkpoint_restore_failed.Notebook', error);
2452 that.events.trigger('checkpoint_restore_failed.Notebook', error);
2433 }
2453 }
2434 );
2454 );
2435 };
2455 };
2436
2456
2437 /**
2457 /**
2438 * Success callback for restoring a notebook to a checkpoint.
2458 * Success callback for restoring a notebook to a checkpoint.
2439 *
2459 *
2440 * @method restore_checkpoint_success
2460 * @method restore_checkpoint_success
2441 */
2461 */
2442 Notebook.prototype.restore_checkpoint_success = function () {
2462 Notebook.prototype.restore_checkpoint_success = function () {
2443 this.events.trigger('checkpoint_restored.Notebook');
2463 this.events.trigger('checkpoint_restored.Notebook');
2444 this.load_notebook(this.notebook_path);
2464 this.load_notebook(this.notebook_path);
2445 };
2465 };
2446
2466
2447 /**
2467 /**
2448 * Delete a notebook checkpoint.
2468 * Delete a notebook checkpoint.
2449 *
2469 *
2450 * @method delete_checkpoint
2470 * @method delete_checkpoint
2451 * @param {String} checkpoint ID
2471 * @param {String} checkpoint ID
2452 */
2472 */
2453 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2473 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2454 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2474 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2455 var that = this;
2475 var that = this;
2456 this.contents.delete_checkpoint(this.notebook_path, checkpoint).then(
2476 this.contents.delete_checkpoint(this.notebook_path, checkpoint).then(
2457 $.proxy(this.delete_checkpoint_success, this),
2477 $.proxy(this.delete_checkpoint_success, this),
2458 function (error) {
2478 function (error) {
2459 that.events.trigger('checkpoint_delete_failed.Notebook', error);
2479 that.events.trigger('checkpoint_delete_failed.Notebook', error);
2460 }
2480 }
2461 );
2481 );
2462 };
2482 };
2463
2483
2464 /**
2484 /**
2465 * Success callback for deleting a notebook checkpoint
2485 * Success callback for deleting a notebook checkpoint
2466 *
2486 *
2467 * @method delete_checkpoint_success
2487 * @method delete_checkpoint_success
2468 */
2488 */
2469 Notebook.prototype.delete_checkpoint_success = function () {
2489 Notebook.prototype.delete_checkpoint_success = function () {
2470 this.events.trigger('checkpoint_deleted.Notebook');
2490 this.events.trigger('checkpoint_deleted.Notebook');
2471 this.load_notebook(this.notebook_path);
2491 this.load_notebook(this.notebook_path);
2472 };
2492 };
2473
2493
2474
2494
2475 // For backwards compatability.
2495 // For backwards compatability.
2476 IPython.Notebook = Notebook;
2496 IPython.Notebook = Notebook;
2477
2497
2478 return {'Notebook': Notebook};
2498 return {'Notebook': Notebook};
2479 });
2499 });
@@ -1,931 +1,932
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 'jqueryui',
6 'jqueryui',
7 'base/js/utils',
7 'base/js/utils',
8 'base/js/security',
8 'base/js/security',
9 'base/js/keyboard',
9 'base/js/keyboard',
10 'notebook/js/mathjaxutils',
10 'notebook/js/mathjaxutils',
11 'components/marked/lib/marked',
11 'components/marked/lib/marked',
12 ], function(IPython, $, utils, security, keyboard, mathjaxutils, marked) {
12 ], function(IPython, $, utils, security, keyboard, mathjaxutils, marked) {
13 "use strict";
13 "use strict";
14
14
15 /**
15 /**
16 * @class OutputArea
16 * @class OutputArea
17 *
17 *
18 * @constructor
18 * @constructor
19 */
19 */
20
20
21 var OutputArea = function (options) {
21 var OutputArea = function (options) {
22 this.selector = options.selector;
22 this.selector = options.selector;
23 this.events = options.events;
23 this.events = options.events;
24 this.keyboard_manager = options.keyboard_manager;
24 this.keyboard_manager = options.keyboard_manager;
25 this.wrapper = $(options.selector);
25 this.wrapper = $(options.selector);
26 this.outputs = [];
26 this.outputs = [];
27 this.collapsed = false;
27 this.collapsed = false;
28 this.scrolled = false;
28 this.scrolled = false;
29 this.trusted = true;
29 this.trusted = true;
30 this.clear_queued = null;
30 this.clear_queued = null;
31 if (options.prompt_area === undefined) {
31 if (options.prompt_area === undefined) {
32 this.prompt_area = true;
32 this.prompt_area = true;
33 } else {
33 } else {
34 this.prompt_area = options.prompt_area;
34 this.prompt_area = options.prompt_area;
35 }
35 }
36 this.create_elements();
36 this.create_elements();
37 this.style();
37 this.style();
38 this.bind_events();
38 this.bind_events();
39 };
39 };
40
40
41
41
42 /**
42 /**
43 * Class prototypes
43 * Class prototypes
44 **/
44 **/
45
45
46 OutputArea.prototype.create_elements = function () {
46 OutputArea.prototype.create_elements = function () {
47 this.element = $("<div/>");
47 this.element = $("<div/>");
48 this.collapse_button = $("<div/>");
48 this.collapse_button = $("<div/>");
49 this.prompt_overlay = $("<div/>");
49 this.prompt_overlay = $("<div/>");
50 this.wrapper.append(this.prompt_overlay);
50 this.wrapper.append(this.prompt_overlay);
51 this.wrapper.append(this.element);
51 this.wrapper.append(this.element);
52 this.wrapper.append(this.collapse_button);
52 this.wrapper.append(this.collapse_button);
53 };
53 };
54
54
55
55
56 OutputArea.prototype.style = function () {
56 OutputArea.prototype.style = function () {
57 this.collapse_button.hide();
57 this.collapse_button.hide();
58 this.prompt_overlay.hide();
58 this.prompt_overlay.hide();
59
59
60 this.wrapper.addClass('output_wrapper');
60 this.wrapper.addClass('output_wrapper');
61 this.element.addClass('output');
61 this.element.addClass('output');
62
62
63 this.collapse_button.addClass("btn btn-default output_collapsed");
63 this.collapse_button.addClass("btn btn-default output_collapsed");
64 this.collapse_button.attr('title', 'click to expand output');
64 this.collapse_button.attr('title', 'click to expand output');
65 this.collapse_button.text('. . .');
65 this.collapse_button.text('. . .');
66
66
67 this.prompt_overlay.addClass('out_prompt_overlay prompt');
67 this.prompt_overlay.addClass('out_prompt_overlay prompt');
68 this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
68 this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
69
69
70 this.collapse();
70 this.collapse();
71 };
71 };
72
72
73 /**
73 /**
74 * Should the OutputArea scroll?
74 * Should the OutputArea scroll?
75 * Returns whether the height (in lines) exceeds a threshold.
75 * Returns whether the height (in lines) exceeds a threshold.
76 *
76 *
77 * @private
77 * @private
78 * @method _should_scroll
78 * @method _should_scroll
79 * @param [lines=100]{Integer}
79 * @param [lines=100]{Integer}
80 * @return {Bool}
80 * @return {Bool}
81 *
81 *
82 */
82 */
83 OutputArea.prototype._should_scroll = function (lines) {
83 OutputArea.prototype._should_scroll = function (lines) {
84 if (lines <=0 ){ return; }
84 if (lines <=0 ){ return; }
85 if (!lines) {
85 if (!lines) {
86 lines = 100;
86 lines = 100;
87 }
87 }
88 // line-height from http://stackoverflow.com/questions/1185151
88 // line-height from http://stackoverflow.com/questions/1185151
89 var fontSize = this.element.css('font-size');
89 var fontSize = this.element.css('font-size');
90 var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5);
90 var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5);
91
91
92 return (this.element.height() > lines * lineHeight);
92 return (this.element.height() > lines * lineHeight);
93 };
93 };
94
94
95
95
96 OutputArea.prototype.bind_events = function () {
96 OutputArea.prototype.bind_events = function () {
97 var that = this;
97 var that = this;
98 this.prompt_overlay.dblclick(function () { that.toggle_output(); });
98 this.prompt_overlay.dblclick(function () { that.toggle_output(); });
99 this.prompt_overlay.click(function () { that.toggle_scroll(); });
99 this.prompt_overlay.click(function () { that.toggle_scroll(); });
100
100
101 this.element.resize(function () {
101 this.element.resize(function () {
102 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
102 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
103 if ( utils.browser[0] === "Firefox" ) {
103 if ( utils.browser[0] === "Firefox" ) {
104 return;
104 return;
105 }
105 }
106 // maybe scroll output,
106 // maybe scroll output,
107 // if it's grown large enough and hasn't already been scrolled.
107 // if it's grown large enough and hasn't already been scrolled.
108 if ( !that.scrolled && that._should_scroll(OutputArea.auto_scroll_threshold)) {
108 if ( !that.scrolled && that._should_scroll(OutputArea.auto_scroll_threshold)) {
109 that.scroll_area();
109 that.scroll_area();
110 }
110 }
111 });
111 });
112 this.collapse_button.click(function () {
112 this.collapse_button.click(function () {
113 that.expand();
113 that.expand();
114 });
114 });
115 };
115 };
116
116
117
117
118 OutputArea.prototype.collapse = function () {
118 OutputArea.prototype.collapse = function () {
119 if (!this.collapsed) {
119 if (!this.collapsed) {
120 this.element.hide();
120 this.element.hide();
121 this.prompt_overlay.hide();
121 this.prompt_overlay.hide();
122 if (this.element.html()){
122 if (this.element.html()){
123 this.collapse_button.show();
123 this.collapse_button.show();
124 }
124 }
125 this.collapsed = true;
125 this.collapsed = true;
126 }
126 }
127 };
127 };
128
128
129
129
130 OutputArea.prototype.expand = function () {
130 OutputArea.prototype.expand = function () {
131 if (this.collapsed) {
131 if (this.collapsed) {
132 this.collapse_button.hide();
132 this.collapse_button.hide();
133 this.element.show();
133 this.element.show();
134 this.prompt_overlay.show();
134 this.prompt_overlay.show();
135 this.collapsed = false;
135 this.collapsed = false;
136 }
136 }
137 };
137 };
138
138
139
139
140 OutputArea.prototype.toggle_output = function () {
140 OutputArea.prototype.toggle_output = function () {
141 if (this.collapsed) {
141 if (this.collapsed) {
142 this.expand();
142 this.expand();
143 } else {
143 } else {
144 this.collapse();
144 this.collapse();
145 }
145 }
146 };
146 };
147
147
148
148
149 OutputArea.prototype.scroll_area = function () {
149 OutputArea.prototype.scroll_area = function () {
150 this.element.addClass('output_scroll');
150 this.element.addClass('output_scroll');
151 this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
151 this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
152 this.scrolled = true;
152 this.scrolled = true;
153 };
153 };
154
154
155
155
156 OutputArea.prototype.unscroll_area = function () {
156 OutputArea.prototype.unscroll_area = function () {
157 this.element.removeClass('output_scroll');
157 this.element.removeClass('output_scroll');
158 this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
158 this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
159 this.scrolled = false;
159 this.scrolled = false;
160 };
160 };
161
161
162 /**
162 /**
163 *
163 *
164 * Scroll OutputArea if height supperior than a threshold (in lines).
164 * Scroll OutputArea if height supperior than a threshold (in lines).
165 *
165 *
166 * Threshold is a maximum number of lines. If unspecified, defaults to
166 * Threshold is a maximum number of lines. If unspecified, defaults to
167 * OutputArea.minimum_scroll_threshold.
167 * OutputArea.minimum_scroll_threshold.
168 *
168 *
169 * Negative threshold will prevent the OutputArea from ever scrolling.
169 * Negative threshold will prevent the OutputArea from ever scrolling.
170 *
170 *
171 * @method scroll_if_long
171 * @method scroll_if_long
172 *
172 *
173 * @param [lines=20]{Number} Default to 20 if not set,
173 * @param [lines=20]{Number} Default to 20 if not set,
174 * behavior undefined for value of `0`.
174 * behavior undefined for value of `0`.
175 *
175 *
176 **/
176 **/
177 OutputArea.prototype.scroll_if_long = function (lines) {
177 OutputArea.prototype.scroll_if_long = function (lines) {
178 var n = lines | OutputArea.minimum_scroll_threshold;
178 var n = lines | OutputArea.minimum_scroll_threshold;
179 if(n <= 0){
179 if(n <= 0){
180 return;
180 return;
181 }
181 }
182
182
183 if (this._should_scroll(n)) {
183 if (this._should_scroll(n)) {
184 // only allow scrolling long-enough output
184 // only allow scrolling long-enough output
185 this.scroll_area();
185 this.scroll_area();
186 }
186 }
187 };
187 };
188
188
189
189
190 OutputArea.prototype.toggle_scroll = function () {
190 OutputArea.prototype.toggle_scroll = function () {
191 if (this.scrolled) {
191 if (this.scrolled) {
192 this.unscroll_area();
192 this.unscroll_area();
193 } else {
193 } else {
194 // only allow scrolling long-enough output
194 // only allow scrolling long-enough output
195 this.scroll_if_long();
195 this.scroll_if_long();
196 }
196 }
197 };
197 };
198
198
199
199
200 // typeset with MathJax if MathJax is available
200 // typeset with MathJax if MathJax is available
201 OutputArea.prototype.typeset = function () {
201 OutputArea.prototype.typeset = function () {
202 if (window.MathJax){
202 if (window.MathJax){
203 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
203 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
204 }
204 }
205 };
205 };
206
206
207
207
208 OutputArea.prototype.handle_output = function (msg) {
208 OutputArea.prototype.handle_output = function (msg) {
209 var json = {};
209 var json = {};
210 var msg_type = json.output_type = msg.header.msg_type;
210 var msg_type = json.output_type = msg.header.msg_type;
211 var content = msg.content;
211 var content = msg.content;
212 if (msg_type === "stream") {
212 if (msg_type === "stream") {
213 json.text = content.text;
213 json.text = content.text;
214 json.name = content.name;
214 json.name = content.name;
215 } else if (msg_type === "display_data") {
215 } else if (msg_type === "display_data") {
216 json.data = content.data;
216 json.data = content.data;
217 json.output_type = msg_type;
217 json.output_type = msg_type;
218 json.metadata = content.metadata;
218 json.metadata = content.metadata;
219 } else if (msg_type === "execute_result") {
219 } else if (msg_type === "execute_result") {
220 json.data = content.data;
220 json.data = content.data;
221 json.output_type = msg_type;
221 json.output_type = msg_type;
222 json.metadata = content.metadata;
222 json.metadata = content.metadata;
223 json.execution_count = content.execution_count;
223 json.execution_count = content.execution_count;
224 } else if (msg_type === "error") {
224 } else if (msg_type === "error") {
225 json.ename = content.ename;
225 json.ename = content.ename;
226 json.evalue = content.evalue;
226 json.evalue = content.evalue;
227 json.traceback = content.traceback;
227 json.traceback = content.traceback;
228 } else {
228 } else {
229 console.log("unhandled output message", msg);
229 console.log("unhandled output message", msg);
230 return;
230 return;
231 }
231 }
232 this.append_output(json);
232 this.append_output(json);
233 };
233 };
234
234
235
235
236 OutputArea.output_types = [
236 OutputArea.output_types = [
237 'application/javascript',
237 'application/javascript',
238 'text/html',
238 'text/html',
239 'text/markdown',
239 'text/markdown',
240 'text/latex',
240 'text/latex',
241 'image/svg+xml',
241 'image/svg+xml',
242 'image/png',
242 'image/png',
243 'image/jpeg',
243 'image/jpeg',
244 'application/pdf',
244 'application/pdf',
245 'text/plain'
245 'text/plain'
246 ];
246 ];
247
247
248 OutputArea.prototype.validate_output = function (json) {
248 OutputArea.prototype.validate_output = function (json) {
249 // scrub invalid outputs
249 // scrub invalid outputs
250 var data = json.data;
250 var data = json.data;
251 $.map(OutputArea.output_types, function(key){
251 $.map(OutputArea.output_types, function(key){
252 if (key !== 'application/json' &&
252 if (key !== 'application/json' &&
253 data[key] !== undefined &&
253 data[key] !== undefined &&
254 typeof data[key] !== 'string'
254 typeof data[key] !== 'string'
255 ) {
255 ) {
256 console.log("Invalid type for " + key, data[key]);
256 console.log("Invalid type for " + key, data[key]);
257 delete data[key];
257 delete data[key];
258 }
258 }
259 });
259 });
260 return json;
260 return json;
261 };
261 };
262
262
263 OutputArea.prototype.append_output = function (json) {
263 OutputArea.prototype.append_output = function (json) {
264 this.expand();
264 this.expand();
265
265
266 // validate output data types
266 // validate output data types
267 if (json.data) {
267 if (json.data) {
268 json = this.validate_output(json);
268 json = this.validate_output(json);
269 }
269 }
270
270
271 // Clear the output if clear is queued.
271 // Clear the output if clear is queued.
272 var needs_height_reset = false;
272 var needs_height_reset = false;
273 if (this.clear_queued) {
273 if (this.clear_queued) {
274 this.clear_output(false);
274 this.clear_output(false);
275 needs_height_reset = true;
275 needs_height_reset = true;
276 }
276 }
277
277
278 var record_output = true;
278 var record_output = true;
279
279
280 if (json.output_type === 'execute_result') {
280 if (json.output_type === 'execute_result') {
281 this.append_execute_result(json);
281 this.append_execute_result(json);
282 } else if (json.output_type === 'error') {
282 } else if (json.output_type === 'error') {
283 this.append_error(json);
283 this.append_error(json);
284 } else if (json.output_type === 'stream') {
284 } else if (json.output_type === 'stream') {
285 // append_stream might have merged the output with earlier stream output
285 // append_stream might have merged the output with earlier stream output
286 record_output = this.append_stream(json);
286 record_output = this.append_stream(json);
287 }
287 }
288
288
289 // We must release the animation fixed height in a callback since Gecko
289 // We must release the animation fixed height in a callback since Gecko
290 // (FireFox) doesn't render the image immediately as the data is
290 // (FireFox) doesn't render the image immediately as the data is
291 // available.
291 // available.
292 var that = this;
292 var that = this;
293 var handle_appended = function ($el) {
293 var handle_appended = function ($el) {
294 // Only reset the height to automatic if the height is currently
294 // Only reset the height to automatic if the height is currently
295 // fixed (done by wait=True flag on clear_output).
295 // fixed (done by wait=True flag on clear_output).
296 if (needs_height_reset) {
296 if (needs_height_reset) {
297 that.element.height('');
297 that.element.height('');
298 }
298 }
299 that.element.trigger('resize');
299 that.element.trigger('resize');
300 };
300 };
301 if (json.output_type === 'display_data') {
301 if (json.output_type === 'display_data') {
302 this.append_display_data(json, handle_appended);
302 this.append_display_data(json, handle_appended);
303 } else {
303 } else {
304 handle_appended();
304 handle_appended();
305 }
305 }
306
306
307 if (record_output) {
307 if (record_output) {
308 this.outputs.push(json);
308 this.outputs.push(json);
309 }
309 }
310 };
310 };
311
311
312
312
313 OutputArea.prototype.create_output_area = function () {
313 OutputArea.prototype.create_output_area = function () {
314 var oa = $("<div/>").addClass("output_area");
314 var oa = $("<div/>").addClass("output_area");
315 if (this.prompt_area) {
315 if (this.prompt_area) {
316 oa.append($('<div/>').addClass('prompt'));
316 oa.append($('<div/>').addClass('prompt'));
317 }
317 }
318 return oa;
318 return oa;
319 };
319 };
320
320
321
321
322 function _get_metadata_key(metadata, key, mime) {
322 function _get_metadata_key(metadata, key, mime) {
323 var mime_md = metadata[mime];
323 var mime_md = metadata[mime];
324 // mime-specific higher priority
324 // mime-specific higher priority
325 if (mime_md && mime_md[key] !== undefined) {
325 if (mime_md && mime_md[key] !== undefined) {
326 return mime_md[key];
326 return mime_md[key];
327 }
327 }
328 // fallback on global
328 // fallback on global
329 return metadata[key];
329 return metadata[key];
330 }
330 }
331
331
332 OutputArea.prototype.create_output_subarea = function(md, classes, mime) {
332 OutputArea.prototype.create_output_subarea = function(md, classes, mime) {
333 var subarea = $('<div/>').addClass('output_subarea').addClass(classes);
333 var subarea = $('<div/>').addClass('output_subarea').addClass(classes);
334 if (_get_metadata_key(md, 'isolated', mime)) {
334 if (_get_metadata_key(md, 'isolated', mime)) {
335 // Create an iframe to isolate the subarea from the rest of the
335 // Create an iframe to isolate the subarea from the rest of the
336 // document
336 // document
337 var iframe = $('<iframe/>').addClass('box-flex1');
337 var iframe = $('<iframe/>').addClass('box-flex1');
338 iframe.css({'height':1, 'width':'100%', 'display':'block'});
338 iframe.css({'height':1, 'width':'100%', 'display':'block'});
339 iframe.attr('frameborder', 0);
339 iframe.attr('frameborder', 0);
340 iframe.attr('scrolling', 'auto');
340 iframe.attr('scrolling', 'auto');
341
341
342 // Once the iframe is loaded, the subarea is dynamically inserted
342 // Once the iframe is loaded, the subarea is dynamically inserted
343 iframe.on('load', function() {
343 iframe.on('load', function() {
344 // Workaround needed by Firefox, to properly render svg inside
344 // Workaround needed by Firefox, to properly render svg inside
345 // iframes, see http://stackoverflow.com/questions/10177190/
345 // iframes, see http://stackoverflow.com/questions/10177190/
346 // svg-dynamically-added-to-iframe-does-not-render-correctly
346 // svg-dynamically-added-to-iframe-does-not-render-correctly
347 this.contentDocument.open();
347 this.contentDocument.open();
348
348
349 // Insert the subarea into the iframe
349 // Insert the subarea into the iframe
350 // We must directly write the html. When using Jquery's append
350 // We must directly write the html. When using Jquery's append
351 // method, javascript is evaluated in the parent document and
351 // method, javascript is evaluated in the parent document and
352 // not in the iframe document. At this point, subarea doesn't
352 // not in the iframe document. At this point, subarea doesn't
353 // contain any user content.
353 // contain any user content.
354 this.contentDocument.write(subarea.html());
354 this.contentDocument.write(subarea.html());
355
355
356 this.contentDocument.close();
356 this.contentDocument.close();
357
357
358 var body = this.contentDocument.body;
358 var body = this.contentDocument.body;
359 // Adjust the iframe height automatically
359 // Adjust the iframe height automatically
360 iframe.height(body.scrollHeight + 'px');
360 iframe.height(body.scrollHeight + 'px');
361 });
361 });
362
362
363 // Elements should be appended to the inner subarea and not to the
363 // Elements should be appended to the inner subarea and not to the
364 // iframe
364 // iframe
365 iframe.append = function(that) {
365 iframe.append = function(that) {
366 subarea.append(that);
366 subarea.append(that);
367 };
367 };
368
368
369 return iframe;
369 return iframe;
370 } else {
370 } else {
371 return subarea;
371 return subarea;
372 }
372 }
373 };
373 };
374
374
375
375
376 OutputArea.prototype._append_javascript_error = function (err, element) {
376 OutputArea.prototype._append_javascript_error = function (err, element) {
377 // display a message when a javascript error occurs in display output
377 // display a message when a javascript error occurs in display output
378 var msg = "Javascript error adding output!";
378 var msg = "Javascript error adding output!";
379 if ( element === undefined ) return;
379 if ( element === undefined ) return;
380 element
380 element
381 .append($('<div/>').text(msg).addClass('js-error'))
381 .append($('<div/>').text(msg).addClass('js-error'))
382 .append($('<div/>').text(err.toString()).addClass('js-error'))
382 .append($('<div/>').text(err.toString()).addClass('js-error'))
383 .append($('<div/>').text('See your browser Javascript console for more details.').addClass('js-error'));
383 .append($('<div/>').text('See your browser Javascript console for more details.').addClass('js-error'));
384 };
384 };
385
385
386 OutputArea.prototype._safe_append = function (toinsert) {
386 OutputArea.prototype._safe_append = function (toinsert) {
387 // safely append an item to the document
387 // safely append an item to the document
388 // this is an object created by user code,
388 // this is an object created by user code,
389 // and may have errors, which should not be raised
389 // and may have errors, which should not be raised
390 // under any circumstances.
390 // under any circumstances.
391 try {
391 try {
392 this.element.append(toinsert);
392 this.element.append(toinsert);
393 } catch(err) {
393 } catch(err) {
394 console.log(err);
394 console.log(err);
395 // Create an actual output_area and output_subarea, which creates
395 // Create an actual output_area and output_subarea, which creates
396 // the prompt area and the proper indentation.
396 // the prompt area and the proper indentation.
397 var toinsert = this.create_output_area();
397 var toinsert = this.create_output_area();
398 var subarea = $('<div/>').addClass('output_subarea');
398 var subarea = $('<div/>').addClass('output_subarea');
399 toinsert.append(subarea);
399 toinsert.append(subarea);
400 this._append_javascript_error(err, subarea);
400 this._append_javascript_error(err, subarea);
401 this.element.append(toinsert);
401 this.element.append(toinsert);
402 }
402 }
403 };
403 };
404
404
405
405
406 OutputArea.prototype.append_execute_result = function (json) {
406 OutputArea.prototype.append_execute_result = function (json) {
407 var n = json.execution_count || ' ';
407 var n = json.execution_count || ' ';
408 var toinsert = this.create_output_area();
408 var toinsert = this.create_output_area();
409 if (this.prompt_area) {
409 if (this.prompt_area) {
410 toinsert.find('div.prompt').addClass('output_prompt').text('Out[' + n + ']:');
410 toinsert.find('div.prompt').addClass('output_prompt').text('Out[' + n + ']:');
411 }
411 }
412 var inserted = this.append_mime_type(json, toinsert);
412 var inserted = this.append_mime_type(json, toinsert);
413 if (inserted) {
413 if (inserted) {
414 inserted.addClass('output_result');
414 inserted.addClass('output_result');
415 }
415 }
416 this._safe_append(toinsert);
416 this._safe_append(toinsert);
417 // If we just output latex, typeset it.
417 // If we just output latex, typeset it.
418 if ((json['text/latex'] !== undefined) ||
418 if ((json['text/latex'] !== undefined) ||
419 (json['text/html'] !== undefined) ||
419 (json['text/html'] !== undefined) ||
420 (json['text/markdown'] !== undefined)) {
420 (json['text/markdown'] !== undefined)) {
421 this.typeset();
421 this.typeset();
422 }
422 }
423 };
423 };
424
424
425
425
426 OutputArea.prototype.append_error = function (json) {
426 OutputArea.prototype.append_error = function (json) {
427 var tb = json.traceback;
427 var tb = json.traceback;
428 if (tb !== undefined && tb.length > 0) {
428 if (tb !== undefined && tb.length > 0) {
429 var s = '';
429 var s = '';
430 var len = tb.length;
430 var len = tb.length;
431 for (var i=0; i<len; i++) {
431 for (var i=0; i<len; i++) {
432 s = s + tb[i] + '\n';
432 s = s + tb[i] + '\n';
433 }
433 }
434 s = s + '\n';
434 s = s + '\n';
435 var toinsert = this.create_output_area();
435 var toinsert = this.create_output_area();
436 var append_text = OutputArea.append_map['text/plain'];
436 var append_text = OutputArea.append_map['text/plain'];
437 if (append_text) {
437 if (append_text) {
438 append_text.apply(this, [s, {}, toinsert]).addClass('output_error');
438 append_text.apply(this, [s, {}, toinsert]).addClass('output_error');
439 }
439 }
440 this._safe_append(toinsert);
440 this._safe_append(toinsert);
441 }
441 }
442 };
442 };
443
443
444
444
445 OutputArea.prototype.append_stream = function (json) {
445 OutputArea.prototype.append_stream = function (json) {
446 var text = json.text;
446 var text = json.text;
447 var subclass = "output_"+json.name;
447 var subclass = "output_"+json.name;
448 if (this.outputs.length > 0){
448 if (this.outputs.length > 0){
449 // have at least one output to consider
449 // have at least one output to consider
450 var last = this.outputs[this.outputs.length-1];
450 var last = this.outputs[this.outputs.length-1];
451 if (last.output_type == 'stream' && json.name == last.name){
451 if (last.output_type == 'stream' && json.name == last.name){
452 // latest output was in the same stream,
452 // latest output was in the same stream,
453 // so append directly into its pre tag
453 // so append directly into its pre tag
454 // escape ANSI & HTML specials:
454 // escape ANSI & HTML specials:
455 last.text = utils.fixCarriageReturn(last.text + json.text);
455 last.text = utils.fixCarriageReturn(last.text + json.text);
456 var pre = this.element.find('div.'+subclass).last().find('pre');
456 var pre = this.element.find('div.'+subclass).last().find('pre');
457 var html = utils.fixConsole(last.text);
457 var html = utils.fixConsole(last.text);
458 // The only user content injected with this HTML call is
458 // The only user content injected with this HTML call is
459 // escaped by the fixConsole() method.
459 // escaped by the fixConsole() method.
460 pre.html(html);
460 pre.html(html);
461 // return false signals that we merged this output with the previous one,
461 // return false signals that we merged this output with the previous one,
462 // and the new output shouldn't be recorded.
462 // and the new output shouldn't be recorded.
463 return false;
463 return false;
464 }
464 }
465 }
465 }
466
466
467 if (!text.replace("\r", "")) {
467 if (!text.replace("\r", "")) {
468 // text is nothing (empty string, \r, etc.)
468 // text is nothing (empty string, \r, etc.)
469 // so don't append any elements, which might add undesirable space
469 // so don't append any elements, which might add undesirable space
470 // return true to indicate the output should be recorded.
470 // return true to indicate the output should be recorded.
471 return true;
471 return true;
472 }
472 }
473
473
474 // If we got here, attach a new div
474 // If we got here, attach a new div
475 var toinsert = this.create_output_area();
475 var toinsert = this.create_output_area();
476 var append_text = OutputArea.append_map['text/plain'];
476 var append_text = OutputArea.append_map['text/plain'];
477 if (append_text) {
477 if (append_text) {
478 append_text.apply(this, [text, {}, toinsert]).addClass("output_stream " + subclass);
478 append_text.apply(this, [text, {}, toinsert]).addClass("output_stream " + subclass);
479 }
479 }
480 this._safe_append(toinsert);
480 this._safe_append(toinsert);
481 return true;
481 return true;
482 };
482 };
483
483
484
484
485 OutputArea.prototype.append_display_data = function (json, handle_inserted) {
485 OutputArea.prototype.append_display_data = function (json, handle_inserted) {
486 var toinsert = this.create_output_area();
486 var toinsert = this.create_output_area();
487 if (this.append_mime_type(json, toinsert, handle_inserted)) {
487 if (this.append_mime_type(json, toinsert, handle_inserted)) {
488 this._safe_append(toinsert);
488 this._safe_append(toinsert);
489 // If we just output latex, typeset it.
489 // If we just output latex, typeset it.
490 if ((json['text/latex'] !== undefined) ||
490 if ((json['text/latex'] !== undefined) ||
491 (json['text/html'] !== undefined) ||
491 (json['text/html'] !== undefined) ||
492 (json['text/markdown'] !== undefined)) {
492 (json['text/markdown'] !== undefined)) {
493 this.typeset();
493 this.typeset();
494 }
494 }
495 }
495 }
496 };
496 };
497
497
498
498
499 OutputArea.safe_outputs = {
499 OutputArea.safe_outputs = {
500 'text/plain' : true,
500 'text/plain' : true,
501 'text/latex' : true,
501 'text/latex' : true,
502 'image/png' : true,
502 'image/png' : true,
503 'image/jpeg' : true
503 'image/jpeg' : true
504 };
504 };
505
505
506 OutputArea.prototype.append_mime_type = function (json, element, handle_inserted) {
506 OutputArea.prototype.append_mime_type = function (json, element, handle_inserted) {
507 for (var i=0; i < OutputArea.display_order.length; i++) {
507 for (var i=0; i < OutputArea.display_order.length; i++) {
508 var type = OutputArea.display_order[i];
508 var type = OutputArea.display_order[i];
509 var append = OutputArea.append_map[type];
509 var append = OutputArea.append_map[type];
510 if ((json.data[type] !== undefined) && append) {
510 if ((json.data[type] !== undefined) && append) {
511 var value = json.data[type];
511 var value = json.data[type];
512 if (!this.trusted && !OutputArea.safe_outputs[type]) {
512 if (!this.trusted && !OutputArea.safe_outputs[type]) {
513 // not trusted, sanitize HTML
513 // not trusted, sanitize HTML
514 if (type==='text/html' || type==='text/svg') {
514 if (type==='text/html' || type==='text/svg') {
515 value = security.sanitize_html(value);
515 value = security.sanitize_html(value);
516 } else {
516 } else {
517 // don't display if we don't know how to sanitize it
517 // don't display if we don't know how to sanitize it
518 console.log("Ignoring untrusted " + type + " output.");
518 console.log("Ignoring untrusted " + type + " output.");
519 continue;
519 continue;
520 }
520 }
521 }
521 }
522 var md = json.metadata || {};
522 var md = json.metadata || {};
523 var toinsert = append.apply(this, [value, md, element, handle_inserted]);
523 var toinsert = append.apply(this, [value, md, element, handle_inserted]);
524 // Since only the png and jpeg mime types call the inserted
524 // Since only the png and jpeg mime types call the inserted
525 // callback, if the mime type is something other we must call the
525 // callback, if the mime type is something other we must call the
526 // inserted callback only when the element is actually inserted
526 // inserted callback only when the element is actually inserted
527 // into the DOM. Use a timeout of 0 to do this.
527 // into the DOM. Use a timeout of 0 to do this.
528 if (['image/png', 'image/jpeg'].indexOf(type) < 0 && handle_inserted !== undefined) {
528 if (['image/png', 'image/jpeg'].indexOf(type) < 0 && handle_inserted !== undefined) {
529 setTimeout(handle_inserted, 0);
529 setTimeout(handle_inserted, 0);
530 }
530 }
531 this.events.trigger('output_appended.OutputArea', [type, value, md, toinsert]);
531 this.events.trigger('output_appended.OutputArea', [type, value, md, toinsert]);
532 return toinsert;
532 return toinsert;
533 }
533 }
534 }
534 }
535 return null;
535 return null;
536 };
536 };
537
537
538
538
539 var append_html = function (html, md, element) {
539 var append_html = function (html, md, element) {
540 var type = 'text/html';
540 var type = 'text/html';
541 var toinsert = this.create_output_subarea(md, "output_html rendered_html", type);
541 var toinsert = this.create_output_subarea(md, "output_html rendered_html", type);
542 this.keyboard_manager.register_events(toinsert);
542 this.keyboard_manager.register_events(toinsert);
543 toinsert.append(html);
543 toinsert.append(html);
544 element.append(toinsert);
544 element.append(toinsert);
545 return toinsert;
545 return toinsert;
546 };
546 };
547
547
548
548
549 var append_markdown = function(markdown, md, element) {
549 var append_markdown = function(markdown, md, element) {
550 var type = 'text/markdown';
550 var type = 'text/markdown';
551 var toinsert = this.create_output_subarea(md, "output_markdown", type);
551 var toinsert = this.create_output_subarea(md, "output_markdown", type);
552 var text_and_math = mathjaxutils.remove_math(markdown);
552 var text_and_math = mathjaxutils.remove_math(markdown);
553 var text = text_and_math[0];
553 var text = text_and_math[0];
554 var math = text_and_math[1];
554 var math = text_and_math[1];
555 var html = marked.parser(marked.lexer(text));
555 marked(text, function (err, html) {
556 html = mathjaxutils.replace_math(html, math);
556 html = mathjaxutils.replace_math(html, math);
557 toinsert.append(html);
557 toinsert.append(html);
558 });
558 element.append(toinsert);
559 element.append(toinsert);
559 return toinsert;
560 return toinsert;
560 };
561 };
561
562
562
563
563 var append_javascript = function (js, md, element) {
564 var append_javascript = function (js, md, element) {
564 // We just eval the JS code, element appears in the local scope.
565 // We just eval the JS code, element appears in the local scope.
565 var type = 'application/javascript';
566 var type = 'application/javascript';
566 var toinsert = this.create_output_subarea(md, "output_javascript", type);
567 var toinsert = this.create_output_subarea(md, "output_javascript", type);
567 this.keyboard_manager.register_events(toinsert);
568 this.keyboard_manager.register_events(toinsert);
568 element.append(toinsert);
569 element.append(toinsert);
569
570
570 // Fix for ipython/issues/5293, make sure `element` is the area which
571 // Fix for ipython/issues/5293, make sure `element` is the area which
571 // output can be inserted into at the time of JS execution.
572 // output can be inserted into at the time of JS execution.
572 element = toinsert;
573 element = toinsert;
573 try {
574 try {
574 eval(js);
575 eval(js);
575 } catch(err) {
576 } catch(err) {
576 console.log(err);
577 console.log(err);
577 this._append_javascript_error(err, toinsert);
578 this._append_javascript_error(err, toinsert);
578 }
579 }
579 return toinsert;
580 return toinsert;
580 };
581 };
581
582
582
583
583 var append_text = function (data, md, element) {
584 var append_text = function (data, md, element) {
584 var type = 'text/plain';
585 var type = 'text/plain';
585 var toinsert = this.create_output_subarea(md, "output_text", type);
586 var toinsert = this.create_output_subarea(md, "output_text", type);
586 // escape ANSI & HTML specials in plaintext:
587 // escape ANSI & HTML specials in plaintext:
587 data = utils.fixConsole(data);
588 data = utils.fixConsole(data);
588 data = utils.fixCarriageReturn(data);
589 data = utils.fixCarriageReturn(data);
589 data = utils.autoLinkUrls(data);
590 data = utils.autoLinkUrls(data);
590 // The only user content injected with this HTML call is
591 // The only user content injected with this HTML call is
591 // escaped by the fixConsole() method.
592 // escaped by the fixConsole() method.
592 toinsert.append($("<pre/>").html(data));
593 toinsert.append($("<pre/>").html(data));
593 element.append(toinsert);
594 element.append(toinsert);
594 return toinsert;
595 return toinsert;
595 };
596 };
596
597
597
598
598 var append_svg = function (svg_html, md, element) {
599 var append_svg = function (svg_html, md, element) {
599 var type = 'image/svg+xml';
600 var type = 'image/svg+xml';
600 var toinsert = this.create_output_subarea(md, "output_svg", type);
601 var toinsert = this.create_output_subarea(md, "output_svg", type);
601
602
602 // Get the svg element from within the HTML.
603 // Get the svg element from within the HTML.
603 var svg = $('<div />').html(svg_html).find('svg');
604 var svg = $('<div />').html(svg_html).find('svg');
604 var svg_area = $('<div />');
605 var svg_area = $('<div />');
605 var width = svg.attr('width');
606 var width = svg.attr('width');
606 var height = svg.attr('height');
607 var height = svg.attr('height');
607 svg
608 svg
608 .width('100%')
609 .width('100%')
609 .height('100%');
610 .height('100%');
610 svg_area
611 svg_area
611 .width(width)
612 .width(width)
612 .height(height);
613 .height(height);
613
614
614 // The jQuery resize handlers don't seem to work on the svg element.
615 // The jQuery resize handlers don't seem to work on the svg element.
615 // When the svg renders completely, measure it's size and set the parent
616 // When the svg renders completely, measure it's size and set the parent
616 // div to that size. Then set the svg to 100% the size of the parent
617 // div to that size. Then set the svg to 100% the size of the parent
617 // div and make the parent div resizable.
618 // div and make the parent div resizable.
618 this._dblclick_to_reset_size(svg_area, true, false);
619 this._dblclick_to_reset_size(svg_area, true, false);
619
620
620 svg_area.append(svg);
621 svg_area.append(svg);
621 toinsert.append(svg_area);
622 toinsert.append(svg_area);
622 element.append(toinsert);
623 element.append(toinsert);
623
624
624 return toinsert;
625 return toinsert;
625 };
626 };
626
627
627 OutputArea.prototype._dblclick_to_reset_size = function (img, immediately, resize_parent) {
628 OutputArea.prototype._dblclick_to_reset_size = function (img, immediately, resize_parent) {
628 // Add a resize handler to an element
629 // Add a resize handler to an element
629 //
630 //
630 // img: jQuery element
631 // img: jQuery element
631 // immediately: bool=False
632 // immediately: bool=False
632 // Wait for the element to load before creating the handle.
633 // Wait for the element to load before creating the handle.
633 // resize_parent: bool=True
634 // resize_parent: bool=True
634 // Should the parent of the element be resized when the element is
635 // Should the parent of the element be resized when the element is
635 // reset (by double click).
636 // reset (by double click).
636 var callback = function (){
637 var callback = function (){
637 var h0 = img.height();
638 var h0 = img.height();
638 var w0 = img.width();
639 var w0 = img.width();
639 if (!(h0 && w0)) {
640 if (!(h0 && w0)) {
640 // zero size, don't make it resizable
641 // zero size, don't make it resizable
641 return;
642 return;
642 }
643 }
643 img.resizable({
644 img.resizable({
644 aspectRatio: true,
645 aspectRatio: true,
645 autoHide: true
646 autoHide: true
646 });
647 });
647 img.dblclick(function () {
648 img.dblclick(function () {
648 // resize wrapper & image together for some reason:
649 // resize wrapper & image together for some reason:
649 img.height(h0);
650 img.height(h0);
650 img.width(w0);
651 img.width(w0);
651 if (resize_parent === undefined || resize_parent) {
652 if (resize_parent === undefined || resize_parent) {
652 img.parent().height(h0);
653 img.parent().height(h0);
653 img.parent().width(w0);
654 img.parent().width(w0);
654 }
655 }
655 });
656 });
656 };
657 };
657
658
658 if (immediately) {
659 if (immediately) {
659 callback();
660 callback();
660 } else {
661 } else {
661 img.on("load", callback);
662 img.on("load", callback);
662 }
663 }
663 };
664 };
664
665
665 var set_width_height = function (img, md, mime) {
666 var set_width_height = function (img, md, mime) {
666 // set width and height of an img element from metadata
667 // set width and height of an img element from metadata
667 var height = _get_metadata_key(md, 'height', mime);
668 var height = _get_metadata_key(md, 'height', mime);
668 if (height !== undefined) img.attr('height', height);
669 if (height !== undefined) img.attr('height', height);
669 var width = _get_metadata_key(md, 'width', mime);
670 var width = _get_metadata_key(md, 'width', mime);
670 if (width !== undefined) img.attr('width', width);
671 if (width !== undefined) img.attr('width', width);
671 };
672 };
672
673
673 var append_png = function (png, md, element, handle_inserted) {
674 var append_png = function (png, md, element, handle_inserted) {
674 var type = 'image/png';
675 var type = 'image/png';
675 var toinsert = this.create_output_subarea(md, "output_png", type);
676 var toinsert = this.create_output_subarea(md, "output_png", type);
676 var img = $("<img/>");
677 var img = $("<img/>");
677 if (handle_inserted !== undefined) {
678 if (handle_inserted !== undefined) {
678 img.on('load', function(){
679 img.on('load', function(){
679 handle_inserted(img);
680 handle_inserted(img);
680 });
681 });
681 }
682 }
682 img[0].src = 'data:image/png;base64,'+ png;
683 img[0].src = 'data:image/png;base64,'+ png;
683 set_width_height(img, md, 'image/png');
684 set_width_height(img, md, 'image/png');
684 this._dblclick_to_reset_size(img);
685 this._dblclick_to_reset_size(img);
685 toinsert.append(img);
686 toinsert.append(img);
686 element.append(toinsert);
687 element.append(toinsert);
687 return toinsert;
688 return toinsert;
688 };
689 };
689
690
690
691
691 var append_jpeg = function (jpeg, md, element, handle_inserted) {
692 var append_jpeg = function (jpeg, md, element, handle_inserted) {
692 var type = 'image/jpeg';
693 var type = 'image/jpeg';
693 var toinsert = this.create_output_subarea(md, "output_jpeg", type);
694 var toinsert = this.create_output_subarea(md, "output_jpeg", type);
694 var img = $("<img/>");
695 var img = $("<img/>");
695 if (handle_inserted !== undefined) {
696 if (handle_inserted !== undefined) {
696 img.on('load', function(){
697 img.on('load', function(){
697 handle_inserted(img);
698 handle_inserted(img);
698 });
699 });
699 }
700 }
700 img[0].src = 'data:image/jpeg;base64,'+ jpeg;
701 img[0].src = 'data:image/jpeg;base64,'+ jpeg;
701 set_width_height(img, md, 'image/jpeg');
702 set_width_height(img, md, 'image/jpeg');
702 this._dblclick_to_reset_size(img);
703 this._dblclick_to_reset_size(img);
703 toinsert.append(img);
704 toinsert.append(img);
704 element.append(toinsert);
705 element.append(toinsert);
705 return toinsert;
706 return toinsert;
706 };
707 };
707
708
708
709
709 var append_pdf = function (pdf, md, element) {
710 var append_pdf = function (pdf, md, element) {
710 var type = 'application/pdf';
711 var type = 'application/pdf';
711 var toinsert = this.create_output_subarea(md, "output_pdf", type);
712 var toinsert = this.create_output_subarea(md, "output_pdf", type);
712 var a = $('<a/>').attr('href', 'data:application/pdf;base64,'+pdf);
713 var a = $('<a/>').attr('href', 'data:application/pdf;base64,'+pdf);
713 a.attr('target', '_blank');
714 a.attr('target', '_blank');
714 a.text('View PDF');
715 a.text('View PDF');
715 toinsert.append(a);
716 toinsert.append(a);
716 element.append(toinsert);
717 element.append(toinsert);
717 return toinsert;
718 return toinsert;
718 };
719 };
719
720
720 var append_latex = function (latex, md, element) {
721 var append_latex = function (latex, md, element) {
721 // This method cannot do the typesetting because the latex first has to
722 // This method cannot do the typesetting because the latex first has to
722 // be on the page.
723 // be on the page.
723 var type = 'text/latex';
724 var type = 'text/latex';
724 var toinsert = this.create_output_subarea(md, "output_latex", type);
725 var toinsert = this.create_output_subarea(md, "output_latex", type);
725 toinsert.append(latex);
726 toinsert.append(latex);
726 element.append(toinsert);
727 element.append(toinsert);
727 return toinsert;
728 return toinsert;
728 };
729 };
729
730
730
731
731 OutputArea.prototype.append_raw_input = function (msg) {
732 OutputArea.prototype.append_raw_input = function (msg) {
732 var that = this;
733 var that = this;
733 this.expand();
734 this.expand();
734 var content = msg.content;
735 var content = msg.content;
735 var area = this.create_output_area();
736 var area = this.create_output_area();
736
737
737 // disable any other raw_inputs, if they are left around
738 // disable any other raw_inputs, if they are left around
738 $("div.output_subarea.raw_input_container").remove();
739 $("div.output_subarea.raw_input_container").remove();
739
740
740 var input_type = content.password ? 'password' : 'text';
741 var input_type = content.password ? 'password' : 'text';
741
742
742 area.append(
743 area.append(
743 $("<div/>")
744 $("<div/>")
744 .addClass("box-flex1 output_subarea raw_input_container")
745 .addClass("box-flex1 output_subarea raw_input_container")
745 .append(
746 .append(
746 $("<span/>")
747 $("<span/>")
747 .addClass("raw_input_prompt")
748 .addClass("raw_input_prompt")
748 .text(content.prompt)
749 .text(content.prompt)
749 )
750 )
750 .append(
751 .append(
751 $("<input/>")
752 $("<input/>")
752 .addClass("raw_input")
753 .addClass("raw_input")
753 .attr('type', input_type)
754 .attr('type', input_type)
754 .attr("size", 47)
755 .attr("size", 47)
755 .keydown(function (event, ui) {
756 .keydown(function (event, ui) {
756 // make sure we submit on enter,
757 // make sure we submit on enter,
757 // and don't re-execute the *cell* on shift-enter
758 // and don't re-execute the *cell* on shift-enter
758 if (event.which === keyboard.keycodes.enter) {
759 if (event.which === keyboard.keycodes.enter) {
759 that._submit_raw_input();
760 that._submit_raw_input();
760 return false;
761 return false;
761 }
762 }
762 })
763 })
763 )
764 )
764 );
765 );
765
766
766 this.element.append(area);
767 this.element.append(area);
767 var raw_input = area.find('input.raw_input');
768 var raw_input = area.find('input.raw_input');
768 // Register events that enable/disable the keyboard manager while raw
769 // Register events that enable/disable the keyboard manager while raw
769 // input is focused.
770 // input is focused.
770 this.keyboard_manager.register_events(raw_input);
771 this.keyboard_manager.register_events(raw_input);
771 // Note, the following line used to read raw_input.focus().focus().
772 // Note, the following line used to read raw_input.focus().focus().
772 // This seemed to be needed otherwise only the cell would be focused.
773 // This seemed to be needed otherwise only the cell would be focused.
773 // But with the modal UI, this seems to work fine with one call to focus().
774 // But with the modal UI, this seems to work fine with one call to focus().
774 raw_input.focus();
775 raw_input.focus();
775 };
776 };
776
777
777 OutputArea.prototype._submit_raw_input = function (evt) {
778 OutputArea.prototype._submit_raw_input = function (evt) {
778 var container = this.element.find("div.raw_input_container");
779 var container = this.element.find("div.raw_input_container");
779 var theprompt = container.find("span.raw_input_prompt");
780 var theprompt = container.find("span.raw_input_prompt");
780 var theinput = container.find("input.raw_input");
781 var theinput = container.find("input.raw_input");
781 var value = theinput.val();
782 var value = theinput.val();
782 var echo = value;
783 var echo = value;
783 // don't echo if it's a password
784 // don't echo if it's a password
784 if (theinput.attr('type') == 'password') {
785 if (theinput.attr('type') == 'password') {
785 echo = '········';
786 echo = '········';
786 }
787 }
787 var content = {
788 var content = {
788 output_type : 'stream',
789 output_type : 'stream',
789 stream : 'stdout',
790 stream : 'stdout',
790 text : theprompt.text() + echo + '\n'
791 text : theprompt.text() + echo + '\n'
791 };
792 };
792 // remove form container
793 // remove form container
793 container.parent().remove();
794 container.parent().remove();
794 // replace with plaintext version in stdout
795 // replace with plaintext version in stdout
795 this.append_output(content, false);
796 this.append_output(content, false);
796 this.events.trigger('send_input_reply.Kernel', value);
797 this.events.trigger('send_input_reply.Kernel', value);
797 };
798 };
798
799
799
800
800 OutputArea.prototype.handle_clear_output = function (msg) {
801 OutputArea.prototype.handle_clear_output = function (msg) {
801 // msg spec v4 had stdout, stderr, display keys
802 // msg spec v4 had stdout, stderr, display keys
802 // v4.1 replaced these with just wait
803 // v4.1 replaced these with just wait
803 // The default behavior is the same (stdout=stderr=display=True, wait=False),
804 // The default behavior is the same (stdout=stderr=display=True, wait=False),
804 // so v4 messages will still be properly handled,
805 // so v4 messages will still be properly handled,
805 // except for the rarely used clearing less than all output.
806 // except for the rarely used clearing less than all output.
806 this.clear_output(msg.content.wait || false);
807 this.clear_output(msg.content.wait || false);
807 };
808 };
808
809
809
810
810 OutputArea.prototype.clear_output = function(wait) {
811 OutputArea.prototype.clear_output = function(wait) {
811 if (wait) {
812 if (wait) {
812
813
813 // If a clear is queued, clear before adding another to the queue.
814 // If a clear is queued, clear before adding another to the queue.
814 if (this.clear_queued) {
815 if (this.clear_queued) {
815 this.clear_output(false);
816 this.clear_output(false);
816 }
817 }
817
818
818 this.clear_queued = true;
819 this.clear_queued = true;
819 } else {
820 } else {
820
821
821 // Fix the output div's height if the clear_output is waiting for
822 // Fix the output div's height if the clear_output is waiting for
822 // new output (it is being used in an animation).
823 // new output (it is being used in an animation).
823 if (this.clear_queued) {
824 if (this.clear_queued) {
824 var height = this.element.height();
825 var height = this.element.height();
825 this.element.height(height);
826 this.element.height(height);
826 this.clear_queued = false;
827 this.clear_queued = false;
827 }
828 }
828
829
829 // Clear all
830 // Clear all
830 // Remove load event handlers from img tags because we don't want
831 // Remove load event handlers from img tags because we don't want
831 // them to fire if the image is never added to the page.
832 // them to fire if the image is never added to the page.
832 this.element.find('img').off('load');
833 this.element.find('img').off('load');
833 this.element.html("");
834 this.element.html("");
834 this.outputs = [];
835 this.outputs = [];
835 this.trusted = true;
836 this.trusted = true;
836 this.unscroll_area();
837 this.unscroll_area();
837 return;
838 return;
838 }
839 }
839 };
840 };
840
841
841
842
842 // JSON serialization
843 // JSON serialization
843
844
844 OutputArea.prototype.fromJSON = function (outputs, metadata) {
845 OutputArea.prototype.fromJSON = function (outputs, metadata) {
845 var len = outputs.length;
846 var len = outputs.length;
846 metadata = metadata || {};
847 metadata = metadata || {};
847
848
848 for (var i=0; i<len; i++) {
849 for (var i=0; i<len; i++) {
849 this.append_output(outputs[i]);
850 this.append_output(outputs[i]);
850 }
851 }
851
852
852 if (metadata.collapsed !== undefined) {
853 if (metadata.collapsed !== undefined) {
853 this.collapsed = metadata.collapsed;
854 this.collapsed = metadata.collapsed;
854 if (metadata.collapsed) {
855 if (metadata.collapsed) {
855 this.collapse_output();
856 this.collapse_output();
856 }
857 }
857 }
858 }
858 if (metadata.autoscroll !== undefined) {
859 if (metadata.autoscroll !== undefined) {
859 this.collapsed = metadata.collapsed;
860 this.collapsed = metadata.collapsed;
860 if (metadata.collapsed) {
861 if (metadata.collapsed) {
861 this.collapse_output();
862 this.collapse_output();
862 } else {
863 } else {
863 this.expand_output();
864 this.expand_output();
864 }
865 }
865 }
866 }
866 };
867 };
867
868
868
869
869 OutputArea.prototype.toJSON = function () {
870 OutputArea.prototype.toJSON = function () {
870 return this.outputs;
871 return this.outputs;
871 };
872 };
872
873
873 /**
874 /**
874 * Class properties
875 * Class properties
875 **/
876 **/
876
877
877 /**
878 /**
878 * Threshold to trigger autoscroll when the OutputArea is resized,
879 * Threshold to trigger autoscroll when the OutputArea is resized,
879 * typically when new outputs are added.
880 * typically when new outputs are added.
880 *
881 *
881 * Behavior is undefined if autoscroll is lower than minimum_scroll_threshold,
882 * Behavior is undefined if autoscroll is lower than minimum_scroll_threshold,
882 * unless it is < 0, in which case autoscroll will never be triggered
883 * unless it is < 0, in which case autoscroll will never be triggered
883 *
884 *
884 * @property auto_scroll_threshold
885 * @property auto_scroll_threshold
885 * @type Number
886 * @type Number
886 * @default 100
887 * @default 100
887 *
888 *
888 **/
889 **/
889 OutputArea.auto_scroll_threshold = 100;
890 OutputArea.auto_scroll_threshold = 100;
890
891
891 /**
892 /**
892 * Lower limit (in lines) for OutputArea to be made scrollable. OutputAreas
893 * Lower limit (in lines) for OutputArea to be made scrollable. OutputAreas
893 * shorter than this are never scrolled.
894 * shorter than this are never scrolled.
894 *
895 *
895 * @property minimum_scroll_threshold
896 * @property minimum_scroll_threshold
896 * @type Number
897 * @type Number
897 * @default 20
898 * @default 20
898 *
899 *
899 **/
900 **/
900 OutputArea.minimum_scroll_threshold = 20;
901 OutputArea.minimum_scroll_threshold = 20;
901
902
902
903
903 OutputArea.display_order = [
904 OutputArea.display_order = [
904 'application/javascript',
905 'application/javascript',
905 'text/html',
906 'text/html',
906 'text/markdown',
907 'text/markdown',
907 'text/latex',
908 'text/latex',
908 'image/svg+xml',
909 'image/svg+xml',
909 'image/png',
910 'image/png',
910 'image/jpeg',
911 'image/jpeg',
911 'application/pdf',
912 'application/pdf',
912 'text/plain'
913 'text/plain'
913 ];
914 ];
914
915
915 OutputArea.append_map = {
916 OutputArea.append_map = {
916 "text/plain" : append_text,
917 "text/plain" : append_text,
917 "text/html" : append_html,
918 "text/html" : append_html,
918 "text/markdown": append_markdown,
919 "text/markdown": append_markdown,
919 "image/svg+xml" : append_svg,
920 "image/svg+xml" : append_svg,
920 "image/png" : append_png,
921 "image/png" : append_png,
921 "image/jpeg" : append_jpeg,
922 "image/jpeg" : append_jpeg,
922 "text/latex" : append_latex,
923 "text/latex" : append_latex,
923 "application/javascript" : append_javascript,
924 "application/javascript" : append_javascript,
924 "application/pdf" : append_pdf
925 "application/pdf" : append_pdf
925 };
926 };
926
927
927 // For backwards compatability.
928 // For backwards compatability.
928 IPython.OutputArea = OutputArea;
929 IPython.OutputArea = OutputArea;
929
930
930 return {'OutputArea': OutputArea};
931 return {'OutputArea': OutputArea};
931 });
932 });
@@ -1,345 +1,346
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 'base/js/utils',
6 'base/js/utils',
7 'jquery',
7 'jquery',
8 'notebook/js/cell',
8 'notebook/js/cell',
9 'base/js/security',
9 'base/js/security',
10 'notebook/js/mathjaxutils',
10 'notebook/js/mathjaxutils',
11 'notebook/js/celltoolbar',
11 'notebook/js/celltoolbar',
12 'components/marked/lib/marked',
12 'components/marked/lib/marked',
13 'codemirror/lib/codemirror',
13 'codemirror/lib/codemirror',
14 'codemirror/mode/gfm/gfm',
14 'codemirror/mode/gfm/gfm',
15 'notebook/js/codemirror-ipythongfm'
15 'notebook/js/codemirror-ipythongfm'
16 ], function(IPython,utils , $, cell, security, mathjaxutils, celltoolbar, marked, CodeMirror, gfm, ipgfm) {
16 ], function(IPython,utils , $, cell, security, mathjaxutils, celltoolbar, marked, CodeMirror, gfm, ipgfm) {
17 "use strict";
17 "use strict";
18 var Cell = cell.Cell;
18 var Cell = cell.Cell;
19
19
20 var TextCell = function (options) {
20 var TextCell = function (options) {
21 // Constructor
21 // Constructor
22 //
22 //
23 // Construct a new TextCell, codemirror mode is by default 'htmlmixed',
23 // Construct a new TextCell, codemirror mode is by default 'htmlmixed',
24 // and cell type is 'text' cell start as not redered.
24 // and cell type is 'text' cell start as not redered.
25 //
25 //
26 // Parameters:
26 // Parameters:
27 // options: dictionary
27 // options: dictionary
28 // Dictionary of keyword arguments.
28 // Dictionary of keyword arguments.
29 // events: $(Events) instance
29 // events: $(Events) instance
30 // config: dictionary
30 // config: dictionary
31 // keyboard_manager: KeyboardManager instance
31 // keyboard_manager: KeyboardManager instance
32 // notebook: Notebook instance
32 // notebook: Notebook instance
33 options = options || {};
33 options = options || {};
34
34
35 // in all TextCell/Cell subclasses
35 // in all TextCell/Cell subclasses
36 // do not assign most of members here, just pass it down
36 // do not assign most of members here, just pass it down
37 // in the options dict potentially overwriting what you wish.
37 // in the options dict potentially overwriting what you wish.
38 // they will be assigned in the base class.
38 // they will be assigned in the base class.
39 this.notebook = options.notebook;
39 this.notebook = options.notebook;
40 this.events = options.events;
40 this.events = options.events;
41 this.config = options.config;
41 this.config = options.config;
42
42
43 // we cannot put this as a class key as it has handle to "this".
43 // we cannot put this as a class key as it has handle to "this".
44 var cm_overwrite_options = {
44 var cm_overwrite_options = {
45 onKeyEvent: $.proxy(this.handle_keyevent,this)
45 onKeyEvent: $.proxy(this.handle_keyevent,this)
46 };
46 };
47 var config = utils.mergeopt(TextCell, this.config, {cm_config:cm_overwrite_options});
47 var config = utils.mergeopt(TextCell, this.config, {cm_config:cm_overwrite_options});
48 Cell.apply(this, [{
48 Cell.apply(this, [{
49 config: config,
49 config: config,
50 keyboard_manager: options.keyboard_manager,
50 keyboard_manager: options.keyboard_manager,
51 events: this.events}]);
51 events: this.events}]);
52
52
53 this.cell_type = this.cell_type || 'text';
53 this.cell_type = this.cell_type || 'text';
54 mathjaxutils = mathjaxutils;
54 mathjaxutils = mathjaxutils;
55 this.rendered = false;
55 this.rendered = false;
56 };
56 };
57
57
58 TextCell.prototype = Object.create(Cell.prototype);
58 TextCell.prototype = Object.create(Cell.prototype);
59
59
60 TextCell.options_default = {
60 TextCell.options_default = {
61 cm_config : {
61 cm_config : {
62 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
62 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
63 mode: 'htmlmixed',
63 mode: 'htmlmixed',
64 lineWrapping : true,
64 lineWrapping : true,
65 }
65 }
66 };
66 };
67
67
68
68
69 /**
69 /**
70 * Create the DOM element of the TextCell
70 * Create the DOM element of the TextCell
71 * @method create_element
71 * @method create_element
72 * @private
72 * @private
73 */
73 */
74 TextCell.prototype.create_element = function () {
74 TextCell.prototype.create_element = function () {
75 Cell.prototype.create_element.apply(this, arguments);
75 Cell.prototype.create_element.apply(this, arguments);
76
76
77 var cell = $("<div>").addClass('cell text_cell');
77 var cell = $("<div>").addClass('cell text_cell');
78 cell.attr('tabindex','2');
78 cell.attr('tabindex','2');
79
79
80 var prompt = $('<div/>').addClass('prompt input_prompt');
80 var prompt = $('<div/>').addClass('prompt input_prompt');
81 cell.append(prompt);
81 cell.append(prompt);
82 var inner_cell = $('<div/>').addClass('inner_cell');
82 var inner_cell = $('<div/>').addClass('inner_cell');
83 this.celltoolbar = new celltoolbar.CellToolbar({
83 this.celltoolbar = new celltoolbar.CellToolbar({
84 cell: this,
84 cell: this,
85 notebook: this.notebook});
85 notebook: this.notebook});
86 inner_cell.append(this.celltoolbar.element);
86 inner_cell.append(this.celltoolbar.element);
87 var input_area = $('<div/>').addClass('input_area');
87 var input_area = $('<div/>').addClass('input_area');
88 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
88 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
89 // The tabindex=-1 makes this div focusable.
89 // The tabindex=-1 makes this div focusable.
90 var render_area = $('<div/>').addClass('text_cell_render rendered_html')
90 var render_area = $('<div/>').addClass('text_cell_render rendered_html')
91 .attr('tabindex','-1');
91 .attr('tabindex','-1');
92 inner_cell.append(input_area).append(render_area);
92 inner_cell.append(input_area).append(render_area);
93 cell.append(inner_cell);
93 cell.append(inner_cell);
94 this.element = cell;
94 this.element = cell;
95 };
95 };
96
96
97
97
98 // Cell level actions
98 // Cell level actions
99
99
100 TextCell.prototype.select = function () {
100 TextCell.prototype.select = function () {
101 var cont = Cell.prototype.select.apply(this);
101 var cont = Cell.prototype.select.apply(this);
102 if (cont) {
102 if (cont) {
103 if (this.mode === 'edit') {
103 if (this.mode === 'edit') {
104 this.code_mirror.refresh();
104 this.code_mirror.refresh();
105 }
105 }
106 }
106 }
107 return cont;
107 return cont;
108 };
108 };
109
109
110 TextCell.prototype.unrender = function () {
110 TextCell.prototype.unrender = function () {
111 if (this.read_only) return;
111 if (this.read_only) return;
112 var cont = Cell.prototype.unrender.apply(this);
112 var cont = Cell.prototype.unrender.apply(this);
113 if (cont) {
113 if (cont) {
114 var text_cell = this.element;
114 var text_cell = this.element;
115 var output = text_cell.find("div.text_cell_render");
115 var output = text_cell.find("div.text_cell_render");
116 if (this.get_text() === this.placeholder) {
116 if (this.get_text() === this.placeholder) {
117 this.set_text('');
117 this.set_text('');
118 }
118 }
119 this.refresh();
119 this.refresh();
120 }
120 }
121 return cont;
121 return cont;
122 };
122 };
123
123
124 TextCell.prototype.execute = function () {
124 TextCell.prototype.execute = function () {
125 this.render();
125 this.render();
126 };
126 };
127
127
128 /**
128 /**
129 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
129 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
130 * @method get_text
130 * @method get_text
131 * @retrun {string} CodeMirror current text value
131 * @retrun {string} CodeMirror current text value
132 */
132 */
133 TextCell.prototype.get_text = function() {
133 TextCell.prototype.get_text = function() {
134 return this.code_mirror.getValue();
134 return this.code_mirror.getValue();
135 };
135 };
136
136
137 /**
137 /**
138 * @param {string} text - Codemiror text value
138 * @param {string} text - Codemiror text value
139 * @see TextCell#get_text
139 * @see TextCell#get_text
140 * @method set_text
140 * @method set_text
141 * */
141 * */
142 TextCell.prototype.set_text = function(text) {
142 TextCell.prototype.set_text = function(text) {
143 this.code_mirror.setValue(text);
143 this.code_mirror.setValue(text);
144 this.unrender();
144 this.unrender();
145 this.code_mirror.refresh();
145 this.code_mirror.refresh();
146 };
146 };
147
147
148 /**
148 /**
149 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
149 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
150 * @method get_rendered
150 * @method get_rendered
151 * */
151 * */
152 TextCell.prototype.get_rendered = function() {
152 TextCell.prototype.get_rendered = function() {
153 return this.element.find('div.text_cell_render').html();
153 return this.element.find('div.text_cell_render').html();
154 };
154 };
155
155
156 /**
156 /**
157 * @method set_rendered
157 * @method set_rendered
158 */
158 */
159 TextCell.prototype.set_rendered = function(text) {
159 TextCell.prototype.set_rendered = function(text) {
160 this.element.find('div.text_cell_render').html(text);
160 this.element.find('div.text_cell_render').html(text);
161 };
161 };
162
162
163
163
164 /**
164 /**
165 * Create Text cell from JSON
165 * Create Text cell from JSON
166 * @param {json} data - JSON serialized text-cell
166 * @param {json} data - JSON serialized text-cell
167 * @method fromJSON
167 * @method fromJSON
168 */
168 */
169 TextCell.prototype.fromJSON = function (data) {
169 TextCell.prototype.fromJSON = function (data) {
170 Cell.prototype.fromJSON.apply(this, arguments);
170 Cell.prototype.fromJSON.apply(this, arguments);
171 if (data.cell_type === this.cell_type) {
171 if (data.cell_type === this.cell_type) {
172 if (data.source !== undefined) {
172 if (data.source !== undefined) {
173 this.set_text(data.source);
173 this.set_text(data.source);
174 // make this value the starting point, so that we can only undo
174 // make this value the starting point, so that we can only undo
175 // to this state, instead of a blank cell
175 // to this state, instead of a blank cell
176 this.code_mirror.clearHistory();
176 this.code_mirror.clearHistory();
177 // TODO: This HTML needs to be treated as potentially dangerous
177 // TODO: This HTML needs to be treated as potentially dangerous
178 // user input and should be handled before set_rendered.
178 // user input and should be handled before set_rendered.
179 this.set_rendered(data.rendered || '');
179 this.set_rendered(data.rendered || '');
180 this.rendered = false;
180 this.rendered = false;
181 this.render();
181 this.render();
182 }
182 }
183 }
183 }
184 };
184 };
185
185
186 /** Generate JSON from cell
186 /** Generate JSON from cell
187 * @return {object} cell data serialised to json
187 * @return {object} cell data serialised to json
188 */
188 */
189 TextCell.prototype.toJSON = function () {
189 TextCell.prototype.toJSON = function () {
190 var data = Cell.prototype.toJSON.apply(this);
190 var data = Cell.prototype.toJSON.apply(this);
191 data.source = this.get_text();
191 data.source = this.get_text();
192 if (data.source == this.placeholder) {
192 if (data.source == this.placeholder) {
193 data.source = "";
193 data.source = "";
194 }
194 }
195 return data;
195 return data;
196 };
196 };
197
197
198
198
199 var MarkdownCell = function (options) {
199 var MarkdownCell = function (options) {
200 // Constructor
200 // Constructor
201 //
201 //
202 // Parameters:
202 // Parameters:
203 // options: dictionary
203 // options: dictionary
204 // Dictionary of keyword arguments.
204 // Dictionary of keyword arguments.
205 // events: $(Events) instance
205 // events: $(Events) instance
206 // config: dictionary
206 // config: dictionary
207 // keyboard_manager: KeyboardManager instance
207 // keyboard_manager: KeyboardManager instance
208 // notebook: Notebook instance
208 // notebook: Notebook instance
209 options = options || {};
209 options = options || {};
210 var config = utils.mergeopt(MarkdownCell, options.config);
210 var config = utils.mergeopt(MarkdownCell, options.config);
211 TextCell.apply(this, [$.extend({}, options, {config: config})]);
211 TextCell.apply(this, [$.extend({}, options, {config: config})]);
212
212
213 this.cell_type = 'markdown';
213 this.cell_type = 'markdown';
214 };
214 };
215
215
216 MarkdownCell.options_default = {
216 MarkdownCell.options_default = {
217 cm_config: {
217 cm_config: {
218 mode: 'ipythongfm'
218 mode: 'ipythongfm'
219 },
219 },
220 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
220 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
221 };
221 };
222
222
223 MarkdownCell.prototype = Object.create(TextCell.prototype);
223 MarkdownCell.prototype = Object.create(TextCell.prototype);
224
224
225 MarkdownCell.prototype.set_heading_level = function (level) {
225 MarkdownCell.prototype.set_heading_level = function (level) {
226 // make a markdown cell a heading
226 // make a markdown cell a heading
227 level = level || 1;
227 level = level || 1;
228 var source = this.get_text();
228 var source = this.get_text();
229 source = source.replace(/^(#*)\s?/,
229 source = source.replace(/^(#*)\s?/,
230 new Array(level + 1).join('#') + ' ');
230 new Array(level + 1).join('#') + ' ');
231 this.set_text(source);
231 this.set_text(source);
232 this.refresh();
232 this.refresh();
233 if (this.rendered) {
233 if (this.rendered) {
234 this.render();
234 this.render();
235 }
235 }
236 };
236 };
237
237
238 /**
238 /**
239 * @method render
239 * @method render
240 */
240 */
241 MarkdownCell.prototype.render = function () {
241 MarkdownCell.prototype.render = function () {
242 var cont = TextCell.prototype.render.apply(this);
242 var cont = TextCell.prototype.render.apply(this);
243 if (cont) {
243 if (cont) {
244 var that = this;
244 var text = this.get_text();
245 var text = this.get_text();
245 var math = null;
246 var math = null;
246 if (text === "") { text = this.placeholder; }
247 if (text === "") { text = this.placeholder; }
247 var text_and_math = mathjaxutils.remove_math(text);
248 var text_and_math = mathjaxutils.remove_math(text);
248 text = text_and_math[0];
249 text = text_and_math[0];
249 math = text_and_math[1];
250 math = text_and_math[1];
250 var html = marked.parser(marked.lexer(text));
251 marked(text, function (err, html) {
251 html = mathjaxutils.replace_math(html, math);
252 html = mathjaxutils.replace_math(html, math);
252 html = security.sanitize_html(html);
253 html = security.sanitize_html(html);
253 html = $($.parseHTML(html));
254 html = $($.parseHTML(html));
254 // add anchors to headings
255 // add anchors to headings
255 // console.log(html);
256 html.find(":header").addBack(":header").each(function (i, h) {
256 html.find(":header").addBack(":header").each(function (i, h) {
257 h = $(h);
257 h = $(h);
258 var hash = h.text().replace(/ /g, '-');
258 var hash = h.text().replace(/ /g, '-');
259 h.attr('id', hash);
259 h.attr('id', hash);
260 h.append(
260 h.append(
261 $('<a/>')
261 $('<a/>')
262 .addClass('anchor-link')
262 .addClass('anchor-link')
263 .attr('href', '#' + hash)
263 .attr('href', '#' + hash)
264 .text('¶')
264 .text('¶')
265 );
265 );
266 })
266 });
267 // links in markdown cells should open in new tabs
267 // links in markdown cells should open in new tabs
268 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
268 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
269 this.set_rendered(html);
269 that.set_rendered(html);
270 this.typeset();
270 that.typeset();
271 this.events.trigger("rendered.MarkdownCell", {cell: this})
271 that.events.trigger("rendered.MarkdownCell", {cell: that});
272 });
272 }
273 }
273 return cont;
274 return cont;
274 };
275 };
275
276
276
277
277 var RawCell = function (options) {
278 var RawCell = function (options) {
278 // Constructor
279 // Constructor
279 //
280 //
280 // Parameters:
281 // Parameters:
281 // options: dictionary
282 // options: dictionary
282 // Dictionary of keyword arguments.
283 // Dictionary of keyword arguments.
283 // events: $(Events) instance
284 // events: $(Events) instance
284 // config: dictionary
285 // config: dictionary
285 // keyboard_manager: KeyboardManager instance
286 // keyboard_manager: KeyboardManager instance
286 // notebook: Notebook instance
287 // notebook: Notebook instance
287 options = options || {};
288 options = options || {};
288 var config = utils.mergeopt(RawCell, options.config);
289 var config = utils.mergeopt(RawCell, options.config);
289 TextCell.apply(this, [$.extend({}, options, {config: config})]);
290 TextCell.apply(this, [$.extend({}, options, {config: config})]);
290
291
291 this.cell_type = 'raw';
292 this.cell_type = 'raw';
292 };
293 };
293
294
294 RawCell.options_default = {
295 RawCell.options_default = {
295 placeholder : "Write raw LaTeX or other formats here, for use with nbconvert. " +
296 placeholder : "Write raw LaTeX or other formats here, for use with nbconvert. " +
296 "It will not be rendered in the notebook. " +
297 "It will not be rendered in the notebook. " +
297 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
298 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
298 };
299 };
299
300
300 RawCell.prototype = Object.create(TextCell.prototype);
301 RawCell.prototype = Object.create(TextCell.prototype);
301
302
302 /** @method bind_events **/
303 /** @method bind_events **/
303 RawCell.prototype.bind_events = function () {
304 RawCell.prototype.bind_events = function () {
304 TextCell.prototype.bind_events.apply(this);
305 TextCell.prototype.bind_events.apply(this);
305 var that = this;
306 var that = this;
306 this.element.focusout(function() {
307 this.element.focusout(function() {
307 that.auto_highlight();
308 that.auto_highlight();
308 that.render();
309 that.render();
309 });
310 });
310
311
311 this.code_mirror.on('focus', function() { that.unrender(); });
312 this.code_mirror.on('focus', function() { that.unrender(); });
312 };
313 };
313
314
314 /**
315 /**
315 * Trigger autodetection of highlight scheme for current cell
316 * Trigger autodetection of highlight scheme for current cell
316 * @method auto_highlight
317 * @method auto_highlight
317 */
318 */
318 RawCell.prototype.auto_highlight = function () {
319 RawCell.prototype.auto_highlight = function () {
319 this._auto_highlight(this.config.raw_cell_highlight);
320 this._auto_highlight(this.config.raw_cell_highlight);
320 };
321 };
321
322
322 /** @method render **/
323 /** @method render **/
323 RawCell.prototype.render = function () {
324 RawCell.prototype.render = function () {
324 var cont = TextCell.prototype.render.apply(this);
325 var cont = TextCell.prototype.render.apply(this);
325 if (cont){
326 if (cont){
326 var text = this.get_text();
327 var text = this.get_text();
327 if (text === "") { text = this.placeholder; }
328 if (text === "") { text = this.placeholder; }
328 this.set_text(text);
329 this.set_text(text);
329 this.element.removeClass('rendered');
330 this.element.removeClass('rendered');
330 }
331 }
331 return cont;
332 return cont;
332 };
333 };
333
334
334 // Backwards compatability.
335 // Backwards compatability.
335 IPython.TextCell = TextCell;
336 IPython.TextCell = TextCell;
336 IPython.MarkdownCell = MarkdownCell;
337 IPython.MarkdownCell = MarkdownCell;
337 IPython.RawCell = RawCell;
338 IPython.RawCell = RawCell;
338
339
339 var textcell = {
340 var textcell = {
340 TextCell: TextCell,
341 TextCell: TextCell,
341 MarkdownCell: MarkdownCell,
342 MarkdownCell: MarkdownCell,
342 RawCell: RawCell,
343 RawCell: RawCell,
343 };
344 };
344 return textcell;
345 return textcell;
345 });
346 });
@@ -1,108 +1,104
1 <!DOCTYPE HTML>
1 <!DOCTYPE HTML>
2 <html>
2 <html>
3
3
4 <head>
4 <head>
5 <meta charset="utf-8">
5 <meta charset="utf-8">
6
6
7 <title>{% block title %}IPython Notebook{% endblock %}</title>
7 <title>{% block title %}IPython Notebook{% endblock %}</title>
8 <link rel="shortcut icon" type="image/x-icon" href="{{static_url("base/images/favicon.ico") }}">
8 <link rel="shortcut icon" type="image/x-icon" href="{{static_url("base/images/favicon.ico") }}">
9 <meta http-equiv="X-UA-Compatible" content="chrome=1">
9 <meta http-equiv="X-UA-Compatible" content="chrome=1">
10 <link rel="stylesheet" href="{{static_url("components/jquery-ui/themes/smoothness/jquery-ui.min.css") }}" type="text/css" />
10 <link rel="stylesheet" href="{{static_url("components/jquery-ui/themes/smoothness/jquery-ui.min.css") }}" type="text/css" />
11 <meta name="viewport" content="width=device-width, initial-scale=1.0">
11 <meta name="viewport" content="width=device-width, initial-scale=1.0">
12
12
13 {% block stylesheet %}
13 {% block stylesheet %}
14 <link rel="stylesheet" href="{{ static_url("style/style.min.css") }}" type="text/css"/>
14 <link rel="stylesheet" href="{{ static_url("style/style.min.css") }}" type="text/css"/>
15 {% endblock %}
15 {% endblock %}
16 <link rel="stylesheet" href="{{ static_url("custom/custom.css") }}" type="text/css" />
16 <link rel="stylesheet" href="{{ static_url("custom/custom.css") }}" type="text/css" />
17 <script src="{{static_url("components/es6-promise/promise.min.js")}}" type="text/javascript" charset="utf-8"></script>
17 <script src="{{static_url("components/es6-promise/promise.min.js")}}" type="text/javascript" charset="utf-8"></script>
18 <script src="{{static_url("components/requirejs/require.js") }}" type="text/javascript" charset="utf-8"></script>
18 <script src="{{static_url("components/requirejs/require.js") }}" type="text/javascript" charset="utf-8"></script>
19 <script>
19 <script>
20 require.config({
20 require.config({
21 baseUrl: '{{static_url("", include_version=False)}}',
21 baseUrl: '{{static_url("", include_version=False)}}',
22 paths: {
22 paths: {
23 nbextensions : '{{ base_url }}nbextensions',
23 nbextensions : '{{ base_url }}nbextensions',
24 underscore : 'components/underscore/underscore-min',
24 underscore : 'components/underscore/underscore-min',
25 backbone : 'components/backbone/backbone-min',
25 backbone : 'components/backbone/backbone-min',
26 jquery: 'components/jquery/jquery.min',
26 jquery: 'components/jquery/jquery.min',
27 bootstrap: 'components/bootstrap/js/bootstrap.min',
27 bootstrap: 'components/bootstrap/js/bootstrap.min',
28 bootstraptour: 'components/bootstrap-tour/build/js/bootstrap-tour.min',
28 bootstraptour: 'components/bootstrap-tour/build/js/bootstrap-tour.min',
29 jqueryui: 'components/jquery-ui/ui/minified/jquery-ui.min',
29 jqueryui: 'components/jquery-ui/ui/minified/jquery-ui.min',
30 highlight: 'components/highlight.js/build/highlight.pack',
31 moment: "components/moment/moment",
30 moment: "components/moment/moment",
32 codemirror: 'components/codemirror',
31 codemirror: 'components/codemirror',
33 termjs: "components/term.js/src/term",
32 termjs: "components/term.js/src/term",
34 contents: '{{ contents_js_source }}',
33 contents: '{{ contents_js_source }}',
35 },
34 },
36 shim: {
35 shim: {
37 underscore: {
36 underscore: {
38 exports: '_'
37 exports: '_'
39 },
38 },
40 backbone: {
39 backbone: {
41 deps: ["underscore", "jquery"],
40 deps: ["underscore", "jquery"],
42 exports: "Backbone"
41 exports: "Backbone"
43 },
42 },
44 bootstrap: {
43 bootstrap: {
45 deps: ["jquery"],
44 deps: ["jquery"],
46 exports: "bootstrap"
45 exports: "bootstrap"
47 },
46 },
48 bootstraptour: {
47 bootstraptour: {
49 deps: ["bootstrap"],
48 deps: ["bootstrap"],
50 exports: "Tour"
49 exports: "Tour"
51 },
50 },
52 jqueryui: {
51 jqueryui: {
53 deps: ["jquery"],
52 deps: ["jquery"],
54 exports: "$"
53 exports: "$"
55 },
54 }
56 highlight: {
57 exports: "hljs"
58 },
59 }
55 }
60 });
56 });
61 </script>
57 </script>
62
58
63 {% block meta %}
59 {% block meta %}
64 {% endblock %}
60 {% endblock %}
65
61
66 </head>
62 </head>
67
63
68 <body {% block params %}{% endblock %}>
64 <body {% block params %}{% endblock %}>
69
65
70 <noscript>
66 <noscript>
71 <div id='noscript'>
67 <div id='noscript'>
72 IPython Notebook requires JavaScript.<br>
68 IPython Notebook requires JavaScript.<br>
73 Please enable it to proceed.
69 Please enable it to proceed.
74 </div>
70 </div>
75 </noscript>
71 </noscript>
76
72
77 <div id="header" class="navbar navbar-static-top">
73 <div id="header" class="navbar navbar-static-top">
78 <div class="container">
74 <div class="container">
79 <div id="ipython_notebook" class="nav navbar-brand pull-left"><a href="{{base_url}}tree" alt='dashboard'><img src='{{static_url("base/images/ipynblogo.png") }}' alt='IPython Notebook'/></a></div>
75 <div id="ipython_notebook" class="nav navbar-brand pull-left"><a href="{{base_url}}tree" alt='dashboard'><img src='{{static_url("base/images/ipynblogo.png") }}' alt='IPython Notebook'/></a></div>
80
76
81 {% block login_widget %}
77 {% block login_widget %}
82
78
83 <span id="login_widget">
79 <span id="login_widget">
84 {% if logged_in %}
80 {% if logged_in %}
85 <button id="logout">Logout</button>
81 <button id="logout">Logout</button>
86 {% elif login_available and not logged_in %}
82 {% elif login_available and not logged_in %}
87 <button id="login">Login</button>
83 <button id="login">Login</button>
88 {% endif %}
84 {% endif %}
89 </span>
85 </span>
90
86
91 {% endblock %}
87 {% endblock %}
92
88
93 {% block header %}
89 {% block header %}
94 {% endblock %}
90 {% endblock %}
95 </div>
91 </div>
96 </div>
92 </div>
97
93
98 <div id="site">
94 <div id="site">
99 {% block site %}
95 {% block site %}
100 {% endblock %}
96 {% endblock %}
101 </div>
97 </div>
102
98
103 {% block script %}
99 {% block script %}
104 {% endblock %}
100 {% endblock %}
105
101
106 </body>
102 </body>
107
103
108 </html>
104 </html>
@@ -1,755 +1,754
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 This module defines the things that are used in setup.py for building IPython
3 This module defines the things that are used in setup.py for building IPython
4
4
5 This includes:
5 This includes:
6
6
7 * The basic arguments to setup
7 * The basic arguments to setup
8 * Functions for finding things like packages, package data, etc.
8 * Functions for finding things like packages, package data, etc.
9 * A function for checking dependencies.
9 * A function for checking dependencies.
10 """
10 """
11
11
12 # Copyright (c) IPython Development Team.
12 # Copyright (c) IPython Development Team.
13 # Distributed under the terms of the Modified BSD License.
13 # Distributed under the terms of the Modified BSD License.
14
14
15 from __future__ import print_function
15 from __future__ import print_function
16
16
17 import errno
17 import errno
18 import os
18 import os
19 import sys
19 import sys
20
20
21 from distutils import log
21 from distutils import log
22 from distutils.command.build_py import build_py
22 from distutils.command.build_py import build_py
23 from distutils.command.build_scripts import build_scripts
23 from distutils.command.build_scripts import build_scripts
24 from distutils.command.install import install
24 from distutils.command.install import install
25 from distutils.command.install_scripts import install_scripts
25 from distutils.command.install_scripts import install_scripts
26 from distutils.cmd import Command
26 from distutils.cmd import Command
27 from fnmatch import fnmatch
27 from fnmatch import fnmatch
28 from glob import glob
28 from glob import glob
29 from subprocess import check_call
29 from subprocess import check_call
30
30
31 from setupext import install_data_ext
31 from setupext import install_data_ext
32
32
33 #-------------------------------------------------------------------------------
33 #-------------------------------------------------------------------------------
34 # Useful globals and utility functions
34 # Useful globals and utility functions
35 #-------------------------------------------------------------------------------
35 #-------------------------------------------------------------------------------
36
36
37 # A few handy globals
37 # A few handy globals
38 isfile = os.path.isfile
38 isfile = os.path.isfile
39 pjoin = os.path.join
39 pjoin = os.path.join
40 repo_root = os.path.dirname(os.path.abspath(__file__))
40 repo_root = os.path.dirname(os.path.abspath(__file__))
41
41
42 def oscmd(s):
42 def oscmd(s):
43 print(">", s)
43 print(">", s)
44 os.system(s)
44 os.system(s)
45
45
46 # Py3 compatibility hacks, without assuming IPython itself is installed with
46 # Py3 compatibility hacks, without assuming IPython itself is installed with
47 # the full py3compat machinery.
47 # the full py3compat machinery.
48
48
49 try:
49 try:
50 execfile
50 execfile
51 except NameError:
51 except NameError:
52 def execfile(fname, globs, locs=None):
52 def execfile(fname, globs, locs=None):
53 locs = locs or globs
53 locs = locs or globs
54 exec(compile(open(fname).read(), fname, "exec"), globs, locs)
54 exec(compile(open(fname).read(), fname, "exec"), globs, locs)
55
55
56 # A little utility we'll need below, since glob() does NOT allow you to do
56 # A little utility we'll need below, since glob() does NOT allow you to do
57 # exclusion on multiple endings!
57 # exclusion on multiple endings!
58 def file_doesnt_endwith(test,endings):
58 def file_doesnt_endwith(test,endings):
59 """Return true if test is a file and its name does NOT end with any
59 """Return true if test is a file and its name does NOT end with any
60 of the strings listed in endings."""
60 of the strings listed in endings."""
61 if not isfile(test):
61 if not isfile(test):
62 return False
62 return False
63 for e in endings:
63 for e in endings:
64 if test.endswith(e):
64 if test.endswith(e):
65 return False
65 return False
66 return True
66 return True
67
67
68 #---------------------------------------------------------------------------
68 #---------------------------------------------------------------------------
69 # Basic project information
69 # Basic project information
70 #---------------------------------------------------------------------------
70 #---------------------------------------------------------------------------
71
71
72 # release.py contains version, authors, license, url, keywords, etc.
72 # release.py contains version, authors, license, url, keywords, etc.
73 execfile(pjoin(repo_root, 'IPython','core','release.py'), globals())
73 execfile(pjoin(repo_root, 'IPython','core','release.py'), globals())
74
74
75 # Create a dict with the basic information
75 # Create a dict with the basic information
76 # This dict is eventually passed to setup after additional keys are added.
76 # This dict is eventually passed to setup after additional keys are added.
77 setup_args = dict(
77 setup_args = dict(
78 name = name,
78 name = name,
79 version = version,
79 version = version,
80 description = description,
80 description = description,
81 long_description = long_description,
81 long_description = long_description,
82 author = author,
82 author = author,
83 author_email = author_email,
83 author_email = author_email,
84 url = url,
84 url = url,
85 download_url = download_url,
85 download_url = download_url,
86 license = license,
86 license = license,
87 platforms = platforms,
87 platforms = platforms,
88 keywords = keywords,
88 keywords = keywords,
89 classifiers = classifiers,
89 classifiers = classifiers,
90 cmdclass = {'install_data': install_data_ext},
90 cmdclass = {'install_data': install_data_ext},
91 )
91 )
92
92
93
93
94 #---------------------------------------------------------------------------
94 #---------------------------------------------------------------------------
95 # Find packages
95 # Find packages
96 #---------------------------------------------------------------------------
96 #---------------------------------------------------------------------------
97
97
98 def find_packages():
98 def find_packages():
99 """
99 """
100 Find all of IPython's packages.
100 Find all of IPython's packages.
101 """
101 """
102 excludes = ['deathrow', 'quarantine']
102 excludes = ['deathrow', 'quarantine']
103 packages = []
103 packages = []
104 for dir,subdirs,files in os.walk('IPython'):
104 for dir,subdirs,files in os.walk('IPython'):
105 package = dir.replace(os.path.sep, '.')
105 package = dir.replace(os.path.sep, '.')
106 if any(package.startswith('IPython.'+exc) for exc in excludes):
106 if any(package.startswith('IPython.'+exc) for exc in excludes):
107 # package is to be excluded (e.g. deathrow)
107 # package is to be excluded (e.g. deathrow)
108 continue
108 continue
109 if '__init__.py' not in files:
109 if '__init__.py' not in files:
110 # not a package
110 # not a package
111 continue
111 continue
112 packages.append(package)
112 packages.append(package)
113 return packages
113 return packages
114
114
115 #---------------------------------------------------------------------------
115 #---------------------------------------------------------------------------
116 # Find package data
116 # Find package data
117 #---------------------------------------------------------------------------
117 #---------------------------------------------------------------------------
118
118
119 def find_package_data():
119 def find_package_data():
120 """
120 """
121 Find IPython's package_data.
121 Find IPython's package_data.
122 """
122 """
123 # This is not enough for these things to appear in an sdist.
123 # This is not enough for these things to appear in an sdist.
124 # We need to muck with the MANIFEST to get this to work
124 # We need to muck with the MANIFEST to get this to work
125
125
126 # exclude components and less from the walk;
126 # exclude components and less from the walk;
127 # we will build the components separately
127 # we will build the components separately
128 excludes = [
128 excludes = [
129 pjoin('static', 'components'),
129 pjoin('static', 'components'),
130 pjoin('static', '*', 'less'),
130 pjoin('static', '*', 'less'),
131 ]
131 ]
132
132
133 # walk notebook resources:
133 # walk notebook resources:
134 cwd = os.getcwd()
134 cwd = os.getcwd()
135 os.chdir(os.path.join('IPython', 'html'))
135 os.chdir(os.path.join('IPython', 'html'))
136 static_data = []
136 static_data = []
137 for parent, dirs, files in os.walk('static'):
137 for parent, dirs, files in os.walk('static'):
138 if any(fnmatch(parent, pat) for pat in excludes):
138 if any(fnmatch(parent, pat) for pat in excludes):
139 # prevent descending into subdirs
139 # prevent descending into subdirs
140 dirs[:] = []
140 dirs[:] = []
141 continue
141 continue
142 for f in files:
142 for f in files:
143 static_data.append(pjoin(parent, f))
143 static_data.append(pjoin(parent, f))
144
144
145 components = pjoin("static", "components")
145 components = pjoin("static", "components")
146 # select the components we actually need to install
146 # select the components we actually need to install
147 # (there are lots of resources we bundle for sdist-reasons that we don't actually use)
147 # (there are lots of resources we bundle for sdist-reasons that we don't actually use)
148 static_data.extend([
148 static_data.extend([
149 pjoin(components, "backbone", "backbone-min.js"),
149 pjoin(components, "backbone", "backbone-min.js"),
150 pjoin(components, "bootstrap", "js", "bootstrap.min.js"),
150 pjoin(components, "bootstrap", "js", "bootstrap.min.js"),
151 pjoin(components, "bootstrap-tour", "build", "css", "bootstrap-tour.min.css"),
151 pjoin(components, "bootstrap-tour", "build", "css", "bootstrap-tour.min.css"),
152 pjoin(components, "bootstrap-tour", "build", "js", "bootstrap-tour.min.js"),
152 pjoin(components, "bootstrap-tour", "build", "js", "bootstrap-tour.min.js"),
153 pjoin(components, "es6-promise", "*.js"),
153 pjoin(components, "es6-promise", "*.js"),
154 pjoin(components, "font-awesome", "fonts", "*.*"),
154 pjoin(components, "font-awesome", "fonts", "*.*"),
155 pjoin(components, "google-caja", "html-css-sanitizer-minified.js"),
155 pjoin(components, "google-caja", "html-css-sanitizer-minified.js"),
156 pjoin(components, "highlight.js", "build", "highlight.pack.js"),
157 pjoin(components, "jquery", "jquery.min.js"),
156 pjoin(components, "jquery", "jquery.min.js"),
158 pjoin(components, "jquery-ui", "ui", "minified", "jquery-ui.min.js"),
157 pjoin(components, "jquery-ui", "ui", "minified", "jquery-ui.min.js"),
159 pjoin(components, "jquery-ui", "themes", "smoothness", "jquery-ui.min.css"),
158 pjoin(components, "jquery-ui", "themes", "smoothness", "jquery-ui.min.css"),
160 pjoin(components, "jquery-ui", "themes", "smoothness", "images", "*"),
159 pjoin(components, "jquery-ui", "themes", "smoothness", "images", "*"),
161 pjoin(components, "marked", "lib", "marked.js"),
160 pjoin(components, "marked", "lib", "marked.js"),
162 pjoin(components, "requirejs", "require.js"),
161 pjoin(components, "requirejs", "require.js"),
163 pjoin(components, "underscore", "underscore-min.js"),
162 pjoin(components, "underscore", "underscore-min.js"),
164 pjoin(components, "moment", "moment.js"),
163 pjoin(components, "moment", "moment.js"),
165 pjoin(components, "moment", "min", "moment.min.js"),
164 pjoin(components, "moment", "min", "moment.min.js"),
166 pjoin(components, "term.js", "src", "term.js"),
165 pjoin(components, "term.js", "src", "term.js"),
167 pjoin(components, "text-encoding", "lib", "encoding.js"),
166 pjoin(components, "text-encoding", "lib", "encoding.js"),
168 ])
167 ])
169
168
170 # Ship all of Codemirror's CSS and JS
169 # Ship all of Codemirror's CSS and JS
171 for parent, dirs, files in os.walk(pjoin(components, 'codemirror')):
170 for parent, dirs, files in os.walk(pjoin(components, 'codemirror')):
172 for f in files:
171 for f in files:
173 if f.endswith(('.js', '.css')):
172 if f.endswith(('.js', '.css')):
174 static_data.append(pjoin(parent, f))
173 static_data.append(pjoin(parent, f))
175
174
176 os.chdir(os.path.join('tests',))
175 os.chdir(os.path.join('tests',))
177 js_tests = glob('*.js') + glob('*/*.js')
176 js_tests = glob('*.js') + glob('*/*.js')
178
177
179 os.chdir(os.path.join(cwd, 'IPython', 'nbconvert'))
178 os.chdir(os.path.join(cwd, 'IPython', 'nbconvert'))
180 nbconvert_templates = [os.path.join(dirpath, '*.*')
179 nbconvert_templates = [os.path.join(dirpath, '*.*')
181 for dirpath, _, _ in os.walk('templates')]
180 for dirpath, _, _ in os.walk('templates')]
182
181
183 os.chdir(cwd)
182 os.chdir(cwd)
184
183
185 package_data = {
184 package_data = {
186 'IPython.config.profile' : ['README*', '*/*.py'],
185 'IPython.config.profile' : ['README*', '*/*.py'],
187 'IPython.core.tests' : ['*.png', '*.jpg'],
186 'IPython.core.tests' : ['*.png', '*.jpg'],
188 'IPython.lib.tests' : ['*.wav'],
187 'IPython.lib.tests' : ['*.wav'],
189 'IPython.testing.plugin' : ['*.txt'],
188 'IPython.testing.plugin' : ['*.txt'],
190 'IPython.html' : ['templates/*'] + static_data,
189 'IPython.html' : ['templates/*'] + static_data,
191 'IPython.html.tests' : js_tests,
190 'IPython.html.tests' : js_tests,
192 'IPython.qt.console' : ['resources/icon/*.svg'],
191 'IPython.qt.console' : ['resources/icon/*.svg'],
193 'IPython.nbconvert' : nbconvert_templates +
192 'IPython.nbconvert' : nbconvert_templates +
194 [
193 [
195 'tests/files/*.*',
194 'tests/files/*.*',
196 'exporters/tests/files/*.*',
195 'exporters/tests/files/*.*',
197 'preprocessors/tests/files/*.*',
196 'preprocessors/tests/files/*.*',
198 ],
197 ],
199 'IPython.nbconvert.filters' : ['marked.js'],
198 'IPython.nbconvert.filters' : ['marked.js'],
200 'IPython.nbformat' : [
199 'IPython.nbformat' : [
201 'tests/*.ipynb',
200 'tests/*.ipynb',
202 'v3/nbformat.v3.schema.json',
201 'v3/nbformat.v3.schema.json',
203 'v4/nbformat.v4.schema.json',
202 'v4/nbformat.v4.schema.json',
204 ]
203 ]
205 }
204 }
206
205
207 return package_data
206 return package_data
208
207
209
208
210 def check_package_data(package_data):
209 def check_package_data(package_data):
211 """verify that package_data globs make sense"""
210 """verify that package_data globs make sense"""
212 print("checking package data")
211 print("checking package data")
213 for pkg, data in package_data.items():
212 for pkg, data in package_data.items():
214 pkg_root = pjoin(*pkg.split('.'))
213 pkg_root = pjoin(*pkg.split('.'))
215 for d in data:
214 for d in data:
216 path = pjoin(pkg_root, d)
215 path = pjoin(pkg_root, d)
217 if '*' in path:
216 if '*' in path:
218 assert len(glob(path)) > 0, "No files match pattern %s" % path
217 assert len(glob(path)) > 0, "No files match pattern %s" % path
219 else:
218 else:
220 assert os.path.exists(path), "Missing package data: %s" % path
219 assert os.path.exists(path), "Missing package data: %s" % path
221
220
222
221
223 def check_package_data_first(command):
222 def check_package_data_first(command):
224 """decorator for checking package_data before running a given command
223 """decorator for checking package_data before running a given command
225
224
226 Probably only needs to wrap build_py
225 Probably only needs to wrap build_py
227 """
226 """
228 class DecoratedCommand(command):
227 class DecoratedCommand(command):
229 def run(self):
228 def run(self):
230 check_package_data(self.package_data)
229 check_package_data(self.package_data)
231 command.run(self)
230 command.run(self)
232 return DecoratedCommand
231 return DecoratedCommand
233
232
234
233
235 #---------------------------------------------------------------------------
234 #---------------------------------------------------------------------------
236 # Find data files
235 # Find data files
237 #---------------------------------------------------------------------------
236 #---------------------------------------------------------------------------
238
237
239 def make_dir_struct(tag,base,out_base):
238 def make_dir_struct(tag,base,out_base):
240 """Make the directory structure of all files below a starting dir.
239 """Make the directory structure of all files below a starting dir.
241
240
242 This is just a convenience routine to help build a nested directory
241 This is just a convenience routine to help build a nested directory
243 hierarchy because distutils is too stupid to do this by itself.
242 hierarchy because distutils is too stupid to do this by itself.
244
243
245 XXX - this needs a proper docstring!
244 XXX - this needs a proper docstring!
246 """
245 """
247
246
248 # we'll use these a lot below
247 # we'll use these a lot below
249 lbase = len(base)
248 lbase = len(base)
250 pathsep = os.path.sep
249 pathsep = os.path.sep
251 lpathsep = len(pathsep)
250 lpathsep = len(pathsep)
252
251
253 out = []
252 out = []
254 for (dirpath,dirnames,filenames) in os.walk(base):
253 for (dirpath,dirnames,filenames) in os.walk(base):
255 # we need to strip out the dirpath from the base to map it to the
254 # we need to strip out the dirpath from the base to map it to the
256 # output (installation) path. This requires possibly stripping the
255 # output (installation) path. This requires possibly stripping the
257 # path separator, because otherwise pjoin will not work correctly
256 # path separator, because otherwise pjoin will not work correctly
258 # (pjoin('foo/','/bar') returns '/bar').
257 # (pjoin('foo/','/bar') returns '/bar').
259
258
260 dp_eff = dirpath[lbase:]
259 dp_eff = dirpath[lbase:]
261 if dp_eff.startswith(pathsep):
260 if dp_eff.startswith(pathsep):
262 dp_eff = dp_eff[lpathsep:]
261 dp_eff = dp_eff[lpathsep:]
263 # The output path must be anchored at the out_base marker
262 # The output path must be anchored at the out_base marker
264 out_path = pjoin(out_base,dp_eff)
263 out_path = pjoin(out_base,dp_eff)
265 # Now we can generate the final filenames. Since os.walk only produces
264 # Now we can generate the final filenames. Since os.walk only produces
266 # filenames, we must join back with the dirpath to get full valid file
265 # filenames, we must join back with the dirpath to get full valid file
267 # paths:
266 # paths:
268 pfiles = [pjoin(dirpath,f) for f in filenames]
267 pfiles = [pjoin(dirpath,f) for f in filenames]
269 # Finally, generate the entry we need, which is a pari of (output
268 # Finally, generate the entry we need, which is a pari of (output
270 # path, files) for use as a data_files parameter in install_data.
269 # path, files) for use as a data_files parameter in install_data.
271 out.append((out_path, pfiles))
270 out.append((out_path, pfiles))
272
271
273 return out
272 return out
274
273
275
274
276 def find_data_files():
275 def find_data_files():
277 """
276 """
278 Find IPython's data_files.
277 Find IPython's data_files.
279
278
280 Just man pages at this point.
279 Just man pages at this point.
281 """
280 """
282
281
283 manpagebase = pjoin('share', 'man', 'man1')
282 manpagebase = pjoin('share', 'man', 'man1')
284
283
285 # Simple file lists can be made by hand
284 # Simple file lists can be made by hand
286 manpages = [f for f in glob(pjoin('docs','man','*.1.gz')) if isfile(f)]
285 manpages = [f for f in glob(pjoin('docs','man','*.1.gz')) if isfile(f)]
287 if not manpages:
286 if not manpages:
288 # When running from a source tree, the manpages aren't gzipped
287 # When running from a source tree, the manpages aren't gzipped
289 manpages = [f for f in glob(pjoin('docs','man','*.1')) if isfile(f)]
288 manpages = [f for f in glob(pjoin('docs','man','*.1')) if isfile(f)]
290
289
291 # And assemble the entire output list
290 # And assemble the entire output list
292 data_files = [ (manpagebase, manpages) ]
291 data_files = [ (manpagebase, manpages) ]
293
292
294 return data_files
293 return data_files
295
294
296
295
297 def make_man_update_target(manpage):
296 def make_man_update_target(manpage):
298 """Return a target_update-compliant tuple for the given manpage.
297 """Return a target_update-compliant tuple for the given manpage.
299
298
300 Parameters
299 Parameters
301 ----------
300 ----------
302 manpage : string
301 manpage : string
303 Name of the manpage, must include the section number (trailing number).
302 Name of the manpage, must include the section number (trailing number).
304
303
305 Example
304 Example
306 -------
305 -------
307
306
308 >>> make_man_update_target('ipython.1') #doctest: +NORMALIZE_WHITESPACE
307 >>> make_man_update_target('ipython.1') #doctest: +NORMALIZE_WHITESPACE
309 ('docs/man/ipython.1.gz',
308 ('docs/man/ipython.1.gz',
310 ['docs/man/ipython.1'],
309 ['docs/man/ipython.1'],
311 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz')
310 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz')
312 """
311 """
313 man_dir = pjoin('docs', 'man')
312 man_dir = pjoin('docs', 'man')
314 manpage_gz = manpage + '.gz'
313 manpage_gz = manpage + '.gz'
315 manpath = pjoin(man_dir, manpage)
314 manpath = pjoin(man_dir, manpage)
316 manpath_gz = pjoin(man_dir, manpage_gz)
315 manpath_gz = pjoin(man_dir, manpage_gz)
317 gz_cmd = ( "cd %(man_dir)s && gzip -9c %(manpage)s > %(manpage_gz)s" %
316 gz_cmd = ( "cd %(man_dir)s && gzip -9c %(manpage)s > %(manpage_gz)s" %
318 locals() )
317 locals() )
319 return (manpath_gz, [manpath], gz_cmd)
318 return (manpath_gz, [manpath], gz_cmd)
320
319
321 # The two functions below are copied from IPython.utils.path, so we don't need
320 # The two functions below are copied from IPython.utils.path, so we don't need
322 # to import IPython during setup, which fails on Python 3.
321 # to import IPython during setup, which fails on Python 3.
323
322
324 def target_outdated(target,deps):
323 def target_outdated(target,deps):
325 """Determine whether a target is out of date.
324 """Determine whether a target is out of date.
326
325
327 target_outdated(target,deps) -> 1/0
326 target_outdated(target,deps) -> 1/0
328
327
329 deps: list of filenames which MUST exist.
328 deps: list of filenames which MUST exist.
330 target: single filename which may or may not exist.
329 target: single filename which may or may not exist.
331
330
332 If target doesn't exist or is older than any file listed in deps, return
331 If target doesn't exist or is older than any file listed in deps, return
333 true, otherwise return false.
332 true, otherwise return false.
334 """
333 """
335 try:
334 try:
336 target_time = os.path.getmtime(target)
335 target_time = os.path.getmtime(target)
337 except os.error:
336 except os.error:
338 return 1
337 return 1
339 for dep in deps:
338 for dep in deps:
340 dep_time = os.path.getmtime(dep)
339 dep_time = os.path.getmtime(dep)
341 if dep_time > target_time:
340 if dep_time > target_time:
342 #print "For target",target,"Dep failed:",dep # dbg
341 #print "For target",target,"Dep failed:",dep # dbg
343 #print "times (dep,tar):",dep_time,target_time # dbg
342 #print "times (dep,tar):",dep_time,target_time # dbg
344 return 1
343 return 1
345 return 0
344 return 0
346
345
347
346
348 def target_update(target,deps,cmd):
347 def target_update(target,deps,cmd):
349 """Update a target with a given command given a list of dependencies.
348 """Update a target with a given command given a list of dependencies.
350
349
351 target_update(target,deps,cmd) -> runs cmd if target is outdated.
350 target_update(target,deps,cmd) -> runs cmd if target is outdated.
352
351
353 This is just a wrapper around target_outdated() which calls the given
352 This is just a wrapper around target_outdated() which calls the given
354 command if target is outdated."""
353 command if target is outdated."""
355
354
356 if target_outdated(target,deps):
355 if target_outdated(target,deps):
357 os.system(cmd)
356 os.system(cmd)
358
357
359 #---------------------------------------------------------------------------
358 #---------------------------------------------------------------------------
360 # Find scripts
359 # Find scripts
361 #---------------------------------------------------------------------------
360 #---------------------------------------------------------------------------
362
361
363 def find_entry_points():
362 def find_entry_points():
364 """Defines the command line entry points for IPython
363 """Defines the command line entry points for IPython
365
364
366 This always uses setuptools-style entry points. When setuptools is not in
365 This always uses setuptools-style entry points. When setuptools is not in
367 use, our own build_scripts_entrypt class below parses these and builds
366 use, our own build_scripts_entrypt class below parses these and builds
368 command line scripts.
367 command line scripts.
369
368
370 Each of our entry points gets both a plain name, e.g. ipython, and one
369 Each of our entry points gets both a plain name, e.g. ipython, and one
371 suffixed with the Python major version number, e.g. ipython3.
370 suffixed with the Python major version number, e.g. ipython3.
372 """
371 """
373 ep = [
372 ep = [
374 'ipython%s = IPython:start_ipython',
373 'ipython%s = IPython:start_ipython',
375 'ipcontroller%s = IPython.parallel.apps.ipcontrollerapp:launch_new_instance',
374 'ipcontroller%s = IPython.parallel.apps.ipcontrollerapp:launch_new_instance',
376 'ipengine%s = IPython.parallel.apps.ipengineapp:launch_new_instance',
375 'ipengine%s = IPython.parallel.apps.ipengineapp:launch_new_instance',
377 'ipcluster%s = IPython.parallel.apps.ipclusterapp:launch_new_instance',
376 'ipcluster%s = IPython.parallel.apps.ipclusterapp:launch_new_instance',
378 'iptest%s = IPython.testing.iptestcontroller:main',
377 'iptest%s = IPython.testing.iptestcontroller:main',
379 ]
378 ]
380 suffix = str(sys.version_info[0])
379 suffix = str(sys.version_info[0])
381 return [e % '' for e in ep] + [e % suffix for e in ep]
380 return [e % '' for e in ep] + [e % suffix for e in ep]
382
381
383 script_src = """#!{executable}
382 script_src = """#!{executable}
384 # This script was automatically generated by setup.py
383 # This script was automatically generated by setup.py
385 if __name__ == '__main__':
384 if __name__ == '__main__':
386 from {mod} import {func}
385 from {mod} import {func}
387 {func}()
386 {func}()
388 """
387 """
389
388
390 class build_scripts_entrypt(build_scripts):
389 class build_scripts_entrypt(build_scripts):
391 """Build the command line scripts
390 """Build the command line scripts
392
391
393 Parse setuptools style entry points and write simple scripts to run the
392 Parse setuptools style entry points and write simple scripts to run the
394 target functions.
393 target functions.
395
394
396 On Windows, this also creates .cmd wrappers for the scripts so that you can
395 On Windows, this also creates .cmd wrappers for the scripts so that you can
397 easily launch them from a command line.
396 easily launch them from a command line.
398 """
397 """
399 def run(self):
398 def run(self):
400 self.mkpath(self.build_dir)
399 self.mkpath(self.build_dir)
401 outfiles = []
400 outfiles = []
402 for script in find_entry_points():
401 for script in find_entry_points():
403 name, entrypt = script.split('=')
402 name, entrypt = script.split('=')
404 name = name.strip()
403 name = name.strip()
405 entrypt = entrypt.strip()
404 entrypt = entrypt.strip()
406 outfile = os.path.join(self.build_dir, name)
405 outfile = os.path.join(self.build_dir, name)
407 outfiles.append(outfile)
406 outfiles.append(outfile)
408 print('Writing script to', outfile)
407 print('Writing script to', outfile)
409
408
410 mod, func = entrypt.split(':')
409 mod, func = entrypt.split(':')
411 with open(outfile, 'w') as f:
410 with open(outfile, 'w') as f:
412 f.write(script_src.format(executable=sys.executable,
411 f.write(script_src.format(executable=sys.executable,
413 mod=mod, func=func))
412 mod=mod, func=func))
414
413
415 if sys.platform == 'win32':
414 if sys.platform == 'win32':
416 # Write .cmd wrappers for Windows so 'ipython' etc. work at the
415 # Write .cmd wrappers for Windows so 'ipython' etc. work at the
417 # command line
416 # command line
418 cmd_file = os.path.join(self.build_dir, name + '.cmd')
417 cmd_file = os.path.join(self.build_dir, name + '.cmd')
419 cmd = '@"{python}" "%~dp0\{script}" %*\r\n'.format(
418 cmd = '@"{python}" "%~dp0\{script}" %*\r\n'.format(
420 python=sys.executable, script=name)
419 python=sys.executable, script=name)
421 log.info("Writing %s wrapper script" % cmd_file)
420 log.info("Writing %s wrapper script" % cmd_file)
422 with open(cmd_file, 'w') as f:
421 with open(cmd_file, 'w') as f:
423 f.write(cmd)
422 f.write(cmd)
424
423
425 return outfiles, outfiles
424 return outfiles, outfiles
426
425
427 class install_lib_symlink(Command):
426 class install_lib_symlink(Command):
428 user_options = [
427 user_options = [
429 ('install-dir=', 'd', "directory to install to"),
428 ('install-dir=', 'd', "directory to install to"),
430 ]
429 ]
431
430
432 def initialize_options(self):
431 def initialize_options(self):
433 self.install_dir = None
432 self.install_dir = None
434
433
435 def finalize_options(self):
434 def finalize_options(self):
436 self.set_undefined_options('symlink',
435 self.set_undefined_options('symlink',
437 ('install_lib', 'install_dir'),
436 ('install_lib', 'install_dir'),
438 )
437 )
439
438
440 def run(self):
439 def run(self):
441 if sys.platform == 'win32':
440 if sys.platform == 'win32':
442 raise Exception("This doesn't work on Windows.")
441 raise Exception("This doesn't work on Windows.")
443 pkg = os.path.join(os.getcwd(), 'IPython')
442 pkg = os.path.join(os.getcwd(), 'IPython')
444 dest = os.path.join(self.install_dir, 'IPython')
443 dest = os.path.join(self.install_dir, 'IPython')
445 if os.path.islink(dest):
444 if os.path.islink(dest):
446 print('removing existing symlink at %s' % dest)
445 print('removing existing symlink at %s' % dest)
447 os.unlink(dest)
446 os.unlink(dest)
448 print('symlinking %s -> %s' % (pkg, dest))
447 print('symlinking %s -> %s' % (pkg, dest))
449 os.symlink(pkg, dest)
448 os.symlink(pkg, dest)
450
449
451 class unsymlink(install):
450 class unsymlink(install):
452 def run(self):
451 def run(self):
453 dest = os.path.join(self.install_lib, 'IPython')
452 dest = os.path.join(self.install_lib, 'IPython')
454 if os.path.islink(dest):
453 if os.path.islink(dest):
455 print('removing symlink at %s' % dest)
454 print('removing symlink at %s' % dest)
456 os.unlink(dest)
455 os.unlink(dest)
457 else:
456 else:
458 print('No symlink exists at %s' % dest)
457 print('No symlink exists at %s' % dest)
459
458
460 class install_symlinked(install):
459 class install_symlinked(install):
461 def run(self):
460 def run(self):
462 if sys.platform == 'win32':
461 if sys.platform == 'win32':
463 raise Exception("This doesn't work on Windows.")
462 raise Exception("This doesn't work on Windows.")
464
463
465 # Run all sub-commands (at least those that need to be run)
464 # Run all sub-commands (at least those that need to be run)
466 for cmd_name in self.get_sub_commands():
465 for cmd_name in self.get_sub_commands():
467 self.run_command(cmd_name)
466 self.run_command(cmd_name)
468
467
469 # 'sub_commands': a list of commands this command might have to run to
468 # 'sub_commands': a list of commands this command might have to run to
470 # get its work done. See cmd.py for more info.
469 # get its work done. See cmd.py for more info.
471 sub_commands = [('install_lib_symlink', lambda self:True),
470 sub_commands = [('install_lib_symlink', lambda self:True),
472 ('install_scripts_sym', lambda self:True),
471 ('install_scripts_sym', lambda self:True),
473 ]
472 ]
474
473
475 class install_scripts_for_symlink(install_scripts):
474 class install_scripts_for_symlink(install_scripts):
476 """Redefined to get options from 'symlink' instead of 'install'.
475 """Redefined to get options from 'symlink' instead of 'install'.
477
476
478 I love distutils almost as much as I love setuptools.
477 I love distutils almost as much as I love setuptools.
479 """
478 """
480 def finalize_options(self):
479 def finalize_options(self):
481 self.set_undefined_options('build', ('build_scripts', 'build_dir'))
480 self.set_undefined_options('build', ('build_scripts', 'build_dir'))
482 self.set_undefined_options('symlink',
481 self.set_undefined_options('symlink',
483 ('install_scripts', 'install_dir'),
482 ('install_scripts', 'install_dir'),
484 ('force', 'force'),
483 ('force', 'force'),
485 ('skip_build', 'skip_build'),
484 ('skip_build', 'skip_build'),
486 )
485 )
487
486
488 #---------------------------------------------------------------------------
487 #---------------------------------------------------------------------------
489 # Verify all dependencies
488 # Verify all dependencies
490 #---------------------------------------------------------------------------
489 #---------------------------------------------------------------------------
491
490
492 def check_for_dependencies():
491 def check_for_dependencies():
493 """Check for IPython's dependencies.
492 """Check for IPython's dependencies.
494
493
495 This function should NOT be called if running under setuptools!
494 This function should NOT be called if running under setuptools!
496 """
495 """
497 from setupext.setupext import (
496 from setupext.setupext import (
498 print_line, print_raw, print_status,
497 print_line, print_raw, print_status,
499 check_for_sphinx, check_for_pygments,
498 check_for_sphinx, check_for_pygments,
500 check_for_nose, check_for_pexpect,
499 check_for_nose, check_for_pexpect,
501 check_for_pyzmq, check_for_readline,
500 check_for_pyzmq, check_for_readline,
502 check_for_jinja2, check_for_tornado
501 check_for_jinja2, check_for_tornado
503 )
502 )
504 print_line()
503 print_line()
505 print_raw("BUILDING IPYTHON")
504 print_raw("BUILDING IPYTHON")
506 print_status('python', sys.version)
505 print_status('python', sys.version)
507 print_status('platform', sys.platform)
506 print_status('platform', sys.platform)
508 if sys.platform == 'win32':
507 if sys.platform == 'win32':
509 print_status('Windows version', sys.getwindowsversion())
508 print_status('Windows version', sys.getwindowsversion())
510
509
511 print_raw("")
510 print_raw("")
512 print_raw("OPTIONAL DEPENDENCIES")
511 print_raw("OPTIONAL DEPENDENCIES")
513
512
514 check_for_sphinx()
513 check_for_sphinx()
515 check_for_pygments()
514 check_for_pygments()
516 check_for_nose()
515 check_for_nose()
517 if os.name == 'posix':
516 if os.name == 'posix':
518 check_for_pexpect()
517 check_for_pexpect()
519 check_for_pyzmq()
518 check_for_pyzmq()
520 check_for_tornado()
519 check_for_tornado()
521 check_for_readline()
520 check_for_readline()
522 check_for_jinja2()
521 check_for_jinja2()
523
522
524 #---------------------------------------------------------------------------
523 #---------------------------------------------------------------------------
525 # VCS related
524 # VCS related
526 #---------------------------------------------------------------------------
525 #---------------------------------------------------------------------------
527
526
528 # utils.submodule has checks for submodule status
527 # utils.submodule has checks for submodule status
529 execfile(pjoin('IPython','utils','submodule.py'), globals())
528 execfile(pjoin('IPython','utils','submodule.py'), globals())
530
529
531 class UpdateSubmodules(Command):
530 class UpdateSubmodules(Command):
532 """Update git submodules
531 """Update git submodules
533
532
534 IPython's external javascript dependencies live in a separate repo.
533 IPython's external javascript dependencies live in a separate repo.
535 """
534 """
536 description = "Update git submodules"
535 description = "Update git submodules"
537 user_options = []
536 user_options = []
538
537
539 def initialize_options(self):
538 def initialize_options(self):
540 pass
539 pass
541
540
542 def finalize_options(self):
541 def finalize_options(self):
543 pass
542 pass
544
543
545 def run(self):
544 def run(self):
546 failure = False
545 failure = False
547 try:
546 try:
548 self.spawn('git submodule init'.split())
547 self.spawn('git submodule init'.split())
549 self.spawn('git submodule update --recursive'.split())
548 self.spawn('git submodule update --recursive'.split())
550 except Exception as e:
549 except Exception as e:
551 failure = e
550 failure = e
552 print(e)
551 print(e)
553
552
554 if not check_submodule_status(repo_root) == 'clean':
553 if not check_submodule_status(repo_root) == 'clean':
555 print("submodules could not be checked out")
554 print("submodules could not be checked out")
556 sys.exit(1)
555 sys.exit(1)
557
556
558
557
559 def git_prebuild(pkg_dir, build_cmd=build_py):
558 def git_prebuild(pkg_dir, build_cmd=build_py):
560 """Return extended build or sdist command class for recording commit
559 """Return extended build or sdist command class for recording commit
561
560
562 records git commit in IPython.utils._sysinfo.commit
561 records git commit in IPython.utils._sysinfo.commit
563
562
564 for use in IPython.utils.sysinfo.sys_info() calls after installation.
563 for use in IPython.utils.sysinfo.sys_info() calls after installation.
565
564
566 Also ensures that submodules exist prior to running
565 Also ensures that submodules exist prior to running
567 """
566 """
568
567
569 class MyBuildPy(build_cmd):
568 class MyBuildPy(build_cmd):
570 ''' Subclass to write commit data into installation tree '''
569 ''' Subclass to write commit data into installation tree '''
571 def run(self):
570 def run(self):
572 build_cmd.run(self)
571 build_cmd.run(self)
573 # this one will only fire for build commands
572 # this one will only fire for build commands
574 if hasattr(self, 'build_lib'):
573 if hasattr(self, 'build_lib'):
575 self._record_commit(self.build_lib)
574 self._record_commit(self.build_lib)
576
575
577 def make_release_tree(self, base_dir, files):
576 def make_release_tree(self, base_dir, files):
578 # this one will fire for sdist
577 # this one will fire for sdist
579 build_cmd.make_release_tree(self, base_dir, files)
578 build_cmd.make_release_tree(self, base_dir, files)
580 self._record_commit(base_dir)
579 self._record_commit(base_dir)
581
580
582 def _record_commit(self, base_dir):
581 def _record_commit(self, base_dir):
583 import subprocess
582 import subprocess
584 proc = subprocess.Popen('git rev-parse --short HEAD',
583 proc = subprocess.Popen('git rev-parse --short HEAD',
585 stdout=subprocess.PIPE,
584 stdout=subprocess.PIPE,
586 stderr=subprocess.PIPE,
585 stderr=subprocess.PIPE,
587 shell=True)
586 shell=True)
588 repo_commit, _ = proc.communicate()
587 repo_commit, _ = proc.communicate()
589 repo_commit = repo_commit.strip().decode("ascii")
588 repo_commit = repo_commit.strip().decode("ascii")
590
589
591 out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py')
590 out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py')
592 if os.path.isfile(out_pth) and not repo_commit:
591 if os.path.isfile(out_pth) and not repo_commit:
593 # nothing to write, don't clobber
592 # nothing to write, don't clobber
594 return
593 return
595
594
596 print("writing git commit '%s' to %s" % (repo_commit, out_pth))
595 print("writing git commit '%s' to %s" % (repo_commit, out_pth))
597
596
598 # remove to avoid overwriting original via hard link
597 # remove to avoid overwriting original via hard link
599 try:
598 try:
600 os.remove(out_pth)
599 os.remove(out_pth)
601 except (IOError, OSError):
600 except (IOError, OSError):
602 pass
601 pass
603 with open(out_pth, 'w') as out_file:
602 with open(out_pth, 'w') as out_file:
604 out_file.writelines([
603 out_file.writelines([
605 '# GENERATED BY setup.py\n',
604 '# GENERATED BY setup.py\n',
606 'commit = u"%s"\n' % repo_commit,
605 'commit = u"%s"\n' % repo_commit,
607 ])
606 ])
608 return require_submodules(MyBuildPy)
607 return require_submodules(MyBuildPy)
609
608
610
609
611 def require_submodules(command):
610 def require_submodules(command):
612 """decorator for instructing a command to check for submodules before running"""
611 """decorator for instructing a command to check for submodules before running"""
613 class DecoratedCommand(command):
612 class DecoratedCommand(command):
614 def run(self):
613 def run(self):
615 if not check_submodule_status(repo_root) == 'clean':
614 if not check_submodule_status(repo_root) == 'clean':
616 print("submodules missing! Run `setup.py submodule` and try again")
615 print("submodules missing! Run `setup.py submodule` and try again")
617 sys.exit(1)
616 sys.exit(1)
618 command.run(self)
617 command.run(self)
619 return DecoratedCommand
618 return DecoratedCommand
620
619
621 #---------------------------------------------------------------------------
620 #---------------------------------------------------------------------------
622 # bdist related
621 # bdist related
623 #---------------------------------------------------------------------------
622 #---------------------------------------------------------------------------
624
623
625 def get_bdist_wheel():
624 def get_bdist_wheel():
626 """Construct bdist_wheel command for building wheels
625 """Construct bdist_wheel command for building wheels
627
626
628 Constructs py2-none-any tag, instead of py2.7-none-any
627 Constructs py2-none-any tag, instead of py2.7-none-any
629 """
628 """
630 class RequiresWheel(Command):
629 class RequiresWheel(Command):
631 description = "Dummy command for missing bdist_wheel"
630 description = "Dummy command for missing bdist_wheel"
632 user_options = []
631 user_options = []
633
632
634 def initialize_options(self):
633 def initialize_options(self):
635 pass
634 pass
636
635
637 def finalize_options(self):
636 def finalize_options(self):
638 pass
637 pass
639
638
640 def run(self):
639 def run(self):
641 print("bdist_wheel requires the wheel package")
640 print("bdist_wheel requires the wheel package")
642 sys.exit(1)
641 sys.exit(1)
643
642
644 if 'setuptools' not in sys.modules:
643 if 'setuptools' not in sys.modules:
645 return RequiresWheel
644 return RequiresWheel
646 else:
645 else:
647 try:
646 try:
648 from wheel.bdist_wheel import bdist_wheel, read_pkg_info, write_pkg_info
647 from wheel.bdist_wheel import bdist_wheel, read_pkg_info, write_pkg_info
649 except ImportError:
648 except ImportError:
650 return RequiresWheel
649 return RequiresWheel
651
650
652 class bdist_wheel_tag(bdist_wheel):
651 class bdist_wheel_tag(bdist_wheel):
653
652
654 def add_requirements(self, metadata_path):
653 def add_requirements(self, metadata_path):
655 """transform platform-dependent requirements"""
654 """transform platform-dependent requirements"""
656 pkg_info = read_pkg_info(metadata_path)
655 pkg_info = read_pkg_info(metadata_path)
657 # pkg_info is an email.Message object (?!)
656 # pkg_info is an email.Message object (?!)
658 # we have to remove the unconditional 'readline' and/or 'pyreadline' entries
657 # we have to remove the unconditional 'readline' and/or 'pyreadline' entries
659 # and transform them to conditionals
658 # and transform them to conditionals
660 requires = pkg_info.get_all('Requires-Dist')
659 requires = pkg_info.get_all('Requires-Dist')
661 del pkg_info['Requires-Dist']
660 del pkg_info['Requires-Dist']
662 def _remove_startswith(lis, prefix):
661 def _remove_startswith(lis, prefix):
663 """like list.remove, but with startswith instead of =="""
662 """like list.remove, but with startswith instead of =="""
664 found = False
663 found = False
665 for idx, item in enumerate(lis):
664 for idx, item in enumerate(lis):
666 if item.startswith(prefix):
665 if item.startswith(prefix):
667 found = True
666 found = True
668 break
667 break
669 if found:
668 if found:
670 lis.pop(idx)
669 lis.pop(idx)
671
670
672 for pkg in ("gnureadline", "pyreadline", "mock"):
671 for pkg in ("gnureadline", "pyreadline", "mock"):
673 _remove_startswith(requires, pkg)
672 _remove_startswith(requires, pkg)
674 requires.append("gnureadline; sys.platform == 'darwin' and platform.python_implementation == 'CPython'")
673 requires.append("gnureadline; sys.platform == 'darwin' and platform.python_implementation == 'CPython'")
675 requires.append("pyreadline (>=2.0); extra == 'terminal' and sys.platform == 'win32' and platform.python_implementation == 'CPython'")
674 requires.append("pyreadline (>=2.0); extra == 'terminal' and sys.platform == 'win32' and platform.python_implementation == 'CPython'")
676 requires.append("pyreadline (>=2.0); extra == 'all' and sys.platform == 'win32' and platform.python_implementation == 'CPython'")
675 requires.append("pyreadline (>=2.0); extra == 'all' and sys.platform == 'win32' and platform.python_implementation == 'CPython'")
677 requires.append("mock; extra == 'test' and python_version < '3.3'")
676 requires.append("mock; extra == 'test' and python_version < '3.3'")
678 for r in requires:
677 for r in requires:
679 pkg_info['Requires-Dist'] = r
678 pkg_info['Requires-Dist'] = r
680 write_pkg_info(metadata_path, pkg_info)
679 write_pkg_info(metadata_path, pkg_info)
681
680
682 return bdist_wheel_tag
681 return bdist_wheel_tag
683
682
684 #---------------------------------------------------------------------------
683 #---------------------------------------------------------------------------
685 # Notebook related
684 # Notebook related
686 #---------------------------------------------------------------------------
685 #---------------------------------------------------------------------------
687
686
688 class CompileCSS(Command):
687 class CompileCSS(Command):
689 """Recompile Notebook CSS
688 """Recompile Notebook CSS
690
689
691 Regenerate the compiled CSS from LESS sources.
690 Regenerate the compiled CSS from LESS sources.
692
691
693 Requires various dev dependencies, such as invoke and lessc.
692 Requires various dev dependencies, such as invoke and lessc.
694 """
693 """
695 description = "Recompile Notebook CSS"
694 description = "Recompile Notebook CSS"
696 user_options = [
695 user_options = [
697 ('minify', 'x', "minify CSS"),
696 ('minify', 'x', "minify CSS"),
698 ('force', 'f', "force recompilation of CSS"),
697 ('force', 'f', "force recompilation of CSS"),
699 ]
698 ]
700
699
701 def initialize_options(self):
700 def initialize_options(self):
702 self.minify = False
701 self.minify = False
703 self.force = False
702 self.force = False
704
703
705 def finalize_options(self):
704 def finalize_options(self):
706 self.minify = bool(self.minify)
705 self.minify = bool(self.minify)
707 self.force = bool(self.force)
706 self.force = bool(self.force)
708
707
709 def run(self):
708 def run(self):
710 cmd = ['invoke', 'css']
709 cmd = ['invoke', 'css']
711 if self.minify:
710 if self.minify:
712 cmd.append('--minify')
711 cmd.append('--minify')
713 if self.force:
712 if self.force:
714 cmd.append('--force')
713 cmd.append('--force')
715 check_call(cmd, cwd=pjoin(repo_root, "IPython", "html"))
714 check_call(cmd, cwd=pjoin(repo_root, "IPython", "html"))
716
715
717
716
718 class JavascriptVersion(Command):
717 class JavascriptVersion(Command):
719 """write the javascript version to notebook javascript"""
718 """write the javascript version to notebook javascript"""
720 description = "Write IPython version to javascript"
719 description = "Write IPython version to javascript"
721 user_options = []
720 user_options = []
722
721
723 def initialize_options(self):
722 def initialize_options(self):
724 pass
723 pass
725
724
726 def finalize_options(self):
725 def finalize_options(self):
727 pass
726 pass
728
727
729 def run(self):
728 def run(self):
730 nsfile = pjoin(repo_root, "IPython", "html", "static", "base", "js", "namespace.js")
729 nsfile = pjoin(repo_root, "IPython", "html", "static", "base", "js", "namespace.js")
731 with open(nsfile) as f:
730 with open(nsfile) as f:
732 lines = f.readlines()
731 lines = f.readlines()
733 with open(nsfile, 'w') as f:
732 with open(nsfile, 'w') as f:
734 for line in lines:
733 for line in lines:
735 if line.startswith("IPython.version"):
734 if line.startswith("IPython.version"):
736 line = 'IPython.version = "{0}";\n'.format(version)
735 line = 'IPython.version = "{0}";\n'.format(version)
737 f.write(line)
736 f.write(line)
738
737
739
738
740 def css_js_prerelease(command, strict=True):
739 def css_js_prerelease(command, strict=True):
741 """decorator for building js/minified css prior to a release"""
740 """decorator for building js/minified css prior to a release"""
742 class DecoratedCommand(command):
741 class DecoratedCommand(command):
743 def run(self):
742 def run(self):
744 self.distribution.run_command('jsversion')
743 self.distribution.run_command('jsversion')
745 css = self.distribution.get_command_obj('css')
744 css = self.distribution.get_command_obj('css')
746 css.minify = True
745 css.minify = True
747 try:
746 try:
748 self.distribution.run_command('css')
747 self.distribution.run_command('css')
749 except Exception as e:
748 except Exception as e:
750 if strict:
749 if strict:
751 raise
750 raise
752 else:
751 else:
753 log.warn("Failed to build css sourcemaps: %s" % e)
752 log.warn("Failed to build css sourcemaps: %s" % e)
754 command.run(self)
753 command.run(self)
755 return DecoratedCommand
754 return DecoratedCommand
General Comments 0
You need to be logged in to leave comments. Login now