##// END OF EJS Templates
Don't always call focus_cell in Cell.command_mode....
Brian E. Granger -
Show More
@@ -1,533 +1,525
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // CodeCell
9 // CodeCell
10 //============================================================================
10 //============================================================================
11 /**
11 /**
12 * An extendable module that provide base functionnality to create cell for notebook.
12 * An extendable module that provide base functionnality to create cell for notebook.
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 * @submodule CodeCell
15 * @submodule CodeCell
16 */
16 */
17
17
18
18
19 /* local util for codemirror */
19 /* local util for codemirror */
20 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;};
20 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;};
21
21
22 /**
22 /**
23 *
23 *
24 * function to delete until previous non blanking space character
24 * function to delete until previous non blanking space character
25 * or first multiple of 4 tabstop.
25 * or first multiple of 4 tabstop.
26 * @private
26 * @private
27 */
27 */
28 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
28 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
29 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
29 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
30 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
30 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
31 var cur = cm.getCursor(), line = cm.getLine(cur.line);
31 var cur = cm.getCursor(), line = cm.getLine(cur.line);
32 var tabsize = cm.getOption('tabSize');
32 var tabsize = cm.getOption('tabSize');
33 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
33 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
34 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
34 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
35 var select = cm.getRange(from,cur);
35 var select = cm.getRange(from,cur);
36 if( select.match(/^\ +$/) !== null){
36 if( select.match(/^\ +$/) !== null){
37 cm.replaceRange("",from,cur);
37 cm.replaceRange("",from,cur);
38 } else {
38 } else {
39 cm.deleteH(-1,"char");
39 cm.deleteH(-1,"char");
40 }
40 }
41 };
41 };
42
42
43
43
44 var IPython = (function (IPython) {
44 var IPython = (function (IPython) {
45 "use strict";
45 "use strict";
46
46
47 var utils = IPython.utils;
47 var utils = IPython.utils;
48 var key = IPython.utils.keycodes;
48 var key = IPython.utils.keycodes;
49
49
50 /**
50 /**
51 * A Cell conceived to write code.
51 * A Cell conceived to write code.
52 *
52 *
53 * The kernel doesn't have to be set at creation time, in that case
53 * The kernel doesn't have to be set at creation time, in that case
54 * it will be null and set_kernel has to be called later.
54 * it will be null and set_kernel has to be called later.
55 * @class CodeCell
55 * @class CodeCell
56 * @extends IPython.Cell
56 * @extends IPython.Cell
57 *
57 *
58 * @constructor
58 * @constructor
59 * @param {Object|null} kernel
59 * @param {Object|null} kernel
60 * @param {object|undefined} [options]
60 * @param {object|undefined} [options]
61 * @param [options.cm_config] {object} config to pass to CodeMirror
61 * @param [options.cm_config] {object} config to pass to CodeMirror
62 */
62 */
63 var CodeCell = function (kernel, options) {
63 var CodeCell = function (kernel, options) {
64 this.kernel = kernel || null;
64 this.kernel = kernel || null;
65 this.collapsed = false;
65 this.collapsed = false;
66
66
67 // create all attributed in constructor function
67 // create all attributed in constructor function
68 // even if null for V8 VM optimisation
68 // even if null for V8 VM optimisation
69 this.input_prompt_number = null;
69 this.input_prompt_number = null;
70 this.celltoolbar = null;
70 this.celltoolbar = null;
71 this.output_area = null;
71 this.output_area = null;
72 this.last_msg_id = null;
72 this.last_msg_id = null;
73 this.completer = null;
73 this.completer = null;
74
74
75
75
76 var cm_overwrite_options = {
76 var cm_overwrite_options = {
77 onKeyEvent: $.proxy(this.handle_keyevent,this)
77 onKeyEvent: $.proxy(this.handle_keyevent,this)
78 };
78 };
79
79
80 options = this.mergeopt(CodeCell, options, {cm_config:cm_overwrite_options});
80 options = this.mergeopt(CodeCell, options, {cm_config:cm_overwrite_options});
81
81
82 IPython.Cell.apply(this,[options]);
82 IPython.Cell.apply(this,[options]);
83
83
84 // Attributes we want to override in this subclass.
84 // Attributes we want to override in this subclass.
85 this.cell_type = "code";
85 this.cell_type = "code";
86
86
87 var that = this;
87 var that = this;
88 this.element.focusout(
88 this.element.focusout(
89 function() { that.auto_highlight(); }
89 function() { that.auto_highlight(); }
90 );
90 );
91 };
91 };
92
92
93 CodeCell.options_default = {
93 CodeCell.options_default = {
94 cm_config : {
94 cm_config : {
95 extraKeys: {
95 extraKeys: {
96 "Tab" : "indentMore",
96 "Tab" : "indentMore",
97 "Shift-Tab" : "indentLess",
97 "Shift-Tab" : "indentLess",
98 "Backspace" : "delSpaceToPrevTabStop",
98 "Backspace" : "delSpaceToPrevTabStop",
99 "Cmd-/" : "toggleComment",
99 "Cmd-/" : "toggleComment",
100 "Ctrl-/" : "toggleComment"
100 "Ctrl-/" : "toggleComment"
101 },
101 },
102 mode: 'ipython',
102 mode: 'ipython',
103 theme: 'ipython',
103 theme: 'ipython',
104 matchBrackets: true
104 matchBrackets: true
105 }
105 }
106 };
106 };
107
107
108
108
109 CodeCell.prototype = new IPython.Cell();
109 CodeCell.prototype = new IPython.Cell();
110
110
111 /**
111 /**
112 * @method auto_highlight
112 * @method auto_highlight
113 */
113 */
114 CodeCell.prototype.auto_highlight = function () {
114 CodeCell.prototype.auto_highlight = function () {
115 this._auto_highlight(IPython.config.cell_magic_highlight);
115 this._auto_highlight(IPython.config.cell_magic_highlight);
116 };
116 };
117
117
118 /** @method create_element */
118 /** @method create_element */
119 CodeCell.prototype.create_element = function () {
119 CodeCell.prototype.create_element = function () {
120 IPython.Cell.prototype.create_element.apply(this, arguments);
120 IPython.Cell.prototype.create_element.apply(this, arguments);
121
121
122 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell');
122 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell');
123 cell.attr('tabindex','2');
123 cell.attr('tabindex','2');
124
124
125 var input = $('<div></div>').addClass('input');
125 var input = $('<div></div>').addClass('input');
126 var prompt = $('<div/>').addClass('prompt input_prompt');
126 var prompt = $('<div/>').addClass('prompt input_prompt');
127 var inner_cell = $('<div/>').addClass('inner_cell');
127 var inner_cell = $('<div/>').addClass('inner_cell');
128 this.celltoolbar = new IPython.CellToolbar(this);
128 this.celltoolbar = new IPython.CellToolbar(this);
129 inner_cell.append(this.celltoolbar.element);
129 inner_cell.append(this.celltoolbar.element);
130 var input_area = $('<div/>').addClass('input_area');
130 var input_area = $('<div/>').addClass('input_area');
131 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
131 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
132 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
132 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
133 inner_cell.append(input_area);
133 inner_cell.append(input_area);
134 input.append(prompt).append(inner_cell);
134 input.append(prompt).append(inner_cell);
135 var output = $('<div></div>');
135 var output = $('<div></div>');
136 cell.append(input).append(output);
136 cell.append(input).append(output);
137 this.element = cell;
137 this.element = cell;
138 this.output_area = new IPython.OutputArea(output, true);
138 this.output_area = new IPython.OutputArea(output, true);
139 this.completer = new IPython.Completer(this);
139 this.completer = new IPython.Completer(this);
140 };
140 };
141
141
142 /** @method bind_events */
142 /** @method bind_events */
143 CodeCell.prototype.bind_events = function () {
143 CodeCell.prototype.bind_events = function () {
144 IPython.Cell.prototype.bind_events.apply(this);
144 IPython.Cell.prototype.bind_events.apply(this);
145 var that = this;
145 var that = this;
146
146
147 this.element.focusout(
147 this.element.focusout(
148 function() { that.auto_highlight(); }
148 function() { that.auto_highlight(); }
149 );
149 );
150 };
150 };
151
151
152 CodeCell.prototype.handle_keyevent = function (editor, event) {
152 CodeCell.prototype.handle_keyevent = function (editor, event) {
153
153
154 console.log('CM', this.mode, event.which, event.type)
154 console.log('CM', this.mode, event.which, event.type)
155
155
156 if (this.mode === 'command') {
156 if (this.mode === 'command') {
157 return true;
157 return true;
158 } else if (this.mode === 'edit') {
158 } else if (this.mode === 'edit') {
159 return this.handle_codemirror_keyevent(editor, event);
159 return this.handle_codemirror_keyevent(editor, event);
160 }
160 }
161 };
161 };
162
162
163 /**
163 /**
164 * This method gets called in CodeMirror's onKeyDown/onKeyPress
164 * This method gets called in CodeMirror's onKeyDown/onKeyPress
165 * handlers and is used to provide custom key handling. Its return
165 * handlers and is used to provide custom key handling. Its return
166 * value is used to determine if CodeMirror should ignore the event:
166 * value is used to determine if CodeMirror should ignore the event:
167 * true = ignore, false = don't ignore.
167 * true = ignore, false = don't ignore.
168 * @method handle_codemirror_keyevent
168 * @method handle_codemirror_keyevent
169 */
169 */
170 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
170 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
171
171
172 var that = this;
172 var that = this;
173 // whatever key is pressed, first, cancel the tooltip request before
173 // whatever key is pressed, first, cancel the tooltip request before
174 // they are sent, and remove tooltip if any, except for tab again
174 // they are sent, and remove tooltip if any, except for tab again
175 var tooltip_closed = null;
175 var tooltip_closed = null;
176 if (event.type === 'keydown' && event.which != key.TAB ) {
176 if (event.type === 'keydown' && event.which != key.TAB ) {
177 tooltip_closed = IPython.tooltip.remove_and_cancel_tooltip();
177 tooltip_closed = IPython.tooltip.remove_and_cancel_tooltip();
178 }
178 }
179
179
180 var cur = editor.getCursor();
180 var cur = editor.getCursor();
181 if (event.keyCode === key.ENTER){
181 if (event.keyCode === key.ENTER){
182 this.auto_highlight();
182 this.auto_highlight();
183 }
183 }
184
184
185 if (event.keyCode === key.ENTER && (event.shiftKey || event.ctrlKey)) {
185 if (event.keyCode === key.ENTER && (event.shiftKey || event.ctrlKey)) {
186 // Always ignore shift-enter in CodeMirror as we handle it.
186 // Always ignore shift-enter in CodeMirror as we handle it.
187 return true;
187 return true;
188 } else if (event.which === 40 && event.type === 'keypress' && IPython.tooltip.time_before_tooltip >= 0) {
188 } else if (event.which === 40 && event.type === 'keypress' && IPython.tooltip.time_before_tooltip >= 0) {
189 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
189 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
190 // browser and keyboard layout !
190 // browser and keyboard layout !
191 // Pressing '(' , request tooltip, don't forget to reappend it
191 // Pressing '(' , request tooltip, don't forget to reappend it
192 // The second argument says to hide the tooltip if the docstring
192 // The second argument says to hide the tooltip if the docstring
193 // is actually empty
193 // is actually empty
194 IPython.tooltip.pending(that, true);
194 IPython.tooltip.pending(that, true);
195 } else if (event.which === key.UPARROW && event.type === 'keydown') {
195 } else if (event.which === key.UPARROW && event.type === 'keydown') {
196 // If we are not at the top, let CM handle the up arrow and
196 // If we are not at the top, let CM handle the up arrow and
197 // prevent the global keydown handler from handling it.
197 // prevent the global keydown handler from handling it.
198 if (!that.at_top()) {
198 if (!that.at_top()) {
199 event.stop();
199 event.stop();
200 return false;
200 return false;
201 } else {
201 } else {
202 return true;
202 return true;
203 }
203 }
204 } else if (event.which === key.ESC && event.type === 'keydown') {
204 } else if (event.which === key.ESC && event.type === 'keydown') {
205 // First see if the tooltip is active and if so cancel it.
205 // First see if the tooltip is active and if so cancel it.
206 if (tooltip_closed) {
206 if (tooltip_closed) {
207 // The call to remove_and_cancel_tooltip above in L177 doesn't pass
207 // The call to remove_and_cancel_tooltip above in L177 doesn't pass
208 // force=true. Because of this it won't actually close the tooltip
208 // force=true. Because of this it won't actually close the tooltip
209 // if it is in sticky mode. Thus, we have to check again if it is open
209 // if it is in sticky mode. Thus, we have to check again if it is open
210 // and close it with force=true.
210 // and close it with force=true.
211 if (!IPython.tooltip._hidden) {
211 if (!IPython.tooltip._hidden) {
212 IPython.tooltip.remove_and_cancel_tooltip(true);
212 IPython.tooltip.remove_and_cancel_tooltip(true);
213 }
213 }
214 // If we closed the tooltip, don't let CM or the global handlers
214 // If we closed the tooltip, don't let CM or the global handlers
215 // handle this event.
215 // handle this event.
216 event.stop();
216 event.stop();
217 return true;
217 return true;
218 }
218 }
219 if (that.code_mirror.options.keyMap === "vim-insert") {
219 if (that.code_mirror.options.keyMap === "vim-insert") {
220 // vim keyMap is active and in insert mode. In this case we leave vim
220 // vim keyMap is active and in insert mode. In this case we leave vim
221 // insert mode, but remain in notebook edit mode.
221 // insert mode, but remain in notebook edit mode.
222 // Let' CM handle this event and prevent global handling.
222 // Let' CM handle this event and prevent global handling.
223 event.stop();
223 event.stop();
224 return false;
224 return false;
225 } else {
225 } else {
226 // vim keyMap is not active. Leave notebook edit mode.
226 // vim keyMap is not active. Leave notebook edit mode.
227 // Don't let CM handle the event, defer to global handling.
227 // Don't let CM handle the event, defer to global handling.
228 return true;
228 return true;
229 }
229 }
230 } else if (event.which === key.DOWNARROW && event.type === 'keydown') {
230 } else if (event.which === key.DOWNARROW && event.type === 'keydown') {
231 // If we are not at the bottom, let CM handle the down arrow and
231 // If we are not at the bottom, let CM handle the down arrow and
232 // prevent the global keydown handler from handling it.
232 // prevent the global keydown handler from handling it.
233 if (!that.at_bottom()) {
233 if (!that.at_bottom()) {
234 event.stop();
234 event.stop();
235 return false;
235 return false;
236 } else {
236 } else {
237 return true;
237 return true;
238 }
238 }
239 } else if (event.keyCode === key.TAB && event.type === 'keydown' && event.shiftKey) {
239 } else if (event.keyCode === key.TAB && event.type === 'keydown' && event.shiftKey) {
240 if (editor.somethingSelected()){
240 if (editor.somethingSelected()){
241 var anchor = editor.getCursor("anchor");
241 var anchor = editor.getCursor("anchor");
242 var head = editor.getCursor("head");
242 var head = editor.getCursor("head");
243 if( anchor.line != head.line){
243 if( anchor.line != head.line){
244 return false;
244 return false;
245 }
245 }
246 }
246 }
247 IPython.tooltip.request(that);
247 IPython.tooltip.request(that);
248 event.stop();
248 event.stop();
249 return true;
249 return true;
250 } else if (event.keyCode === key.TAB && event.type == 'keydown') {
250 } else if (event.keyCode === key.TAB && event.type == 'keydown') {
251 // Tab completion.
251 // Tab completion.
252 IPython.tooltip.remove_and_cancel_tooltip();
252 IPython.tooltip.remove_and_cancel_tooltip();
253 if (editor.somethingSelected()) {
253 if (editor.somethingSelected()) {
254 return false;
254 return false;
255 }
255 }
256 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
256 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
257 if (pre_cursor.trim() === "") {
257 if (pre_cursor.trim() === "") {
258 // Don't autocomplete if the part of the line before the cursor
258 // Don't autocomplete if the part of the line before the cursor
259 // is empty. In this case, let CodeMirror handle indentation.
259 // is empty. In this case, let CodeMirror handle indentation.
260 return false;
260 return false;
261 } else {
261 } else {
262 event.stop();
262 event.stop();
263 this.completer.startCompletion();
263 this.completer.startCompletion();
264 return true;
264 return true;
265 }
265 }
266 } else {
266 } else {
267 // keypress/keyup also trigger on TAB press, and we don't want to
267 // keypress/keyup also trigger on TAB press, and we don't want to
268 // use those to disable tab completion.
268 // use those to disable tab completion.
269 return false;
269 return false;
270 }
270 }
271 return false;
271 return false;
272 };
272 };
273
273
274 // Kernel related calls.
274 // Kernel related calls.
275
275
276 CodeCell.prototype.set_kernel = function (kernel) {
276 CodeCell.prototype.set_kernel = function (kernel) {
277 this.kernel = kernel;
277 this.kernel = kernel;
278 };
278 };
279
279
280 /**
280 /**
281 * Execute current code cell to the kernel
281 * Execute current code cell to the kernel
282 * @method execute
282 * @method execute
283 */
283 */
284 CodeCell.prototype.execute = function () {
284 CodeCell.prototype.execute = function () {
285 this.output_area.clear_output();
285 this.output_area.clear_output();
286 this.set_input_prompt('*');
286 this.set_input_prompt('*');
287 this.element.addClass("running");
287 this.element.addClass("running");
288 if (this.last_msg_id) {
288 if (this.last_msg_id) {
289 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
289 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
290 }
290 }
291 var callbacks = this.get_callbacks();
291 var callbacks = this.get_callbacks();
292
292
293 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
293 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
294 };
294 };
295
295
296 /**
296 /**
297 * Construct the default callbacks for
297 * Construct the default callbacks for
298 * @method get_callbacks
298 * @method get_callbacks
299 */
299 */
300 CodeCell.prototype.get_callbacks = function () {
300 CodeCell.prototype.get_callbacks = function () {
301 return {
301 return {
302 shell : {
302 shell : {
303 reply : $.proxy(this._handle_execute_reply, this),
303 reply : $.proxy(this._handle_execute_reply, this),
304 payload : {
304 payload : {
305 set_next_input : $.proxy(this._handle_set_next_input, this),
305 set_next_input : $.proxy(this._handle_set_next_input, this),
306 page : $.proxy(this._open_with_pager, this)
306 page : $.proxy(this._open_with_pager, this)
307 }
307 }
308 },
308 },
309 iopub : {
309 iopub : {
310 output : $.proxy(this.output_area.handle_output, this.output_area),
310 output : $.proxy(this.output_area.handle_output, this.output_area),
311 clear_output : $.proxy(this.output_area.handle_clear_output, this.output_area),
311 clear_output : $.proxy(this.output_area.handle_clear_output, this.output_area),
312 },
312 },
313 input : $.proxy(this._handle_input_request, this)
313 input : $.proxy(this._handle_input_request, this)
314 };
314 };
315 };
315 };
316
316
317 CodeCell.prototype._open_with_pager = function (payload) {
317 CodeCell.prototype._open_with_pager = function (payload) {
318 $([IPython.events]).trigger('open_with_text.Pager', payload);
318 $([IPython.events]).trigger('open_with_text.Pager', payload);
319 };
319 };
320
320
321 /**
321 /**
322 * @method _handle_execute_reply
322 * @method _handle_execute_reply
323 * @private
323 * @private
324 */
324 */
325 CodeCell.prototype._handle_execute_reply = function (msg) {
325 CodeCell.prototype._handle_execute_reply = function (msg) {
326 this.set_input_prompt(msg.content.execution_count);
326 this.set_input_prompt(msg.content.execution_count);
327 this.element.removeClass("running");
327 this.element.removeClass("running");
328 $([IPython.events]).trigger('set_dirty.Notebook', {value: true});
328 $([IPython.events]).trigger('set_dirty.Notebook', {value: true});
329 };
329 };
330
330
331 /**
331 /**
332 * @method _handle_set_next_input
332 * @method _handle_set_next_input
333 * @private
333 * @private
334 */
334 */
335 CodeCell.prototype._handle_set_next_input = function (payload) {
335 CodeCell.prototype._handle_set_next_input = function (payload) {
336 var data = {'cell': this, 'text': payload.text};
336 var data = {'cell': this, 'text': payload.text};
337 $([IPython.events]).trigger('set_next_input.Notebook', data);
337 $([IPython.events]).trigger('set_next_input.Notebook', data);
338 };
338 };
339
339
340 /**
340 /**
341 * @method _handle_input_request
341 * @method _handle_input_request
342 * @private
342 * @private
343 */
343 */
344 CodeCell.prototype._handle_input_request = function (msg) {
344 CodeCell.prototype._handle_input_request = function (msg) {
345 this.output_area.append_raw_input(msg);
345 this.output_area.append_raw_input(msg);
346 };
346 };
347
347
348
348
349 // Basic cell manipulation.
349 // Basic cell manipulation.
350
350
351 CodeCell.prototype.select = function () {
351 CodeCell.prototype.select = function () {
352 var cont = IPython.Cell.prototype.select.apply(this);
352 var cont = IPython.Cell.prototype.select.apply(this);
353 if (cont) {
353 if (cont) {
354 this.code_mirror.refresh();
354 this.code_mirror.refresh();
355 this.auto_highlight();
355 this.auto_highlight();
356 };
356 };
357 return cont;
357 return cont;
358 };
358 };
359
359
360 CodeCell.prototype.render = function () {
360 CodeCell.prototype.render = function () {
361 var cont = IPython.Cell.prototype.render.apply(this);
361 var cont = IPython.Cell.prototype.render.apply(this);
362 // Always execute, even if we are already in the rendered state
362 // Always execute, even if we are already in the rendered state
363 return cont;
363 return cont;
364 };
364 };
365
365
366 CodeCell.prototype.unrender = function () {
366 CodeCell.prototype.unrender = function () {
367 // CodeCell is always rendered
367 // CodeCell is always rendered
368 return false;
368 return false;
369 };
369 };
370
370
371 CodeCell.prototype.command_mode = function () {
372 var cont = IPython.Cell.prototype.command_mode.apply(this);
373 if (cont) {
374 this.focus_cell();
375 };
376 return cont;
377 }
378
379 CodeCell.prototype.edit_mode = function () {
371 CodeCell.prototype.edit_mode = function () {
380 var cont = IPython.Cell.prototype.edit_mode.apply(this);
372 var cont = IPython.Cell.prototype.edit_mode.apply(this);
381 if (cont) {
373 if (cont) {
382 this.focus_editor();
374 this.focus_editor();
383 };
375 };
384 return cont;
376 return cont;
385 }
377 }
386
378
387 CodeCell.prototype.select_all = function () {
379 CodeCell.prototype.select_all = function () {
388 var start = {line: 0, ch: 0};
380 var start = {line: 0, ch: 0};
389 var nlines = this.code_mirror.lineCount();
381 var nlines = this.code_mirror.lineCount();
390 var last_line = this.code_mirror.getLine(nlines-1);
382 var last_line = this.code_mirror.getLine(nlines-1);
391 var end = {line: nlines-1, ch: last_line.length};
383 var end = {line: nlines-1, ch: last_line.length};
392 this.code_mirror.setSelection(start, end);
384 this.code_mirror.setSelection(start, end);
393 };
385 };
394
386
395
387
396 CodeCell.prototype.collapse = function () {
388 CodeCell.prototype.collapse = function () {
397 this.collapsed = true;
389 this.collapsed = true;
398 this.output_area.collapse();
390 this.output_area.collapse();
399 };
391 };
400
392
401
393
402 CodeCell.prototype.expand = function () {
394 CodeCell.prototype.expand = function () {
403 this.collapsed = false;
395 this.collapsed = false;
404 this.output_area.expand();
396 this.output_area.expand();
405 };
397 };
406
398
407
399
408 CodeCell.prototype.toggle_output = function () {
400 CodeCell.prototype.toggle_output = function () {
409 this.collapsed = Boolean(1 - this.collapsed);
401 this.collapsed = Boolean(1 - this.collapsed);
410 this.output_area.toggle_output();
402 this.output_area.toggle_output();
411 };
403 };
412
404
413
405
414 CodeCell.prototype.toggle_output_scroll = function () {
406 CodeCell.prototype.toggle_output_scroll = function () {
415 this.output_area.toggle_scroll();
407 this.output_area.toggle_scroll();
416 };
408 };
417
409
418
410
419 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
411 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
420 var ns = prompt_value || "&nbsp;";
412 var ns = prompt_value || "&nbsp;";
421 return 'In&nbsp;[' + ns + ']:';
413 return 'In&nbsp;[' + ns + ']:';
422 };
414 };
423
415
424 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
416 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
425 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
417 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
426 for(var i=1; i < lines_number; i++) {
418 for(var i=1; i < lines_number; i++) {
427 html.push(['...:']);
419 html.push(['...:']);
428 }
420 }
429 return html.join('<br/>');
421 return html.join('<br/>');
430 };
422 };
431
423
432 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
424 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
433
425
434
426
435 CodeCell.prototype.set_input_prompt = function (number) {
427 CodeCell.prototype.set_input_prompt = function (number) {
436 var nline = 1;
428 var nline = 1;
437 if (this.code_mirror !== undefined) {
429 if (this.code_mirror !== undefined) {
438 nline = this.code_mirror.lineCount();
430 nline = this.code_mirror.lineCount();
439 }
431 }
440 this.input_prompt_number = number;
432 this.input_prompt_number = number;
441 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
433 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
442 this.element.find('div.input_prompt').html(prompt_html);
434 this.element.find('div.input_prompt').html(prompt_html);
443 };
435 };
444
436
445
437
446 CodeCell.prototype.clear_input = function () {
438 CodeCell.prototype.clear_input = function () {
447 this.code_mirror.setValue('');
439 this.code_mirror.setValue('');
448 };
440 };
449
441
450
442
451 CodeCell.prototype.get_text = function () {
443 CodeCell.prototype.get_text = function () {
452 return this.code_mirror.getValue();
444 return this.code_mirror.getValue();
453 };
445 };
454
446
455
447
456 CodeCell.prototype.set_text = function (code) {
448 CodeCell.prototype.set_text = function (code) {
457 return this.code_mirror.setValue(code);
449 return this.code_mirror.setValue(code);
458 };
450 };
459
451
460
452
461 CodeCell.prototype.at_top = function () {
453 CodeCell.prototype.at_top = function () {
462 var cursor = this.code_mirror.getCursor();
454 var cursor = this.code_mirror.getCursor();
463 if (cursor.line === 0 && cursor.ch === 0) {
455 if (cursor.line === 0 && cursor.ch === 0) {
464 return true;
456 return true;
465 } else {
457 } else {
466 return false;
458 return false;
467 }
459 }
468 };
460 };
469
461
470
462
471 CodeCell.prototype.at_bottom = function () {
463 CodeCell.prototype.at_bottom = function () {
472 var cursor = this.code_mirror.getCursor();
464 var cursor = this.code_mirror.getCursor();
473 if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) {
465 if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) {
474 return true;
466 return true;
475 } else {
467 } else {
476 return false;
468 return false;
477 }
469 }
478 };
470 };
479
471
480
472
481 CodeCell.prototype.clear_output = function (wait) {
473 CodeCell.prototype.clear_output = function (wait) {
482 this.output_area.clear_output(wait);
474 this.output_area.clear_output(wait);
483 };
475 };
484
476
485
477
486 // JSON serialization
478 // JSON serialization
487
479
488 CodeCell.prototype.fromJSON = function (data) {
480 CodeCell.prototype.fromJSON = function (data) {
489 IPython.Cell.prototype.fromJSON.apply(this, arguments);
481 IPython.Cell.prototype.fromJSON.apply(this, arguments);
490 if (data.cell_type === 'code') {
482 if (data.cell_type === 'code') {
491 if (data.input !== undefined) {
483 if (data.input !== undefined) {
492 this.set_text(data.input);
484 this.set_text(data.input);
493 // make this value the starting point, so that we can only undo
485 // make this value the starting point, so that we can only undo
494 // to this state, instead of a blank cell
486 // to this state, instead of a blank cell
495 this.code_mirror.clearHistory();
487 this.code_mirror.clearHistory();
496 this.auto_highlight();
488 this.auto_highlight();
497 }
489 }
498 if (data.prompt_number !== undefined) {
490 if (data.prompt_number !== undefined) {
499 this.set_input_prompt(data.prompt_number);
491 this.set_input_prompt(data.prompt_number);
500 } else {
492 } else {
501 this.set_input_prompt();
493 this.set_input_prompt();
502 }
494 }
503 this.output_area.fromJSON(data.outputs);
495 this.output_area.fromJSON(data.outputs);
504 if (data.collapsed !== undefined) {
496 if (data.collapsed !== undefined) {
505 if (data.collapsed) {
497 if (data.collapsed) {
506 this.collapse();
498 this.collapse();
507 } else {
499 } else {
508 this.expand();
500 this.expand();
509 }
501 }
510 }
502 }
511 }
503 }
512 };
504 };
513
505
514
506
515 CodeCell.prototype.toJSON = function () {
507 CodeCell.prototype.toJSON = function () {
516 var data = IPython.Cell.prototype.toJSON.apply(this);
508 var data = IPython.Cell.prototype.toJSON.apply(this);
517 data.input = this.get_text();
509 data.input = this.get_text();
518 // is finite protect against undefined and '*' value
510 // is finite protect against undefined and '*' value
519 if (isFinite(this.input_prompt_number)) {
511 if (isFinite(this.input_prompt_number)) {
520 data.prompt_number = this.input_prompt_number;
512 data.prompt_number = this.input_prompt_number;
521 }
513 }
522 var outputs = this.output_area.toJSON();
514 var outputs = this.output_area.toJSON();
523 data.outputs = outputs;
515 data.outputs = outputs;
524 data.language = 'python';
516 data.language = 'python';
525 data.collapsed = this.collapsed;
517 data.collapsed = this.collapsed;
526 return data;
518 return data;
527 };
519 };
528
520
529
521
530 IPython.CodeCell = CodeCell;
522 IPython.CodeCell = CodeCell;
531
523
532 return IPython;
524 return IPython;
533 }(IPython));
525 }(IPython));
@@ -1,619 +1,621
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2011 The IPython Development Team
2 // Copyright (C) 2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Keyboard management
9 // Keyboard management
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13 "use strict";
14
14
15 // Setup global keycodes and inverse keycodes.
15 // Setup global keycodes and inverse keycodes.
16
16
17 // See http://unixpapa.com/js/key.html for a complete description. The short of
17 // See http://unixpapa.com/js/key.html for a complete description. The short of
18 // it is that there are different keycode sets. Firefox uses the "Mozilla keycodes"
18 // it is that there are different keycode sets. Firefox uses the "Mozilla keycodes"
19 // and Webkit/IE use the "IE keycodes". These keycode sets are mostly the same
19 // and Webkit/IE use the "IE keycodes". These keycode sets are mostly the same
20 // but have minor differences.
20 // but have minor differences.
21
21
22 // These apply to Firefox, (Webkit and IE)
22 // These apply to Firefox, (Webkit and IE)
23 var _keycodes = {
23 var _keycodes = {
24 'a': 65, 'b': 66, 'c': 67, 'd': 68, 'e': 69, 'f': 70, 'g': 71, 'h': 72, 'i': 73,
24 'a': 65, 'b': 66, 'c': 67, 'd': 68, 'e': 69, 'f': 70, 'g': 71, 'h': 72, 'i': 73,
25 'j': 74, 'k': 75, 'l': 76, 'm': 77, 'n': 78, 'o': 79, 'p': 80, 'q': 81, 'r': 82,
25 'j': 74, 'k': 75, 'l': 76, 'm': 77, 'n': 78, 'o': 79, 'p': 80, 'q': 81, 'r': 82,
26 's': 83, 't': 84, 'u': 85, 'v': 86, 'w': 87, 'x': 88, 'y': 89, 'z': 90,
26 's': 83, 't': 84, 'u': 85, 'v': 86, 'w': 87, 'x': 88, 'y': 89, 'z': 90,
27 '1 !': 49, '2 @': 50, '3 #': 51, '4 $': 52, '5 %': 53, '6 ^': 54,
27 '1 !': 49, '2 @': 50, '3 #': 51, '4 $': 52, '5 %': 53, '6 ^': 54,
28 '7 &': 55, '8 *': 56, '9 (': 57, '0 )': 48,
28 '7 &': 55, '8 *': 56, '9 (': 57, '0 )': 48,
29 '[ {': 219, '] }': 221, '` ~': 192, ', <': 188, '. >': 190, '/ ?': 191,
29 '[ {': 219, '] }': 221, '` ~': 192, ', <': 188, '. >': 190, '/ ?': 191,
30 '\\ |': 220, '\' "': 222,
30 '\\ |': 220, '\' "': 222,
31 'numpad0': 96, 'numpad1': 97, 'numpad2': 98, 'numpad3': 99, 'numpad4': 100,
31 'numpad0': 96, 'numpad1': 97, 'numpad2': 98, 'numpad3': 99, 'numpad4': 100,
32 'numpad5': 101, 'numpad6': 102, 'numpad7': 103, 'numpad8': 104, 'numpad9': 105,
32 'numpad5': 101, 'numpad6': 102, 'numpad7': 103, 'numpad8': 104, 'numpad9': 105,
33 'multiply': 106, 'add': 107, 'subtract': 109, 'decimal': 110, 'divide': 111,
33 'multiply': 106, 'add': 107, 'subtract': 109, 'decimal': 110, 'divide': 111,
34 'f1': 112, 'f2': 113, 'f3': 114, 'f4': 115, 'f5': 116, 'f6': 117, 'f7': 118,
34 'f1': 112, 'f2': 113, 'f3': 114, 'f4': 115, 'f5': 116, 'f6': 117, 'f7': 118,
35 'f8': 119, 'f9': 120, 'f11': 122, 'f12': 123, 'f13': 124, 'f14': 125, 'f15': 126,
35 'f8': 119, 'f9': 120, 'f11': 122, 'f12': 123, 'f13': 124, 'f14': 125, 'f15': 126,
36 'backspace': 8, 'tab': 9, 'enter': 13, 'shift': 16, 'ctrl': 17, 'alt': 18,
36 'backspace': 8, 'tab': 9, 'enter': 13, 'shift': 16, 'ctrl': 17, 'alt': 18,
37 'meta': 91, 'capslock': 20, 'esc': 27, 'space': 32, 'pageup': 33, 'pagedown': 34,
37 'meta': 91, 'capslock': 20, 'esc': 27, 'space': 32, 'pageup': 33, 'pagedown': 34,
38 'end': 35, 'home': 36, 'left': 37, 'up': 38, 'right': 39, 'down': 40,
38 'end': 35, 'home': 36, 'left': 37, 'up': 38, 'right': 39, 'down': 40,
39 'insert': 45, 'delete': 46, 'numlock': 144,
39 'insert': 45, 'delete': 46, 'numlock': 144,
40 };
40 };
41
41
42 // These apply to Firefox and Opera
42 // These apply to Firefox and Opera
43 var _mozilla_keycodes = {
43 var _mozilla_keycodes = {
44 '; :': 59, '= +': 61, '- _': 109,
44 '; :': 59, '= +': 61, '- _': 109,
45 }
45 }
46
46
47 // This apply to Webkit and IE
47 // This apply to Webkit and IE
48 var _ie_keycodes = {
48 var _ie_keycodes = {
49 '; :': 186, '= +': 187, '- _': 189,
49 '; :': 186, '= +': 187, '- _': 189,
50 }
50 }
51
51
52 var browser = IPython.utils.browser[0];
52 var browser = IPython.utils.browser[0];
53
53
54 if (browser === 'Firefox' || browser === 'Opera') {
54 if (browser === 'Firefox' || browser === 'Opera') {
55 $.extend(_keycodes, _mozilla_keycodes);
55 $.extend(_keycodes, _mozilla_keycodes);
56 } else if (browser === 'Safari' || browser === 'Chrome' || browser === 'MSIE') {
56 } else if (browser === 'Safari' || browser === 'Chrome' || browser === 'MSIE') {
57 $.extend(_keycodes, _ie_keycodes);
57 $.extend(_keycodes, _ie_keycodes);
58 }
58 }
59
59
60 var keycodes = {};
60 var keycodes = {};
61 var inv_keycodes = {};
61 var inv_keycodes = {};
62 for (var name in _keycodes) {
62 for (var name in _keycodes) {
63 var names = name.split(' ');
63 var names = name.split(' ');
64 if (names.length === 1) {
64 if (names.length === 1) {
65 var n = names[0]
65 var n = names[0]
66 keycodes[n] = _keycodes[n]
66 keycodes[n] = _keycodes[n]
67 inv_keycodes[_keycodes[n]] = n
67 inv_keycodes[_keycodes[n]] = n
68 } else {
68 } else {
69 var primary = names[0];
69 var primary = names[0];
70 var secondary = names[1];
70 var secondary = names[1];
71 keycodes[primary] = _keycodes[name]
71 keycodes[primary] = _keycodes[name]
72 keycodes[secondary] = _keycodes[name]
72 keycodes[secondary] = _keycodes[name]
73 inv_keycodes[_keycodes[name]] = primary
73 inv_keycodes[_keycodes[name]] = primary
74 }
74 }
75 }
75 }
76
76
77
77
78 // Default keyboard shortcuts
78 // Default keyboard shortcuts
79
79
80 var default_common_shortcuts = {
80 var default_common_shortcuts = {
81 'meta+s' : {
81 'meta+s' : {
82 help : 'save notebook',
82 help : 'save notebook',
83 handler : function (event) {
83 handler : function (event) {
84 IPython.notebook.save_checkpoint();
84 IPython.notebook.save_checkpoint();
85 event.preventDefault();
85 event.preventDefault();
86 return false;
86 return false;
87 }
87 }
88 },
88 },
89 'ctrl+s' : {
89 'ctrl+s' : {
90 help : 'save notebook',
90 help : 'save notebook',
91 handler : function (event) {
91 handler : function (event) {
92 IPython.notebook.save_checkpoint();
92 IPython.notebook.save_checkpoint();
93 event.preventDefault();
93 event.preventDefault();
94 return false;
94 return false;
95 }
95 }
96 },
96 },
97 'shift' : {
97 'shift' : {
98 help : '',
98 help : '',
99 handler : function (event) {
99 handler : function (event) {
100 // ignore shift keydown
100 // ignore shift keydown
101 return true;
101 return true;
102 }
102 }
103 },
103 },
104 'shift+enter' : {
104 'shift+enter' : {
105 help : 'run cell',
105 help : 'run cell',
106 handler : function (event) {
106 handler : function (event) {
107 IPython.notebook.execute_selected_cell('shift');
107 IPython.notebook.execute_selected_cell('shift');
108 return false;
108 return false;
109 }
109 }
110 },
110 },
111 'alt+enter' : {
111 'alt+enter' : {
112 help : 'run cell, insert below',
112 help : 'run cell, insert below',
113 handler : function (event) {
113 handler : function (event) {
114 IPython.notebook.execute_selected_cell('alt');
114 IPython.notebook.execute_selected_cell('alt');
115 return false;
115 return false;
116 }
116 }
117 },
117 },
118 'ctrl+enter' : {
118 'ctrl+enter' : {
119 help : 'run cell, select below',
119 help : 'run cell, select below',
120 handler : function (event) {
120 handler : function (event) {
121 IPython.notebook.execute_selected_cell('ctrl');
121 IPython.notebook.execute_selected_cell('ctrl');
122 return false;
122 return false;
123 }
123 }
124 }
124 }
125 }
125 }
126
126
127 // Edit mode defaults
127 // Edit mode defaults
128
128
129 var default_edit_shortcuts = {
129 var default_edit_shortcuts = {
130 'esc' : {
130 'esc' : {
131 help : 'command mode',
131 help : 'command mode',
132 handler : function (event) {
132 handler : function (event) {
133 IPython.notebook.command_mode();
133 IPython.notebook.command_mode();
134 IPython.notebook.focus_cell();
134 return false;
135 return false;
135 }
136 }
136 },
137 },
137 'ctrl+m' : {
138 'ctrl+m' : {
138 help : 'command mode',
139 help : 'command mode',
139 handler : function (event) {
140 handler : function (event) {
140 IPython.notebook.command_mode();
141 IPython.notebook.command_mode();
142 IPython.notebook.focus_cell();
141 return false;
143 return false;
142 }
144 }
143 },
145 },
144 'up' : {
146 'up' : {
145 help : 'select previous cell',
147 help : 'select previous cell',
146 handler : function (event) {
148 handler : function (event) {
147 var cell = IPython.notebook.get_selected_cell();
149 var cell = IPython.notebook.get_selected_cell();
148 if (cell && cell.at_top()) {
150 if (cell && cell.at_top()) {
149 event.preventDefault();
151 event.preventDefault();
150 IPython.notebook.command_mode()
152 IPython.notebook.command_mode()
151 IPython.notebook.select_prev();
153 IPython.notebook.select_prev();
152 IPython.notebook.edit_mode();
154 IPython.notebook.edit_mode();
153 return false;
155 return false;
154 };
156 };
155 }
157 }
156 },
158 },
157 'down' : {
159 'down' : {
158 help : 'select next cell',
160 help : 'select next cell',
159 handler : function (event) {
161 handler : function (event) {
160 var cell = IPython.notebook.get_selected_cell();
162 var cell = IPython.notebook.get_selected_cell();
161 if (cell && cell.at_bottom()) {
163 if (cell && cell.at_bottom()) {
162 event.preventDefault();
164 event.preventDefault();
163 IPython.notebook.command_mode()
165 IPython.notebook.command_mode()
164 IPython.notebook.select_next();
166 IPython.notebook.select_next();
165 IPython.notebook.edit_mode();
167 IPython.notebook.edit_mode();
166 return false;
168 return false;
167 };
169 };
168 }
170 }
169 },
171 },
170 'alt+-' : {
172 'alt+-' : {
171 help : 'split cell',
173 help : 'split cell',
172 handler : function (event) {
174 handler : function (event) {
173 IPython.notebook.split_cell();
175 IPython.notebook.split_cell();
174 return false;
176 return false;
175 }
177 }
176 },
178 },
177 }
179 }
178
180
179 // Command mode defaults
181 // Command mode defaults
180
182
181 var default_command_shortcuts = {
183 var default_command_shortcuts = {
182 'enter' : {
184 'enter' : {
183 help : 'edit mode',
185 help : 'edit mode',
184 handler : function (event) {
186 handler : function (event) {
185 IPython.notebook.edit_mode();
187 IPython.notebook.edit_mode();
186 return false;
188 return false;
187 }
189 }
188 },
190 },
189 'up' : {
191 'up' : {
190 help : 'select previous cell',
192 help : 'select previous cell',
191 handler : function (event) {
193 handler : function (event) {
192 var index = IPython.notebook.get_selected_index();
194 var index = IPython.notebook.get_selected_index();
193 if (index !== 0 && index !== null) {
195 if (index !== 0 && index !== null) {
194 IPython.notebook.select_prev();
196 IPython.notebook.select_prev();
195 var cell = IPython.notebook.get_selected_cell();
197 var cell = IPython.notebook.get_selected_cell();
196 cell.focus_cell();
198 cell.focus_cell();
197 };
199 };
198 return false;
200 return false;
199 }
201 }
200 },
202 },
201 'down' : {
203 'down' : {
202 help : 'select next cell',
204 help : 'select next cell',
203 handler : function (event) {
205 handler : function (event) {
204 var index = IPython.notebook.get_selected_index();
206 var index = IPython.notebook.get_selected_index();
205 if (index !== (IPython.notebook.ncells()-1) && index !== null) {
207 if (index !== (IPython.notebook.ncells()-1) && index !== null) {
206 IPython.notebook.select_next();
208 IPython.notebook.select_next();
207 var cell = IPython.notebook.get_selected_cell();
209 var cell = IPython.notebook.get_selected_cell();
208 cell.focus_cell();
210 cell.focus_cell();
209 };
211 };
210 return false;
212 return false;
211 }
213 }
212 },
214 },
213 'k' : {
215 'k' : {
214 help : 'select previous cell',
216 help : 'select previous cell',
215 handler : function (event) {
217 handler : function (event) {
216 var index = IPython.notebook.get_selected_index();
218 var index = IPython.notebook.get_selected_index();
217 if (index !== 0 && index !== null) {
219 if (index !== 0 && index !== null) {
218 IPython.notebook.select_prev();
220 IPython.notebook.select_prev();
219 var cell = IPython.notebook.get_selected_cell();
221 var cell = IPython.notebook.get_selected_cell();
220 cell.focus_cell();
222 cell.focus_cell();
221 };
223 };
222 return false;
224 return false;
223 }
225 }
224 },
226 },
225 'j' : {
227 'j' : {
226 help : 'select next cell',
228 help : 'select next cell',
227 handler : function (event) {
229 handler : function (event) {
228 var index = IPython.notebook.get_selected_index();
230 var index = IPython.notebook.get_selected_index();
229 if (index !== (IPython.notebook.ncells()-1) && index !== null) {
231 if (index !== (IPython.notebook.ncells()-1) && index !== null) {
230 IPython.notebook.select_next();
232 IPython.notebook.select_next();
231 var cell = IPython.notebook.get_selected_cell();
233 var cell = IPython.notebook.get_selected_cell();
232 cell.focus_cell();
234 cell.focus_cell();
233 };
235 };
234 return false;
236 return false;
235 }
237 }
236 },
238 },
237 'x' : {
239 'x' : {
238 help : 'cut cell',
240 help : 'cut cell',
239 handler : function (event) {
241 handler : function (event) {
240 IPython.notebook.cut_cell();
242 IPython.notebook.cut_cell();
241 return false;
243 return false;
242 }
244 }
243 },
245 },
244 'c' : {
246 'c' : {
245 help : 'copy cell',
247 help : 'copy cell',
246 handler : function (event) {
248 handler : function (event) {
247 IPython.notebook.copy_cell();
249 IPython.notebook.copy_cell();
248 return false;
250 return false;
249 }
251 }
250 },
252 },
251 'v' : {
253 'v' : {
252 help : 'paste cell below',
254 help : 'paste cell below',
253 handler : function (event) {
255 handler : function (event) {
254 IPython.notebook.paste_cell_below();
256 IPython.notebook.paste_cell_below();
255 return false;
257 return false;
256 }
258 }
257 },
259 },
258 'd' : {
260 'd' : {
259 help : 'delete cell (press twice)',
261 help : 'delete cell (press twice)',
260 handler : function (event) {
262 handler : function (event) {
261 var dc = IPython.delete_count;
263 var dc = IPython.delete_count;
262 if (dc === undefined) {
264 if (dc === undefined) {
263 IPython.delete_count = 1;
265 IPython.delete_count = 1;
264 } else if (dc === 0) {
266 } else if (dc === 0) {
265 IPython.delete_count = 1;
267 IPython.delete_count = 1;
266 setTimeout(function () {
268 setTimeout(function () {
267 IPython.delete_count = 0;
269 IPython.delete_count = 0;
268 }, 800);
270 }, 800);
269 } else if (dc === 1) {
271 } else if (dc === 1) {
270 IPython.notebook.delete_cell();
272 IPython.notebook.delete_cell();
271 IPython.delete_count = 0;
273 IPython.delete_count = 0;
272 }
274 }
273 return false;
275 return false;
274 }
276 }
275 },
277 },
276 'a' : {
278 'a' : {
277 help : 'insert cell above',
279 help : 'insert cell above',
278 handler : function (event) {
280 handler : function (event) {
279 IPython.notebook.insert_cell_above('code');
281 IPython.notebook.insert_cell_above('code');
280 IPython.notebook.select_prev();
282 IPython.notebook.select_prev();
281 return false;
283 return false;
282 }
284 }
283 },
285 },
284 'b' : {
286 'b' : {
285 help : 'insert cell below',
287 help : 'insert cell below',
286 handler : function (event) {
288 handler : function (event) {
287 IPython.notebook.insert_cell_below('code');
289 IPython.notebook.insert_cell_below('code');
288 IPython.notebook.select_next();
290 IPython.notebook.select_next();
289 return false;
291 return false;
290 }
292 }
291 },
293 },
292 'y' : {
294 'y' : {
293 help : 'to code',
295 help : 'to code',
294 handler : function (event) {
296 handler : function (event) {
295 IPython.notebook.to_code();
297 IPython.notebook.to_code();
296 return false;
298 return false;
297 }
299 }
298 },
300 },
299 'm' : {
301 'm' : {
300 help : 'to markdown',
302 help : 'to markdown',
301 handler : function (event) {
303 handler : function (event) {
302 IPython.notebook.to_markdown();
304 IPython.notebook.to_markdown();
303 return false;
305 return false;
304 }
306 }
305 },
307 },
306 't' : {
308 't' : {
307 help : 'to raw',
309 help : 'to raw',
308 handler : function (event) {
310 handler : function (event) {
309 IPython.notebook.to_raw();
311 IPython.notebook.to_raw();
310 return false;
312 return false;
311 }
313 }
312 },
314 },
313 '1' : {
315 '1' : {
314 help : 'to heading 1',
316 help : 'to heading 1',
315 handler : function (event) {
317 handler : function (event) {
316 IPython.notebook.to_heading(undefined, 1);
318 IPython.notebook.to_heading(undefined, 1);
317 return false;
319 return false;
318 }
320 }
319 },
321 },
320 '2' : {
322 '2' : {
321 help : 'to heading 2',
323 help : 'to heading 2',
322 handler : function (event) {
324 handler : function (event) {
323 IPython.notebook.to_heading(undefined, 2);
325 IPython.notebook.to_heading(undefined, 2);
324 return false;
326 return false;
325 }
327 }
326 },
328 },
327 '3' : {
329 '3' : {
328 help : 'to heading 3',
330 help : 'to heading 3',
329 handler : function (event) {
331 handler : function (event) {
330 IPython.notebook.to_heading(undefined, 3);
332 IPython.notebook.to_heading(undefined, 3);
331 return false;
333 return false;
332 }
334 }
333 },
335 },
334 '4' : {
336 '4' : {
335 help : 'to heading 4',
337 help : 'to heading 4',
336 handler : function (event) {
338 handler : function (event) {
337 IPython.notebook.to_heading(undefined, 4);
339 IPython.notebook.to_heading(undefined, 4);
338 return false;
340 return false;
339 }
341 }
340 },
342 },
341 '5' : {
343 '5' : {
342 help : 'to heading 5',
344 help : 'to heading 5',
343 handler : function (event) {
345 handler : function (event) {
344 IPython.notebook.to_heading(undefined, 5);
346 IPython.notebook.to_heading(undefined, 5);
345 return false;
347 return false;
346 }
348 }
347 },
349 },
348 '6' : {
350 '6' : {
349 help : 'to heading 6',
351 help : 'to heading 6',
350 handler : function (event) {
352 handler : function (event) {
351 IPython.notebook.to_heading(undefined, 6);
353 IPython.notebook.to_heading(undefined, 6);
352 return false;
354 return false;
353 }
355 }
354 },
356 },
355 'o' : {
357 'o' : {
356 help : 'toggle output',
358 help : 'toggle output',
357 handler : function (event) {
359 handler : function (event) {
358 IPython.notebook.toggle_output();
360 IPython.notebook.toggle_output();
359 return false;
361 return false;
360 }
362 }
361 },
363 },
362 'shift+o' : {
364 'shift+o' : {
363 help : 'toggle output',
365 help : 'toggle output',
364 handler : function (event) {
366 handler : function (event) {
365 IPython.notebook.toggle_output_scroll();
367 IPython.notebook.toggle_output_scroll();
366 return false;
368 return false;
367 }
369 }
368 },
370 },
369 's' : {
371 's' : {
370 help : 'save notebook',
372 help : 'save notebook',
371 handler : function (event) {
373 handler : function (event) {
372 IPython.notebook.save_checkpoint();
374 IPython.notebook.save_checkpoint();
373 return false;
375 return false;
374 }
376 }
375 },
377 },
376 'ctrl+j' : {
378 'ctrl+j' : {
377 help : 'move cell down',
379 help : 'move cell down',
378 handler : function (event) {
380 handler : function (event) {
379 IPython.notebook.move_cell_down();
381 IPython.notebook.move_cell_down();
380 return false;
382 return false;
381 }
383 }
382 },
384 },
383 'ctrl+k' : {
385 'ctrl+k' : {
384 help : 'move cell up',
386 help : 'move cell up',
385 handler : function (event) {
387 handler : function (event) {
386 IPython.notebook.move_cell_up();
388 IPython.notebook.move_cell_up();
387 return false;
389 return false;
388 }
390 }
389 },
391 },
390 'l' : {
392 'l' : {
391 help : 'toggle line numbers',
393 help : 'toggle line numbers',
392 handler : function (event) {
394 handler : function (event) {
393 IPython.notebook.cell_toggle_line_numbers();
395 IPython.notebook.cell_toggle_line_numbers();
394 return false;
396 return false;
395 }
397 }
396 },
398 },
397 'i' : {
399 'i' : {
398 help : 'interrupt kernel',
400 help : 'interrupt kernel',
399 handler : function (event) {
401 handler : function (event) {
400 IPython.notebook.kernel.interrupt();
402 IPython.notebook.kernel.interrupt();
401 return false;
403 return false;
402 }
404 }
403 },
405 },
404 '.' : {
406 '.' : {
405 help : 'restart kernel',
407 help : 'restart kernel',
406 handler : function (event) {
408 handler : function (event) {
407 IPython.notebook.restart_kernel();
409 IPython.notebook.restart_kernel();
408 return false;
410 return false;
409 }
411 }
410 },
412 },
411 'h' : {
413 'h' : {
412 help : 'keyboard shortcuts',
414 help : 'keyboard shortcuts',
413 handler : function (event) {
415 handler : function (event) {
414 IPython.quick_help.show_keyboard_shortcuts();
416 IPython.quick_help.show_keyboard_shortcuts();
415 return false;
417 return false;
416 }
418 }
417 },
419 },
418 'z' : {
420 'z' : {
419 help : 'undo last delete',
421 help : 'undo last delete',
420 handler : function (event) {
422 handler : function (event) {
421 IPython.notebook.undelete_cell();
423 IPython.notebook.undelete_cell();
422 return false;
424 return false;
423 }
425 }
424 },
426 },
425 '-' : {
427 '-' : {
426 help : 'split cell',
428 help : 'split cell',
427 handler : function (event) {
429 handler : function (event) {
428 IPython.notebook.split_cell();
430 IPython.notebook.split_cell();
429 return false;
431 return false;
430 }
432 }
431 },
433 },
432 'shift+=' : {
434 'shift+=' : {
433 help : 'merge cell below',
435 help : 'merge cell below',
434 handler : function (event) {
436 handler : function (event) {
435 IPython.notebook.merge_cell_below();
437 IPython.notebook.merge_cell_below();
436 return false;
438 return false;
437 }
439 }
438 },
440 },
439 }
441 }
440
442
441
443
442 // Shortcut manager class
444 // Shortcut manager class
443
445
444 var ShortcutManager = function () {
446 var ShortcutManager = function () {
445 this._shortcuts = {}
447 this._shortcuts = {}
446 }
448 }
447
449
448 ShortcutManager.prototype.help = function () {
450 ShortcutManager.prototype.help = function () {
449 var help = [];
451 var help = [];
450 for (var shortcut in this._shortcuts) {
452 for (var shortcut in this._shortcuts) {
451 help.push({shortcut: shortcut, help: this._shortcuts[shortcut]['help']});
453 help.push({shortcut: shortcut, help: this._shortcuts[shortcut]['help']});
452 }
454 }
453 return help;
455 return help;
454 }
456 }
455
457
456 ShortcutManager.prototype.canonicalize_key = function (key) {
458 ShortcutManager.prototype.canonicalize_key = function (key) {
457 return inv_keycodes[keycodes[key]];
459 return inv_keycodes[keycodes[key]];
458 }
460 }
459
461
460 ShortcutManager.prototype.canonicalize_shortcut = function (shortcut) {
462 ShortcutManager.prototype.canonicalize_shortcut = function (shortcut) {
461 // Sort a sequence of + separated modifiers into the order alt+ctrl+meta+shift
463 // Sort a sequence of + separated modifiers into the order alt+ctrl+meta+shift
462 var values = shortcut.split("+");
464 var values = shortcut.split("+");
463 if (values.length === 1) {
465 if (values.length === 1) {
464 return this.canonicalize_key(values[0])
466 return this.canonicalize_key(values[0])
465 } else {
467 } else {
466 var modifiers = values.slice(0,-1);
468 var modifiers = values.slice(0,-1);
467 var key = this.canonicalize_key(values[values.length-1]);
469 var key = this.canonicalize_key(values[values.length-1]);
468 modifiers.sort();
470 modifiers.sort();
469 return modifiers.join('+') + '+' + key;
471 return modifiers.join('+') + '+' + key;
470 }
472 }
471 }
473 }
472
474
473 ShortcutManager.prototype.event_to_shortcut = function (event) {
475 ShortcutManager.prototype.event_to_shortcut = function (event) {
474 // Convert a jQuery keyboard event to a strong based keyboard shortcut
476 // Convert a jQuery keyboard event to a strong based keyboard shortcut
475 var shortcut = '';
477 var shortcut = '';
476 var key = inv_keycodes[event.which]
478 var key = inv_keycodes[event.which]
477 if (event.altKey && key !== 'alt') {shortcut += 'alt+';}
479 if (event.altKey && key !== 'alt') {shortcut += 'alt+';}
478 if (event.ctrlKey && key !== 'ctrl') {shortcut += 'ctrl+';}
480 if (event.ctrlKey && key !== 'ctrl') {shortcut += 'ctrl+';}
479 if (event.metaKey && key !== 'meta') {shortcut += 'meta+';}
481 if (event.metaKey && key !== 'meta') {shortcut += 'meta+';}
480 if (event.shiftKey && key !== 'shift') {shortcut += 'shift+';}
482 if (event.shiftKey && key !== 'shift') {shortcut += 'shift+';}
481 shortcut += key;
483 shortcut += key;
482 return shortcut
484 return shortcut
483 }
485 }
484
486
485 ShortcutManager.prototype.clear_shortcuts = function () {
487 ShortcutManager.prototype.clear_shortcuts = function () {
486 this._shortcuts = {};
488 this._shortcuts = {};
487 }
489 }
488
490
489 ShortcutManager.prototype.add_shortcut = function (shortcut, data) {
491 ShortcutManager.prototype.add_shortcut = function (shortcut, data) {
490 shortcut = this.canonicalize_shortcut(shortcut);
492 shortcut = this.canonicalize_shortcut(shortcut);
491 this._shortcuts[shortcut] = data;
493 this._shortcuts[shortcut] = data;
492 }
494 }
493
495
494 ShortcutManager.prototype.add_shortcuts = function (data) {
496 ShortcutManager.prototype.add_shortcuts = function (data) {
495 for (var shortcut in data) {
497 for (var shortcut in data) {
496 this.add_shortcut(shortcut, data[shortcut]);
498 this.add_shortcut(shortcut, data[shortcut]);
497 }
499 }
498 }
500 }
499
501
500 ShortcutManager.prototype.remove_shortcut = function (shortcut) {
502 ShortcutManager.prototype.remove_shortcut = function (shortcut) {
501 shortcut = this.canonicalize_shortcut(shortcut);
503 shortcut = this.canonicalize_shortcut(shortcut);
502 delete this._shortcuts[shortcut];
504 delete this._shortcuts[shortcut];
503 }
505 }
504
506
505 ShortcutManager.prototype.call_handler = function (event) {
507 ShortcutManager.prototype.call_handler = function (event) {
506 var shortcut = this.event_to_shortcut(event);
508 var shortcut = this.event_to_shortcut(event);
507 var data = this._shortcuts[shortcut];
509 var data = this._shortcuts[shortcut];
508 if (data !== undefined) {
510 if (data !== undefined) {
509 var handler = data['handler'];
511 var handler = data['handler'];
510 if (handler !== undefined) {
512 if (handler !== undefined) {
511 return handler(event);
513 return handler(event);
512 }
514 }
513 }
515 }
514 return true;
516 return true;
515 }
517 }
516
518
517
519
518
520
519 // Main keyboard manager for the notebook
521 // Main keyboard manager for the notebook
520
522
521 var KeyboardManager = function () {
523 var KeyboardManager = function () {
522 this.mode = 'command';
524 this.mode = 'command';
523 this.enabled = true;
525 this.enabled = true;
524 this.delete_count = 0;
526 this.delete_count = 0;
525 this.bind_events();
527 this.bind_events();
526 this.command_shortcuts = new ShortcutManager();
528 this.command_shortcuts = new ShortcutManager();
527 this.command_shortcuts.add_shortcuts(default_common_shortcuts);
529 this.command_shortcuts.add_shortcuts(default_common_shortcuts);
528 this.command_shortcuts.add_shortcuts(default_command_shortcuts);
530 this.command_shortcuts.add_shortcuts(default_command_shortcuts);
529 this.edit_shortcuts = new ShortcutManager();
531 this.edit_shortcuts = new ShortcutManager();
530 this.edit_shortcuts.add_shortcuts(default_common_shortcuts);
532 this.edit_shortcuts.add_shortcuts(default_common_shortcuts);
531 this.edit_shortcuts.add_shortcuts(default_edit_shortcuts);
533 this.edit_shortcuts.add_shortcuts(default_edit_shortcuts);
532 };
534 };
533
535
534 KeyboardManager.prototype.bind_events = function () {
536 KeyboardManager.prototype.bind_events = function () {
535 var that = this;
537 var that = this;
536 $(document).keydown(function (event) {
538 $(document).keydown(function (event) {
537 return that.handle_keydown(event);
539 return that.handle_keydown(event);
538 });
540 });
539 };
541 };
540
542
541 KeyboardManager.prototype.handle_keydown = function (event) {
543 KeyboardManager.prototype.handle_keydown = function (event) {
542 var notebook = IPython.notebook;
544 var notebook = IPython.notebook;
543
545
544 console.log('keyboard_manager', this.mode, event.keyCode);
546 console.log('keyboard_manager', this.mode, event.keyCode);
545
547
546 if (event.which === keycodes['esc']) {
548 if (event.which === keycodes['esc']) {
547 // Intercept escape at highest level to avoid closing
549 // Intercept escape at highest level to avoid closing
548 // websocket connection with firefox
550 // websocket connection with firefox
549 event.preventDefault();
551 event.preventDefault();
550 }
552 }
551
553
552 if (!this.enabled) {
554 if (!this.enabled) {
553 if (event.which === keycodes['esc']) {
555 if (event.which === keycodes['esc']) {
554 // ESC
556 // ESC
555 notebook.command_mode();
557 notebook.command_mode();
556 return false;
558 return false;
557 }
559 }
558 return true;
560 return true;
559 }
561 }
560
562
561 if (this.mode === 'edit') {
563 if (this.mode === 'edit') {
562 return this.edit_shortcuts.call_handler(event);
564 return this.edit_shortcuts.call_handler(event);
563 } else if (this.mode === 'command') {
565 } else if (this.mode === 'command') {
564 return this.command_shortcuts.call_handler(event);
566 return this.command_shortcuts.call_handler(event);
565 }
567 }
566 return true;
568 return true;
567 }
569 }
568
570
569 KeyboardManager.prototype.edit_mode = function () {
571 KeyboardManager.prototype.edit_mode = function () {
570 console.log('KeyboardManager', 'changing to edit mode');
572 console.log('KeyboardManager', 'changing to edit mode');
571 this.last_mode = this.mode;
573 this.last_mode = this.mode;
572 this.mode = 'edit';
574 this.mode = 'edit';
573 }
575 }
574
576
575 KeyboardManager.prototype.command_mode = function () {
577 KeyboardManager.prototype.command_mode = function () {
576 console.log('KeyboardManager', 'changing to command mode');
578 console.log('KeyboardManager', 'changing to command mode');
577 this.last_mode = this.mode;
579 this.last_mode = this.mode;
578 this.mode = 'command';
580 this.mode = 'command';
579 }
581 }
580
582
581 KeyboardManager.prototype.enable = function () {
583 KeyboardManager.prototype.enable = function () {
582 this.enabled = true;
584 this.enabled = true;
583 }
585 }
584
586
585 KeyboardManager.prototype.disable = function () {
587 KeyboardManager.prototype.disable = function () {
586 this.enabled = false;
588 this.enabled = false;
587 }
589 }
588
590
589 KeyboardManager.prototype.register_events = function (e) {
591 KeyboardManager.prototype.register_events = function (e) {
590 var that = this;
592 var that = this;
591 e.on('focusin', function () {
593 e.on('focusin', function () {
592 that.command_mode();
594 that.command_mode();
593 that.disable();
595 that.disable();
594 });
596 });
595 e.on('focusout', function () {
597 e.on('focusout', function () {
596 that.command_mode();
598 that.command_mode();
597 that.enable();
599 that.enable();
598 });
600 });
599 // There are times (raw_input) where we remove the element from the DOM before
601 // There are times (raw_input) where we remove the element from the DOM before
600 // focusout is called. In this case we bind to the remove event of jQueryUI,
602 // focusout is called. In this case we bind to the remove event of jQueryUI,
601 // which gets triggered upon removal.
603 // which gets triggered upon removal.
602 e.on('remove', function () {
604 e.on('remove', function () {
603 that.command_mode();
605 that.command_mode();
604 that.enable();
606 that.enable();
605 });
607 });
606 }
608 }
607
609
608
610
609 IPython.keycodes = keycodes;
611 IPython.keycodes = keycodes;
610 IPython.inv_keycodes = inv_keycodes;
612 IPython.inv_keycodes = inv_keycodes;
611 IPython.default_common_shortcuts = default_common_shortcuts;
613 IPython.default_common_shortcuts = default_common_shortcuts;
612 IPython.default_edit_shortcuts = default_edit_shortcuts;
614 IPython.default_edit_shortcuts = default_edit_shortcuts;
613 IPython.default_command_shortcuts = default_command_shortcuts;
615 IPython.default_command_shortcuts = default_command_shortcuts;
614 IPython.ShortcutManager = ShortcutManager;
616 IPython.ShortcutManager = ShortcutManager;
615 IPython.KeyboardManager = KeyboardManager;
617 IPython.KeyboardManager = KeyboardManager;
616
618
617 return IPython;
619 return IPython;
618
620
619 }(IPython));
621 }(IPython));
@@ -1,2180 +1,2185
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2011 The IPython Development Team
2 // Copyright (C) 2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Notebook
9 // Notebook
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13 "use strict";
14
14
15 var utils = IPython.utils;
15 var utils = IPython.utils;
16
16
17 /**
17 /**
18 * A notebook contains and manages cells.
18 * A notebook contains and manages cells.
19 *
19 *
20 * @class Notebook
20 * @class Notebook
21 * @constructor
21 * @constructor
22 * @param {String} selector A jQuery selector for the notebook's DOM element
22 * @param {String} selector A jQuery selector for the notebook's DOM element
23 * @param {Object} [options] A config object
23 * @param {Object} [options] A config object
24 */
24 */
25 var Notebook = function (selector, options) {
25 var Notebook = function (selector, options) {
26 var options = options || {};
26 var options = options || {};
27 this._baseProjectUrl = options.baseProjectUrl;
27 this._baseProjectUrl = options.baseProjectUrl;
28 this.notebook_path = options.notebookPath;
28 this.notebook_path = options.notebookPath;
29 this.notebook_name = options.notebookName;
29 this.notebook_name = options.notebookName;
30 this.element = $(selector);
30 this.element = $(selector);
31 this.element.scroll();
31 this.element.scroll();
32 this.element.data("notebook", this);
32 this.element.data("notebook", this);
33 this.next_prompt_number = 1;
33 this.next_prompt_number = 1;
34 this.session = null;
34 this.session = null;
35 this.kernel = null;
35 this.kernel = null;
36 this.clipboard = null;
36 this.clipboard = null;
37 this.undelete_backup = null;
37 this.undelete_backup = null;
38 this.undelete_index = null;
38 this.undelete_index = null;
39 this.undelete_below = false;
39 this.undelete_below = false;
40 this.paste_enabled = false;
40 this.paste_enabled = false;
41 // It is important to start out in command mode to match the intial mode
41 // It is important to start out in command mode to match the intial mode
42 // of the KeyboardManager.
42 // of the KeyboardManager.
43 this.mode = 'command';
43 this.mode = 'command';
44 this.set_dirty(false);
44 this.set_dirty(false);
45 this.metadata = {};
45 this.metadata = {};
46 this._checkpoint_after_save = false;
46 this._checkpoint_after_save = false;
47 this.last_checkpoint = null;
47 this.last_checkpoint = null;
48 this.checkpoints = [];
48 this.checkpoints = [];
49 this.autosave_interval = 0;
49 this.autosave_interval = 0;
50 this.autosave_timer = null;
50 this.autosave_timer = null;
51 // autosave *at most* every two minutes
51 // autosave *at most* every two minutes
52 this.minimum_autosave_interval = 120000;
52 this.minimum_autosave_interval = 120000;
53 // single worksheet for now
53 // single worksheet for now
54 this.worksheet_metadata = {};
54 this.worksheet_metadata = {};
55 this.notebook_name_blacklist_re = /[\/\\:]/;
55 this.notebook_name_blacklist_re = /[\/\\:]/;
56 this.nbformat = 3 // Increment this when changing the nbformat
56 this.nbformat = 3 // Increment this when changing the nbformat
57 this.nbformat_minor = 0 // Increment this when changing the nbformat
57 this.nbformat_minor = 0 // Increment this when changing the nbformat
58 this.style();
58 this.style();
59 this.create_elements();
59 this.create_elements();
60 this.bind_events();
60 this.bind_events();
61 };
61 };
62
62
63 /**
63 /**
64 * Tweak the notebook's CSS style.
64 * Tweak the notebook's CSS style.
65 *
65 *
66 * @method style
66 * @method style
67 */
67 */
68 Notebook.prototype.style = function () {
68 Notebook.prototype.style = function () {
69 $('div#notebook').addClass('border-box-sizing');
69 $('div#notebook').addClass('border-box-sizing');
70 };
70 };
71
71
72 /**
72 /**
73 * Get the root URL of the notebook server.
73 * Get the root URL of the notebook server.
74 *
74 *
75 * @method baseProjectUrl
75 * @method baseProjectUrl
76 * @return {String} The base project URL
76 * @return {String} The base project URL
77 */
77 */
78 Notebook.prototype.baseProjectUrl = function() {
78 Notebook.prototype.baseProjectUrl = function() {
79 return this._baseProjectUrl || $('body').data('baseProjectUrl');
79 return this._baseProjectUrl || $('body').data('baseProjectUrl');
80 };
80 };
81
81
82 Notebook.prototype.notebookName = function() {
82 Notebook.prototype.notebookName = function() {
83 return $('body').data('notebookName');
83 return $('body').data('notebookName');
84 };
84 };
85
85
86 Notebook.prototype.notebookPath = function() {
86 Notebook.prototype.notebookPath = function() {
87 return $('body').data('notebookPath');
87 return $('body').data('notebookPath');
88 };
88 };
89
89
90 /**
90 /**
91 * Create an HTML and CSS representation of the notebook.
91 * Create an HTML and CSS representation of the notebook.
92 *
92 *
93 * @method create_elements
93 * @method create_elements
94 */
94 */
95 Notebook.prototype.create_elements = function () {
95 Notebook.prototype.create_elements = function () {
96 var that = this;
96 var that = this;
97 this.element.attr('tabindex','-1');
97 this.element.attr('tabindex','-1');
98 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
98 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
99 // We add this end_space div to the end of the notebook div to:
99 // We add this end_space div to the end of the notebook div to:
100 // i) provide a margin between the last cell and the end of the notebook
100 // i) provide a margin between the last cell and the end of the notebook
101 // ii) to prevent the div from scrolling up when the last cell is being
101 // ii) to prevent the div from scrolling up when the last cell is being
102 // edited, but is too low on the page, which browsers will do automatically.
102 // edited, but is too low on the page, which browsers will do automatically.
103 var end_space = $('<div/>').addClass('end_space');
103 var end_space = $('<div/>').addClass('end_space');
104 end_space.dblclick(function (e) {
104 end_space.dblclick(function (e) {
105 var ncells = that.ncells();
105 var ncells = that.ncells();
106 that.insert_cell_below('code',ncells-1);
106 that.insert_cell_below('code',ncells-1);
107 });
107 });
108 this.element.append(this.container);
108 this.element.append(this.container);
109 this.container.append(end_space);
109 this.container.append(end_space);
110 };
110 };
111
111
112 /**
112 /**
113 * Bind JavaScript events: key presses and custom IPython events.
113 * Bind JavaScript events: key presses and custom IPython events.
114 *
114 *
115 * @method bind_events
115 * @method bind_events
116 */
116 */
117 Notebook.prototype.bind_events = function () {
117 Notebook.prototype.bind_events = function () {
118 var that = this;
118 var that = this;
119
119
120 $([IPython.events]).on('set_next_input.Notebook', function (event, data) {
120 $([IPython.events]).on('set_next_input.Notebook', function (event, data) {
121 var index = that.find_cell_index(data.cell);
121 var index = that.find_cell_index(data.cell);
122 var new_cell = that.insert_cell_below('code',index);
122 var new_cell = that.insert_cell_below('code',index);
123 new_cell.set_text(data.text);
123 new_cell.set_text(data.text);
124 that.dirty = true;
124 that.dirty = true;
125 });
125 });
126
126
127 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
127 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
128 that.dirty = data.value;
128 that.dirty = data.value;
129 });
129 });
130
130
131 $([IPython.events]).on('select.Cell', function (event, data) {
131 $([IPython.events]).on('select.Cell', function (event, data) {
132 var index = that.find_cell_index(data.cell);
132 var index = that.find_cell_index(data.cell);
133 that.select(index);
133 that.select(index);
134 });
134 });
135
135
136 $([IPython.events]).on('edit_mode.Cell', function (event, data) {
136 $([IPython.events]).on('edit_mode.Cell', function (event, data) {
137 var index = that.find_cell_index(data.cell);
137 var index = that.find_cell_index(data.cell);
138 that.select(index);
138 that.select(index);
139 that.edit_mode();
139 that.edit_mode();
140 });
140 });
141
141
142 $([IPython.events]).on('command_mode.Cell', function (event, data) {
142 $([IPython.events]).on('command_mode.Cell', function (event, data) {
143 that.command_mode();
143 that.command_mode();
144 });
144 });
145
145
146 $([IPython.events]).on('status_autorestarting.Kernel', function () {
146 $([IPython.events]).on('status_autorestarting.Kernel', function () {
147 IPython.dialog.modal({
147 IPython.dialog.modal({
148 title: "Kernel Restarting",
148 title: "Kernel Restarting",
149 body: "The kernel appears to have died. It will restart automatically.",
149 body: "The kernel appears to have died. It will restart automatically.",
150 buttons: {
150 buttons: {
151 OK : {
151 OK : {
152 class : "btn-primary"
152 class : "btn-primary"
153 }
153 }
154 }
154 }
155 });
155 });
156 });
156 });
157
157
158 var collapse_time = function (time) {
158 var collapse_time = function (time) {
159 var app_height = $('#ipython-main-app').height(); // content height
159 var app_height = $('#ipython-main-app').height(); // content height
160 var splitter_height = $('div#pager_splitter').outerHeight(true);
160 var splitter_height = $('div#pager_splitter').outerHeight(true);
161 var new_height = app_height - splitter_height;
161 var new_height = app_height - splitter_height;
162 that.element.animate({height : new_height + 'px'}, time);
162 that.element.animate({height : new_height + 'px'}, time);
163 };
163 };
164
164
165 this.element.bind('collapse_pager', function (event, extrap) {
165 this.element.bind('collapse_pager', function (event, extrap) {
166 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
166 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
167 collapse_time(time);
167 collapse_time(time);
168 });
168 });
169
169
170 var expand_time = function (time) {
170 var expand_time = function (time) {
171 var app_height = $('#ipython-main-app').height(); // content height
171 var app_height = $('#ipython-main-app').height(); // content height
172 var splitter_height = $('div#pager_splitter').outerHeight(true);
172 var splitter_height = $('div#pager_splitter').outerHeight(true);
173 var pager_height = $('div#pager').outerHeight(true);
173 var pager_height = $('div#pager').outerHeight(true);
174 var new_height = app_height - pager_height - splitter_height;
174 var new_height = app_height - pager_height - splitter_height;
175 that.element.animate({height : new_height + 'px'}, time);
175 that.element.animate({height : new_height + 'px'}, time);
176 };
176 };
177
177
178 this.element.bind('expand_pager', function (event, extrap) {
178 this.element.bind('expand_pager', function (event, extrap) {
179 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
179 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
180 expand_time(time);
180 expand_time(time);
181 });
181 });
182
182
183 // Firefox 22 broke $(window).on("beforeunload")
183 // Firefox 22 broke $(window).on("beforeunload")
184 // I'm not sure why or how.
184 // I'm not sure why or how.
185 window.onbeforeunload = function (e) {
185 window.onbeforeunload = function (e) {
186 // TODO: Make killing the kernel configurable.
186 // TODO: Make killing the kernel configurable.
187 var kill_kernel = false;
187 var kill_kernel = false;
188 if (kill_kernel) {
188 if (kill_kernel) {
189 that.session.kill_kernel();
189 that.session.kill_kernel();
190 }
190 }
191 // if we are autosaving, trigger an autosave on nav-away.
191 // if we are autosaving, trigger an autosave on nav-away.
192 // still warn, because if we don't the autosave may fail.
192 // still warn, because if we don't the autosave may fail.
193 if (that.dirty) {
193 if (that.dirty) {
194 if ( that.autosave_interval ) {
194 if ( that.autosave_interval ) {
195 // schedule autosave in a timeout
195 // schedule autosave in a timeout
196 // this gives you a chance to forcefully discard changes
196 // this gives you a chance to forcefully discard changes
197 // by reloading the page if you *really* want to.
197 // by reloading the page if you *really* want to.
198 // the timer doesn't start until you *dismiss* the dialog.
198 // the timer doesn't start until you *dismiss* the dialog.
199 setTimeout(function () {
199 setTimeout(function () {
200 if (that.dirty) {
200 if (that.dirty) {
201 that.save_notebook();
201 that.save_notebook();
202 }
202 }
203 }, 1000);
203 }, 1000);
204 return "Autosave in progress, latest changes may be lost.";
204 return "Autosave in progress, latest changes may be lost.";
205 } else {
205 } else {
206 return "Unsaved changes will be lost.";
206 return "Unsaved changes will be lost.";
207 }
207 }
208 };
208 };
209 // Null is the *only* return value that will make the browser not
209 // Null is the *only* return value that will make the browser not
210 // pop up the "don't leave" dialog.
210 // pop up the "don't leave" dialog.
211 return null;
211 return null;
212 };
212 };
213 };
213 };
214
214
215 /**
215 /**
216 * Set the dirty flag, and trigger the set_dirty.Notebook event
216 * Set the dirty flag, and trigger the set_dirty.Notebook event
217 *
217 *
218 * @method set_dirty
218 * @method set_dirty
219 */
219 */
220 Notebook.prototype.set_dirty = function (value) {
220 Notebook.prototype.set_dirty = function (value) {
221 if (value === undefined) {
221 if (value === undefined) {
222 value = true;
222 value = true;
223 }
223 }
224 if (this.dirty == value) {
224 if (this.dirty == value) {
225 return;
225 return;
226 }
226 }
227 $([IPython.events]).trigger('set_dirty.Notebook', {value: value});
227 $([IPython.events]).trigger('set_dirty.Notebook', {value: value});
228 };
228 };
229
229
230 /**
230 /**
231 * Scroll the top of the page to a given cell.
231 * Scroll the top of the page to a given cell.
232 *
232 *
233 * @method scroll_to_cell
233 * @method scroll_to_cell
234 * @param {Number} cell_number An index of the cell to view
234 * @param {Number} cell_number An index of the cell to view
235 * @param {Number} time Animation time in milliseconds
235 * @param {Number} time Animation time in milliseconds
236 * @return {Number} Pixel offset from the top of the container
236 * @return {Number} Pixel offset from the top of the container
237 */
237 */
238 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
238 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
239 var cells = this.get_cells();
239 var cells = this.get_cells();
240 var time = time || 0;
240 var time = time || 0;
241 cell_number = Math.min(cells.length-1,cell_number);
241 cell_number = Math.min(cells.length-1,cell_number);
242 cell_number = Math.max(0 ,cell_number);
242 cell_number = Math.max(0 ,cell_number);
243 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
243 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
244 this.element.animate({scrollTop:scroll_value}, time);
244 this.element.animate({scrollTop:scroll_value}, time);
245 return scroll_value;
245 return scroll_value;
246 };
246 };
247
247
248 /**
248 /**
249 * Scroll to the bottom of the page.
249 * Scroll to the bottom of the page.
250 *
250 *
251 * @method scroll_to_bottom
251 * @method scroll_to_bottom
252 */
252 */
253 Notebook.prototype.scroll_to_bottom = function () {
253 Notebook.prototype.scroll_to_bottom = function () {
254 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
254 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
255 };
255 };
256
256
257 /**
257 /**
258 * Scroll to the top of the page.
258 * Scroll to the top of the page.
259 *
259 *
260 * @method scroll_to_top
260 * @method scroll_to_top
261 */
261 */
262 Notebook.prototype.scroll_to_top = function () {
262 Notebook.prototype.scroll_to_top = function () {
263 this.element.animate({scrollTop:0}, 0);
263 this.element.animate({scrollTop:0}, 0);
264 };
264 };
265
265
266 // Edit Notebook metadata
266 // Edit Notebook metadata
267
267
268 Notebook.prototype.edit_metadata = function () {
268 Notebook.prototype.edit_metadata = function () {
269 var that = this;
269 var that = this;
270 IPython.dialog.edit_metadata(this.metadata, function (md) {
270 IPython.dialog.edit_metadata(this.metadata, function (md) {
271 that.metadata = md;
271 that.metadata = md;
272 }, 'Notebook');
272 }, 'Notebook');
273 };
273 };
274
274
275 // Cell indexing, retrieval, etc.
275 // Cell indexing, retrieval, etc.
276
276
277 /**
277 /**
278 * Get all cell elements in the notebook.
278 * Get all cell elements in the notebook.
279 *
279 *
280 * @method get_cell_elements
280 * @method get_cell_elements
281 * @return {jQuery} A selector of all cell elements
281 * @return {jQuery} A selector of all cell elements
282 */
282 */
283 Notebook.prototype.get_cell_elements = function () {
283 Notebook.prototype.get_cell_elements = function () {
284 return this.container.children("div.cell");
284 return this.container.children("div.cell");
285 };
285 };
286
286
287 /**
287 /**
288 * Get a particular cell element.
288 * Get a particular cell element.
289 *
289 *
290 * @method get_cell_element
290 * @method get_cell_element
291 * @param {Number} index An index of a cell to select
291 * @param {Number} index An index of a cell to select
292 * @return {jQuery} A selector of the given cell.
292 * @return {jQuery} A selector of the given cell.
293 */
293 */
294 Notebook.prototype.get_cell_element = function (index) {
294 Notebook.prototype.get_cell_element = function (index) {
295 var result = null;
295 var result = null;
296 var e = this.get_cell_elements().eq(index);
296 var e = this.get_cell_elements().eq(index);
297 if (e.length !== 0) {
297 if (e.length !== 0) {
298 result = e;
298 result = e;
299 }
299 }
300 return result;
300 return result;
301 };
301 };
302
302
303 /**
303 /**
304 * Count the cells in this notebook.
304 * Count the cells in this notebook.
305 *
305 *
306 * @method ncells
306 * @method ncells
307 * @return {Number} The number of cells in this notebook
307 * @return {Number} The number of cells in this notebook
308 */
308 */
309 Notebook.prototype.ncells = function () {
309 Notebook.prototype.ncells = function () {
310 return this.get_cell_elements().length;
310 return this.get_cell_elements().length;
311 };
311 };
312
312
313 /**
313 /**
314 * Get all Cell objects in this notebook.
314 * Get all Cell objects in this notebook.
315 *
315 *
316 * @method get_cells
316 * @method get_cells
317 * @return {Array} This notebook's Cell objects
317 * @return {Array} This notebook's Cell objects
318 */
318 */
319 // TODO: we are often calling cells as cells()[i], which we should optimize
319 // TODO: we are often calling cells as cells()[i], which we should optimize
320 // to cells(i) or a new method.
320 // to cells(i) or a new method.
321 Notebook.prototype.get_cells = function () {
321 Notebook.prototype.get_cells = function () {
322 return this.get_cell_elements().toArray().map(function (e) {
322 return this.get_cell_elements().toArray().map(function (e) {
323 return $(e).data("cell");
323 return $(e).data("cell");
324 });
324 });
325 };
325 };
326
326
327 /**
327 /**
328 * Get a Cell object from this notebook.
328 * Get a Cell object from this notebook.
329 *
329 *
330 * @method get_cell
330 * @method get_cell
331 * @param {Number} index An index of a cell to retrieve
331 * @param {Number} index An index of a cell to retrieve
332 * @return {Cell} A particular cell
332 * @return {Cell} A particular cell
333 */
333 */
334 Notebook.prototype.get_cell = function (index) {
334 Notebook.prototype.get_cell = function (index) {
335 var result = null;
335 var result = null;
336 var ce = this.get_cell_element(index);
336 var ce = this.get_cell_element(index);
337 if (ce !== null) {
337 if (ce !== null) {
338 result = ce.data('cell');
338 result = ce.data('cell');
339 }
339 }
340 return result;
340 return result;
341 }
341 }
342
342
343 /**
343 /**
344 * Get the cell below a given cell.
344 * Get the cell below a given cell.
345 *
345 *
346 * @method get_next_cell
346 * @method get_next_cell
347 * @param {Cell} cell The provided cell
347 * @param {Cell} cell The provided cell
348 * @return {Cell} The next cell
348 * @return {Cell} The next cell
349 */
349 */
350 Notebook.prototype.get_next_cell = function (cell) {
350 Notebook.prototype.get_next_cell = function (cell) {
351 var result = null;
351 var result = null;
352 var index = this.find_cell_index(cell);
352 var index = this.find_cell_index(cell);
353 if (this.is_valid_cell_index(index+1)) {
353 if (this.is_valid_cell_index(index+1)) {
354 result = this.get_cell(index+1);
354 result = this.get_cell(index+1);
355 }
355 }
356 return result;
356 return result;
357 }
357 }
358
358
359 /**
359 /**
360 * Get the cell above a given cell.
360 * Get the cell above a given cell.
361 *
361 *
362 * @method get_prev_cell
362 * @method get_prev_cell
363 * @param {Cell} cell The provided cell
363 * @param {Cell} cell The provided cell
364 * @return {Cell} The previous cell
364 * @return {Cell} The previous cell
365 */
365 */
366 Notebook.prototype.get_prev_cell = function (cell) {
366 Notebook.prototype.get_prev_cell = function (cell) {
367 // TODO: off-by-one
367 // TODO: off-by-one
368 // nb.get_prev_cell(nb.get_cell(1)) is null
368 // nb.get_prev_cell(nb.get_cell(1)) is null
369 var result = null;
369 var result = null;
370 var index = this.find_cell_index(cell);
370 var index = this.find_cell_index(cell);
371 if (index !== null && index > 1) {
371 if (index !== null && index > 1) {
372 result = this.get_cell(index-1);
372 result = this.get_cell(index-1);
373 }
373 }
374 return result;
374 return result;
375 }
375 }
376
376
377 /**
377 /**
378 * Get the numeric index of a given cell.
378 * Get the numeric index of a given cell.
379 *
379 *
380 * @method find_cell_index
380 * @method find_cell_index
381 * @param {Cell} cell The provided cell
381 * @param {Cell} cell The provided cell
382 * @return {Number} The cell's numeric index
382 * @return {Number} The cell's numeric index
383 */
383 */
384 Notebook.prototype.find_cell_index = function (cell) {
384 Notebook.prototype.find_cell_index = function (cell) {
385 var result = null;
385 var result = null;
386 this.get_cell_elements().filter(function (index) {
386 this.get_cell_elements().filter(function (index) {
387 if ($(this).data("cell") === cell) {
387 if ($(this).data("cell") === cell) {
388 result = index;
388 result = index;
389 };
389 };
390 });
390 });
391 return result;
391 return result;
392 };
392 };
393
393
394 /**
394 /**
395 * Get a given index , or the selected index if none is provided.
395 * Get a given index , or the selected index if none is provided.
396 *
396 *
397 * @method index_or_selected
397 * @method index_or_selected
398 * @param {Number} index A cell's index
398 * @param {Number} index A cell's index
399 * @return {Number} The given index, or selected index if none is provided.
399 * @return {Number} The given index, or selected index if none is provided.
400 */
400 */
401 Notebook.prototype.index_or_selected = function (index) {
401 Notebook.prototype.index_or_selected = function (index) {
402 var i;
402 var i;
403 if (index === undefined || index === null) {
403 if (index === undefined || index === null) {
404 i = this.get_selected_index();
404 i = this.get_selected_index();
405 if (i === null) {
405 if (i === null) {
406 i = 0;
406 i = 0;
407 }
407 }
408 } else {
408 } else {
409 i = index;
409 i = index;
410 }
410 }
411 return i;
411 return i;
412 };
412 };
413
413
414 /**
414 /**
415 * Get the currently selected cell.
415 * Get the currently selected cell.
416 * @method get_selected_cell
416 * @method get_selected_cell
417 * @return {Cell} The selected cell
417 * @return {Cell} The selected cell
418 */
418 */
419 Notebook.prototype.get_selected_cell = function () {
419 Notebook.prototype.get_selected_cell = function () {
420 var index = this.get_selected_index();
420 var index = this.get_selected_index();
421 return this.get_cell(index);
421 return this.get_cell(index);
422 };
422 };
423
423
424 /**
424 /**
425 * Check whether a cell index is valid.
425 * Check whether a cell index is valid.
426 *
426 *
427 * @method is_valid_cell_index
427 * @method is_valid_cell_index
428 * @param {Number} index A cell index
428 * @param {Number} index A cell index
429 * @return True if the index is valid, false otherwise
429 * @return True if the index is valid, false otherwise
430 */
430 */
431 Notebook.prototype.is_valid_cell_index = function (index) {
431 Notebook.prototype.is_valid_cell_index = function (index) {
432 if (index !== null && index >= 0 && index < this.ncells()) {
432 if (index !== null && index >= 0 && index < this.ncells()) {
433 return true;
433 return true;
434 } else {
434 } else {
435 return false;
435 return false;
436 };
436 };
437 }
437 }
438
438
439 /**
439 /**
440 * Get the index of the currently selected cell.
440 * Get the index of the currently selected cell.
441
441
442 * @method get_selected_index
442 * @method get_selected_index
443 * @return {Number} The selected cell's numeric index
443 * @return {Number} The selected cell's numeric index
444 */
444 */
445 Notebook.prototype.get_selected_index = function () {
445 Notebook.prototype.get_selected_index = function () {
446 var result = null;
446 var result = null;
447 this.get_cell_elements().filter(function (index) {
447 this.get_cell_elements().filter(function (index) {
448 if ($(this).data("cell").selected === true) {
448 if ($(this).data("cell").selected === true) {
449 result = index;
449 result = index;
450 };
450 };
451 });
451 });
452 return result;
452 return result;
453 };
453 };
454
454
455
455
456 // Cell selection.
456 // Cell selection.
457
457
458 /**
458 /**
459 * Programmatically select a cell.
459 * Programmatically select a cell.
460 *
460 *
461 * @method select
461 * @method select
462 * @param {Number} index A cell's index
462 * @param {Number} index A cell's index
463 * @return {Notebook} This notebook
463 * @return {Notebook} This notebook
464 */
464 */
465 Notebook.prototype.select = function (index) {
465 Notebook.prototype.select = function (index) {
466 if (this.is_valid_cell_index(index)) {
466 if (this.is_valid_cell_index(index)) {
467 var sindex = this.get_selected_index()
467 var sindex = this.get_selected_index()
468 if (sindex !== null && index !== sindex) {
468 if (sindex !== null && index !== sindex) {
469 this.command_mode();
469 this.command_mode();
470 this.get_cell(sindex).unselect();
470 this.get_cell(sindex).unselect();
471 };
471 };
472 var cell = this.get_cell(index);
472 var cell = this.get_cell(index);
473 cell.select();
473 cell.select();
474 if (cell.cell_type === 'heading') {
474 if (cell.cell_type === 'heading') {
475 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
475 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
476 {'cell_type':cell.cell_type,level:cell.level}
476 {'cell_type':cell.cell_type,level:cell.level}
477 );
477 );
478 } else {
478 } else {
479 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
479 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
480 {'cell_type':cell.cell_type}
480 {'cell_type':cell.cell_type}
481 );
481 );
482 };
482 };
483 };
483 };
484 return this;
484 return this;
485 };
485 };
486
486
487 /**
487 /**
488 * Programmatically select the next cell.
488 * Programmatically select the next cell.
489 *
489 *
490 * @method select_next
490 * @method select_next
491 * @return {Notebook} This notebook
491 * @return {Notebook} This notebook
492 */
492 */
493 Notebook.prototype.select_next = function () {
493 Notebook.prototype.select_next = function () {
494 var index = this.get_selected_index();
494 var index = this.get_selected_index();
495 this.select(index+1);
495 this.select(index+1);
496 return this;
496 return this;
497 };
497 };
498
498
499 /**
499 /**
500 * Programmatically select the previous cell.
500 * Programmatically select the previous cell.
501 *
501 *
502 * @method select_prev
502 * @method select_prev
503 * @return {Notebook} This notebook
503 * @return {Notebook} This notebook
504 */
504 */
505 Notebook.prototype.select_prev = function () {
505 Notebook.prototype.select_prev = function () {
506 var index = this.get_selected_index();
506 var index = this.get_selected_index();
507 this.select(index-1);
507 this.select(index-1);
508 return this;
508 return this;
509 };
509 };
510
510
511
511
512 // Edit/Command mode
512 // Edit/Command mode
513
513
514 Notebook.prototype.get_edit_index = function () {
514 Notebook.prototype.get_edit_index = function () {
515 var result = null;
515 var result = null;
516 this.get_cell_elements().filter(function (index) {
516 this.get_cell_elements().filter(function (index) {
517 if ($(this).data("cell").mode === 'edit') {
517 if ($(this).data("cell").mode === 'edit') {
518 result = index;
518 result = index;
519 };
519 };
520 });
520 });
521 return result;
521 return result;
522 };
522 };
523
523
524 Notebook.prototype.command_mode = function () {
524 Notebook.prototype.command_mode = function () {
525 if (this.mode !== 'command') {
525 if (this.mode !== 'command') {
526 console.log('\nNotebook', 'changing to command mode');
526 console.log('\nNotebook', 'changing to command mode');
527 var index = this.get_edit_index();
527 var index = this.get_edit_index();
528 var cell = this.get_cell(index);
528 var cell = this.get_cell(index);
529 if (cell) {
529 if (cell) {
530 cell.command_mode();
530 cell.command_mode();
531 };
531 };
532 this.mode = 'command';
532 this.mode = 'command';
533 IPython.keyboard_manager.command_mode();
533 IPython.keyboard_manager.command_mode();
534 };
534 };
535 };
535 };
536
536
537 Notebook.prototype.edit_mode = function () {
537 Notebook.prototype.edit_mode = function () {
538 if (this.mode !== 'edit') {
538 if (this.mode !== 'edit') {
539 console.log('\nNotebook', 'changing to edit mode');
539 console.log('\nNotebook', 'changing to edit mode');
540 var cell = this.get_selected_cell();
540 var cell = this.get_selected_cell();
541 if (cell === null) {return;} // No cell is selected
541 if (cell === null) {return;} // No cell is selected
542 // We need to set the mode to edit to prevent reentering this method
542 // We need to set the mode to edit to prevent reentering this method
543 // when cell.edit_mode() is called below.
543 // when cell.edit_mode() is called below.
544 this.mode = 'edit';
544 this.mode = 'edit';
545 IPython.keyboard_manager.edit_mode();
545 IPython.keyboard_manager.edit_mode();
546 cell.edit_mode();
546 cell.edit_mode();
547 };
547 };
548 };
548 };
549
549
550 Notebook.prototype.focus_cell = function () {
551 var cell = this.get_selected_cell();
552 if (cell === null) {return;} // No cell is selected
553 cell.focus_cell();
554 };
550
555
551 // Cell movement
556 // Cell movement
552
557
553 /**
558 /**
554 * Move given (or selected) cell up and select it.
559 * Move given (or selected) cell up and select it.
555 *
560 *
556 * @method move_cell_up
561 * @method move_cell_up
557 * @param [index] {integer} cell index
562 * @param [index] {integer} cell index
558 * @return {Notebook} This notebook
563 * @return {Notebook} This notebook
559 **/
564 **/
560 Notebook.prototype.move_cell_up = function (index) {
565 Notebook.prototype.move_cell_up = function (index) {
561 var i = this.index_or_selected(index);
566 var i = this.index_or_selected(index);
562 if (this.is_valid_cell_index(i) && i > 0) {
567 if (this.is_valid_cell_index(i) && i > 0) {
563 var pivot = this.get_cell_element(i-1);
568 var pivot = this.get_cell_element(i-1);
564 var tomove = this.get_cell_element(i);
569 var tomove = this.get_cell_element(i);
565 if (pivot !== null && tomove !== null) {
570 if (pivot !== null && tomove !== null) {
566 tomove.detach();
571 tomove.detach();
567 pivot.before(tomove);
572 pivot.before(tomove);
568 this.select(i-1);
573 this.select(i-1);
569 var cell = this.get_selected_cell();
574 var cell = this.get_selected_cell();
570 cell.focus_cell();
575 cell.focus_cell();
571 };
576 };
572 this.set_dirty(true);
577 this.set_dirty(true);
573 };
578 };
574 return this;
579 return this;
575 };
580 };
576
581
577
582
578 /**
583 /**
579 * Move given (or selected) cell down and select it
584 * Move given (or selected) cell down and select it
580 *
585 *
581 * @method move_cell_down
586 * @method move_cell_down
582 * @param [index] {integer} cell index
587 * @param [index] {integer} cell index
583 * @return {Notebook} This notebook
588 * @return {Notebook} This notebook
584 **/
589 **/
585 Notebook.prototype.move_cell_down = function (index) {
590 Notebook.prototype.move_cell_down = function (index) {
586 var i = this.index_or_selected(index);
591 var i = this.index_or_selected(index);
587 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
592 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
588 var pivot = this.get_cell_element(i+1);
593 var pivot = this.get_cell_element(i+1);
589 var tomove = this.get_cell_element(i);
594 var tomove = this.get_cell_element(i);
590 if (pivot !== null && tomove !== null) {
595 if (pivot !== null && tomove !== null) {
591 tomove.detach();
596 tomove.detach();
592 pivot.after(tomove);
597 pivot.after(tomove);
593 this.select(i+1);
598 this.select(i+1);
594 var cell = this.get_selected_cell();
599 var cell = this.get_selected_cell();
595 cell.focus_cell();
600 cell.focus_cell();
596 };
601 };
597 };
602 };
598 this.set_dirty();
603 this.set_dirty();
599 return this;
604 return this;
600 };
605 };
601
606
602
607
603 // Insertion, deletion.
608 // Insertion, deletion.
604
609
605 /**
610 /**
606 * Delete a cell from the notebook.
611 * Delete a cell from the notebook.
607 *
612 *
608 * @method delete_cell
613 * @method delete_cell
609 * @param [index] A cell's numeric index
614 * @param [index] A cell's numeric index
610 * @return {Notebook} This notebook
615 * @return {Notebook} This notebook
611 */
616 */
612 Notebook.prototype.delete_cell = function (index) {
617 Notebook.prototype.delete_cell = function (index) {
613 var i = this.index_or_selected(index);
618 var i = this.index_or_selected(index);
614 var cell = this.get_selected_cell();
619 var cell = this.get_selected_cell();
615 this.undelete_backup = cell.toJSON();
620 this.undelete_backup = cell.toJSON();
616 $('#undelete_cell').removeClass('disabled');
621 $('#undelete_cell').removeClass('disabled');
617 if (this.is_valid_cell_index(i)) {
622 if (this.is_valid_cell_index(i)) {
618 var old_ncells = this.ncells();
623 var old_ncells = this.ncells();
619 var ce = this.get_cell_element(i);
624 var ce = this.get_cell_element(i);
620 ce.remove();
625 ce.remove();
621 if (i === 0) {
626 if (i === 0) {
622 // Always make sure we have at least one cell.
627 // Always make sure we have at least one cell.
623 if (old_ncells === 1) {
628 if (old_ncells === 1) {
624 this.insert_cell_below('code');
629 this.insert_cell_below('code');
625 }
630 }
626 this.select(0);
631 this.select(0);
627 this.undelete_index = 0;
632 this.undelete_index = 0;
628 this.undelete_below = false;
633 this.undelete_below = false;
629 } else if (i === old_ncells-1 && i !== 0) {
634 } else if (i === old_ncells-1 && i !== 0) {
630 this.select(i-1);
635 this.select(i-1);
631 this.undelete_index = i - 1;
636 this.undelete_index = i - 1;
632 this.undelete_below = true;
637 this.undelete_below = true;
633 } else {
638 } else {
634 this.select(i);
639 this.select(i);
635 this.undelete_index = i;
640 this.undelete_index = i;
636 this.undelete_below = false;
641 this.undelete_below = false;
637 };
642 };
638 $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i});
643 $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i});
639 this.set_dirty(true);
644 this.set_dirty(true);
640 };
645 };
641 return this;
646 return this;
642 };
647 };
643
648
644 /**
649 /**
645 * Restore the most recently deleted cell.
650 * Restore the most recently deleted cell.
646 *
651 *
647 * @method undelete
652 * @method undelete
648 */
653 */
649 Notebook.prototype.undelete_cell = function() {
654 Notebook.prototype.undelete_cell = function() {
650 if (this.undelete_backup !== null && this.undelete_index !== null) {
655 if (this.undelete_backup !== null && this.undelete_index !== null) {
651 var current_index = this.get_selected_index();
656 var current_index = this.get_selected_index();
652 if (this.undelete_index < current_index) {
657 if (this.undelete_index < current_index) {
653 current_index = current_index + 1;
658 current_index = current_index + 1;
654 }
659 }
655 if (this.undelete_index >= this.ncells()) {
660 if (this.undelete_index >= this.ncells()) {
656 this.select(this.ncells() - 1);
661 this.select(this.ncells() - 1);
657 }
662 }
658 else {
663 else {
659 this.select(this.undelete_index);
664 this.select(this.undelete_index);
660 }
665 }
661 var cell_data = this.undelete_backup;
666 var cell_data = this.undelete_backup;
662 var new_cell = null;
667 var new_cell = null;
663 if (this.undelete_below) {
668 if (this.undelete_below) {
664 new_cell = this.insert_cell_below(cell_data.cell_type);
669 new_cell = this.insert_cell_below(cell_data.cell_type);
665 } else {
670 } else {
666 new_cell = this.insert_cell_above(cell_data.cell_type);
671 new_cell = this.insert_cell_above(cell_data.cell_type);
667 }
672 }
668 new_cell.fromJSON(cell_data);
673 new_cell.fromJSON(cell_data);
669 if (this.undelete_below) {
674 if (this.undelete_below) {
670 this.select(current_index+1);
675 this.select(current_index+1);
671 } else {
676 } else {
672 this.select(current_index);
677 this.select(current_index);
673 }
678 }
674 this.undelete_backup = null;
679 this.undelete_backup = null;
675 this.undelete_index = null;
680 this.undelete_index = null;
676 }
681 }
677 $('#undelete_cell').addClass('disabled');
682 $('#undelete_cell').addClass('disabled');
678 }
683 }
679
684
680 /**
685 /**
681 * Insert a cell so that after insertion the cell is at given index.
686 * Insert a cell so that after insertion the cell is at given index.
682 *
687 *
683 * Similar to insert_above, but index parameter is mandatory
688 * Similar to insert_above, but index parameter is mandatory
684 *
689 *
685 * Index will be brought back into the accissible range [0,n]
690 * Index will be brought back into the accissible range [0,n]
686 *
691 *
687 * @method insert_cell_at_index
692 * @method insert_cell_at_index
688 * @param type {string} in ['code','markdown','heading']
693 * @param type {string} in ['code','markdown','heading']
689 * @param [index] {int} a valid index where to inser cell
694 * @param [index] {int} a valid index where to inser cell
690 *
695 *
691 * @return cell {cell|null} created cell or null
696 * @return cell {cell|null} created cell or null
692 **/
697 **/
693 Notebook.prototype.insert_cell_at_index = function(type, index){
698 Notebook.prototype.insert_cell_at_index = function(type, index){
694
699
695 var ncells = this.ncells();
700 var ncells = this.ncells();
696 var index = Math.min(index,ncells);
701 var index = Math.min(index,ncells);
697 index = Math.max(index,0);
702 index = Math.max(index,0);
698 var cell = null;
703 var cell = null;
699
704
700 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
705 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
701 if (type === 'code') {
706 if (type === 'code') {
702 cell = new IPython.CodeCell(this.kernel);
707 cell = new IPython.CodeCell(this.kernel);
703 cell.set_input_prompt();
708 cell.set_input_prompt();
704 } else if (type === 'markdown') {
709 } else if (type === 'markdown') {
705 cell = new IPython.MarkdownCell();
710 cell = new IPython.MarkdownCell();
706 } else if (type === 'raw') {
711 } else if (type === 'raw') {
707 cell = new IPython.RawCell();
712 cell = new IPython.RawCell();
708 } else if (type === 'heading') {
713 } else if (type === 'heading') {
709 cell = new IPython.HeadingCell();
714 cell = new IPython.HeadingCell();
710 }
715 }
711
716
712 if(this._insert_element_at_index(cell.element,index)) {
717 if(this._insert_element_at_index(cell.element,index)) {
713 cell.render();
718 cell.render();
714 $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
719 $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
715 cell.refresh();
720 cell.refresh();
716 // We used to select the cell after we refresh it, but there
721 // We used to select the cell after we refresh it, but there
717 // are now cases were this method is called where select is
722 // are now cases were this method is called where select is
718 // not appropriate. The selection logic should be handled by the
723 // not appropriate. The selection logic should be handled by the
719 // caller of the the top level insert_cell methods.
724 // caller of the the top level insert_cell methods.
720 this.set_dirty(true);
725 this.set_dirty(true);
721 }
726 }
722 }
727 }
723 return cell;
728 return cell;
724
729
725 };
730 };
726
731
727 /**
732 /**
728 * Insert an element at given cell index.
733 * Insert an element at given cell index.
729 *
734 *
730 * @method _insert_element_at_index
735 * @method _insert_element_at_index
731 * @param element {dom element} a cell element
736 * @param element {dom element} a cell element
732 * @param [index] {int} a valid index where to inser cell
737 * @param [index] {int} a valid index where to inser cell
733 * @private
738 * @private
734 *
739 *
735 * return true if everything whent fine.
740 * return true if everything whent fine.
736 **/
741 **/
737 Notebook.prototype._insert_element_at_index = function(element, index){
742 Notebook.prototype._insert_element_at_index = function(element, index){
738 if (element === undefined){
743 if (element === undefined){
739 return false;
744 return false;
740 }
745 }
741
746
742 var ncells = this.ncells();
747 var ncells = this.ncells();
743
748
744 if (ncells === 0) {
749 if (ncells === 0) {
745 // special case append if empty
750 // special case append if empty
746 this.element.find('div.end_space').before(element);
751 this.element.find('div.end_space').before(element);
747 } else if ( ncells === index ) {
752 } else if ( ncells === index ) {
748 // special case append it the end, but not empty
753 // special case append it the end, but not empty
749 this.get_cell_element(index-1).after(element);
754 this.get_cell_element(index-1).after(element);
750 } else if (this.is_valid_cell_index(index)) {
755 } else if (this.is_valid_cell_index(index)) {
751 // otherwise always somewhere to append to
756 // otherwise always somewhere to append to
752 this.get_cell_element(index).before(element);
757 this.get_cell_element(index).before(element);
753 } else {
758 } else {
754 return false;
759 return false;
755 }
760 }
756
761
757 if (this.undelete_index !== null && index <= this.undelete_index) {
762 if (this.undelete_index !== null && index <= this.undelete_index) {
758 this.undelete_index = this.undelete_index + 1;
763 this.undelete_index = this.undelete_index + 1;
759 this.set_dirty(true);
764 this.set_dirty(true);
760 }
765 }
761 return true;
766 return true;
762 };
767 };
763
768
764 /**
769 /**
765 * Insert a cell of given type above given index, or at top
770 * Insert a cell of given type above given index, or at top
766 * of notebook if index smaller than 0.
771 * of notebook if index smaller than 0.
767 *
772 *
768 * default index value is the one of currently selected cell
773 * default index value is the one of currently selected cell
769 *
774 *
770 * @method insert_cell_above
775 * @method insert_cell_above
771 * @param type {string} cell type
776 * @param type {string} cell type
772 * @param [index] {integer}
777 * @param [index] {integer}
773 *
778 *
774 * @return handle to created cell or null
779 * @return handle to created cell or null
775 **/
780 **/
776 Notebook.prototype.insert_cell_above = function (type, index) {
781 Notebook.prototype.insert_cell_above = function (type, index) {
777 index = this.index_or_selected(index);
782 index = this.index_or_selected(index);
778 return this.insert_cell_at_index(type, index);
783 return this.insert_cell_at_index(type, index);
779 };
784 };
780
785
781 /**
786 /**
782 * Insert a cell of given type below given index, or at bottom
787 * Insert a cell of given type below given index, or at bottom
783 * of notebook if index greater thatn number of cell
788 * of notebook if index greater thatn number of cell
784 *
789 *
785 * default index value is the one of currently selected cell
790 * default index value is the one of currently selected cell
786 *
791 *
787 * @method insert_cell_below
792 * @method insert_cell_below
788 * @param type {string} cell type
793 * @param type {string} cell type
789 * @param [index] {integer}
794 * @param [index] {integer}
790 *
795 *
791 * @return handle to created cell or null
796 * @return handle to created cell or null
792 *
797 *
793 **/
798 **/
794 Notebook.prototype.insert_cell_below = function (type, index) {
799 Notebook.prototype.insert_cell_below = function (type, index) {
795 index = this.index_or_selected(index);
800 index = this.index_or_selected(index);
796 return this.insert_cell_at_index(type, index+1);
801 return this.insert_cell_at_index(type, index+1);
797 };
802 };
798
803
799
804
800 /**
805 /**
801 * Insert cell at end of notebook
806 * Insert cell at end of notebook
802 *
807 *
803 * @method insert_cell_at_bottom
808 * @method insert_cell_at_bottom
804 * @param {String} type cell type
809 * @param {String} type cell type
805 *
810 *
806 * @return the added cell; or null
811 * @return the added cell; or null
807 **/
812 **/
808 Notebook.prototype.insert_cell_at_bottom = function (type){
813 Notebook.prototype.insert_cell_at_bottom = function (type){
809 var len = this.ncells();
814 var len = this.ncells();
810 return this.insert_cell_below(type,len-1);
815 return this.insert_cell_below(type,len-1);
811 };
816 };
812
817
813 /**
818 /**
814 * Turn a cell into a code cell.
819 * Turn a cell into a code cell.
815 *
820 *
816 * @method to_code
821 * @method to_code
817 * @param {Number} [index] A cell's index
822 * @param {Number} [index] A cell's index
818 */
823 */
819 Notebook.prototype.to_code = function (index) {
824 Notebook.prototype.to_code = function (index) {
820 var i = this.index_or_selected(index);
825 var i = this.index_or_selected(index);
821 if (this.is_valid_cell_index(i)) {
826 if (this.is_valid_cell_index(i)) {
822 var source_element = this.get_cell_element(i);
827 var source_element = this.get_cell_element(i);
823 var source_cell = source_element.data("cell");
828 var source_cell = source_element.data("cell");
824 if (!(source_cell instanceof IPython.CodeCell)) {
829 if (!(source_cell instanceof IPython.CodeCell)) {
825 var target_cell = this.insert_cell_below('code',i);
830 var target_cell = this.insert_cell_below('code',i);
826 var text = source_cell.get_text();
831 var text = source_cell.get_text();
827 if (text === source_cell.placeholder) {
832 if (text === source_cell.placeholder) {
828 text = '';
833 text = '';
829 }
834 }
830 target_cell.set_text(text);
835 target_cell.set_text(text);
831 // make this value the starting point, so that we can only undo
836 // make this value the starting point, so that we can only undo
832 // to this state, instead of a blank cell
837 // to this state, instead of a blank cell
833 target_cell.code_mirror.clearHistory();
838 target_cell.code_mirror.clearHistory();
834 source_element.remove();
839 source_element.remove();
835 this.select(i);
840 this.select(i);
836 this.edit_mode();
841 this.edit_mode();
837 this.set_dirty(true);
842 this.set_dirty(true);
838 };
843 };
839 };
844 };
840 };
845 };
841
846
842 /**
847 /**
843 * Turn a cell into a Markdown cell.
848 * Turn a cell into a Markdown cell.
844 *
849 *
845 * @method to_markdown
850 * @method to_markdown
846 * @param {Number} [index] A cell's index
851 * @param {Number} [index] A cell's index
847 */
852 */
848 Notebook.prototype.to_markdown = function (index) {
853 Notebook.prototype.to_markdown = function (index) {
849 var i = this.index_or_selected(index);
854 var i = this.index_or_selected(index);
850 if (this.is_valid_cell_index(i)) {
855 if (this.is_valid_cell_index(i)) {
851 var source_element = this.get_cell_element(i);
856 var source_element = this.get_cell_element(i);
852 var source_cell = source_element.data("cell");
857 var source_cell = source_element.data("cell");
853 if (!(source_cell instanceof IPython.MarkdownCell)) {
858 if (!(source_cell instanceof IPython.MarkdownCell)) {
854 var target_cell = this.insert_cell_below('markdown',i);
859 var target_cell = this.insert_cell_below('markdown',i);
855 var text = source_cell.get_text();
860 var text = source_cell.get_text();
856 if (text === source_cell.placeholder) {
861 if (text === source_cell.placeholder) {
857 text = '';
862 text = '';
858 };
863 };
859 // We must show the editor before setting its contents
864 // We must show the editor before setting its contents
860 target_cell.unrender();
865 target_cell.unrender();
861 target_cell.set_text(text);
866 target_cell.set_text(text);
862 // make this value the starting point, so that we can only undo
867 // make this value the starting point, so that we can only undo
863 // to this state, instead of a blank cell
868 // to this state, instead of a blank cell
864 target_cell.code_mirror.clearHistory();
869 target_cell.code_mirror.clearHistory();
865 source_element.remove();
870 source_element.remove();
866 this.select(i);
871 this.select(i);
867 this.edit_mode();
872 this.edit_mode();
868 this.set_dirty(true);
873 this.set_dirty(true);
869 };
874 };
870 };
875 };
871 };
876 };
872
877
873 /**
878 /**
874 * Turn a cell into a raw text cell.
879 * Turn a cell into a raw text cell.
875 *
880 *
876 * @method to_raw
881 * @method to_raw
877 * @param {Number} [index] A cell's index
882 * @param {Number} [index] A cell's index
878 */
883 */
879 Notebook.prototype.to_raw = function (index) {
884 Notebook.prototype.to_raw = function (index) {
880 var i = this.index_or_selected(index);
885 var i = this.index_or_selected(index);
881 if (this.is_valid_cell_index(i)) {
886 if (this.is_valid_cell_index(i)) {
882 var source_element = this.get_cell_element(i);
887 var source_element = this.get_cell_element(i);
883 var source_cell = source_element.data("cell");
888 var source_cell = source_element.data("cell");
884 var target_cell = null;
889 var target_cell = null;
885 if (!(source_cell instanceof IPython.RawCell)) {
890 if (!(source_cell instanceof IPython.RawCell)) {
886 target_cell = this.insert_cell_below('raw',i);
891 target_cell = this.insert_cell_below('raw',i);
887 var text = source_cell.get_text();
892 var text = source_cell.get_text();
888 if (text === source_cell.placeholder) {
893 if (text === source_cell.placeholder) {
889 text = '';
894 text = '';
890 };
895 };
891 // We must show the editor before setting its contents
896 // We must show the editor before setting its contents
892 target_cell.unrender();
897 target_cell.unrender();
893 target_cell.set_text(text);
898 target_cell.set_text(text);
894 // make this value the starting point, so that we can only undo
899 // make this value the starting point, so that we can only undo
895 // to this state, instead of a blank cell
900 // to this state, instead of a blank cell
896 target_cell.code_mirror.clearHistory();
901 target_cell.code_mirror.clearHistory();
897 source_element.remove();
902 source_element.remove();
898 this.select(i);
903 this.select(i);
899 this.edit_mode();
904 this.edit_mode();
900 this.set_dirty(true);
905 this.set_dirty(true);
901 };
906 };
902 };
907 };
903 };
908 };
904
909
905 /**
910 /**
906 * Turn a cell into a heading cell.
911 * Turn a cell into a heading cell.
907 *
912 *
908 * @method to_heading
913 * @method to_heading
909 * @param {Number} [index] A cell's index
914 * @param {Number} [index] A cell's index
910 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
915 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
911 */
916 */
912 Notebook.prototype.to_heading = function (index, level) {
917 Notebook.prototype.to_heading = function (index, level) {
913 level = level || 1;
918 level = level || 1;
914 var i = this.index_or_selected(index);
919 var i = this.index_or_selected(index);
915 if (this.is_valid_cell_index(i)) {
920 if (this.is_valid_cell_index(i)) {
916 var source_element = this.get_cell_element(i);
921 var source_element = this.get_cell_element(i);
917 var source_cell = source_element.data("cell");
922 var source_cell = source_element.data("cell");
918 var target_cell = null;
923 var target_cell = null;
919 if (source_cell instanceof IPython.HeadingCell) {
924 if (source_cell instanceof IPython.HeadingCell) {
920 source_cell.set_level(level);
925 source_cell.set_level(level);
921 } else {
926 } else {
922 target_cell = this.insert_cell_below('heading',i);
927 target_cell = this.insert_cell_below('heading',i);
923 var text = source_cell.get_text();
928 var text = source_cell.get_text();
924 if (text === source_cell.placeholder) {
929 if (text === source_cell.placeholder) {
925 text = '';
930 text = '';
926 };
931 };
927 // We must show the editor before setting its contents
932 // We must show the editor before setting its contents
928 target_cell.set_level(level);
933 target_cell.set_level(level);
929 target_cell.unrender();
934 target_cell.unrender();
930 target_cell.set_text(text);
935 target_cell.set_text(text);
931 // make this value the starting point, so that we can only undo
936 // make this value the starting point, so that we can only undo
932 // to this state, instead of a blank cell
937 // to this state, instead of a blank cell
933 target_cell.code_mirror.clearHistory();
938 target_cell.code_mirror.clearHistory();
934 source_element.remove();
939 source_element.remove();
935 this.select(i);
940 this.select(i);
936 };
941 };
937 this.edit_mode();
942 this.edit_mode();
938 this.set_dirty(true);
943 this.set_dirty(true);
939 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
944 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
940 {'cell_type':'heading',level:level}
945 {'cell_type':'heading',level:level}
941 );
946 );
942 };
947 };
943 };
948 };
944
949
945
950
946 // Cut/Copy/Paste
951 // Cut/Copy/Paste
947
952
948 /**
953 /**
949 * Enable UI elements for pasting cells.
954 * Enable UI elements for pasting cells.
950 *
955 *
951 * @method enable_paste
956 * @method enable_paste
952 */
957 */
953 Notebook.prototype.enable_paste = function () {
958 Notebook.prototype.enable_paste = function () {
954 var that = this;
959 var that = this;
955 if (!this.paste_enabled) {
960 if (!this.paste_enabled) {
956 $('#paste_cell_replace').removeClass('disabled')
961 $('#paste_cell_replace').removeClass('disabled')
957 .on('click', function () {that.paste_cell_replace();});
962 .on('click', function () {that.paste_cell_replace();});
958 $('#paste_cell_above').removeClass('disabled')
963 $('#paste_cell_above').removeClass('disabled')
959 .on('click', function () {that.paste_cell_above();});
964 .on('click', function () {that.paste_cell_above();});
960 $('#paste_cell_below').removeClass('disabled')
965 $('#paste_cell_below').removeClass('disabled')
961 .on('click', function () {that.paste_cell_below();});
966 .on('click', function () {that.paste_cell_below();});
962 this.paste_enabled = true;
967 this.paste_enabled = true;
963 };
968 };
964 };
969 };
965
970
966 /**
971 /**
967 * Disable UI elements for pasting cells.
972 * Disable UI elements for pasting cells.
968 *
973 *
969 * @method disable_paste
974 * @method disable_paste
970 */
975 */
971 Notebook.prototype.disable_paste = function () {
976 Notebook.prototype.disable_paste = function () {
972 if (this.paste_enabled) {
977 if (this.paste_enabled) {
973 $('#paste_cell_replace').addClass('disabled').off('click');
978 $('#paste_cell_replace').addClass('disabled').off('click');
974 $('#paste_cell_above').addClass('disabled').off('click');
979 $('#paste_cell_above').addClass('disabled').off('click');
975 $('#paste_cell_below').addClass('disabled').off('click');
980 $('#paste_cell_below').addClass('disabled').off('click');
976 this.paste_enabled = false;
981 this.paste_enabled = false;
977 };
982 };
978 };
983 };
979
984
980 /**
985 /**
981 * Cut a cell.
986 * Cut a cell.
982 *
987 *
983 * @method cut_cell
988 * @method cut_cell
984 */
989 */
985 Notebook.prototype.cut_cell = function () {
990 Notebook.prototype.cut_cell = function () {
986 this.copy_cell();
991 this.copy_cell();
987 this.delete_cell();
992 this.delete_cell();
988 }
993 }
989
994
990 /**
995 /**
991 * Copy a cell.
996 * Copy a cell.
992 *
997 *
993 * @method copy_cell
998 * @method copy_cell
994 */
999 */
995 Notebook.prototype.copy_cell = function () {
1000 Notebook.prototype.copy_cell = function () {
996 var cell = this.get_selected_cell();
1001 var cell = this.get_selected_cell();
997 this.clipboard = cell.toJSON();
1002 this.clipboard = cell.toJSON();
998 this.enable_paste();
1003 this.enable_paste();
999 };
1004 };
1000
1005
1001 /**
1006 /**
1002 * Replace the selected cell with a cell in the clipboard.
1007 * Replace the selected cell with a cell in the clipboard.
1003 *
1008 *
1004 * @method paste_cell_replace
1009 * @method paste_cell_replace
1005 */
1010 */
1006 Notebook.prototype.paste_cell_replace = function () {
1011 Notebook.prototype.paste_cell_replace = function () {
1007 if (this.clipboard !== null && this.paste_enabled) {
1012 if (this.clipboard !== null && this.paste_enabled) {
1008 var cell_data = this.clipboard;
1013 var cell_data = this.clipboard;
1009 var new_cell = this.insert_cell_above(cell_data.cell_type);
1014 var new_cell = this.insert_cell_above(cell_data.cell_type);
1010 new_cell.fromJSON(cell_data);
1015 new_cell.fromJSON(cell_data);
1011 var old_cell = this.get_next_cell(new_cell);
1016 var old_cell = this.get_next_cell(new_cell);
1012 this.delete_cell(this.find_cell_index(old_cell));
1017 this.delete_cell(this.find_cell_index(old_cell));
1013 this.select(this.find_cell_index(new_cell));
1018 this.select(this.find_cell_index(new_cell));
1014 };
1019 };
1015 };
1020 };
1016
1021
1017 /**
1022 /**
1018 * Paste a cell from the clipboard above the selected cell.
1023 * Paste a cell from the clipboard above the selected cell.
1019 *
1024 *
1020 * @method paste_cell_above
1025 * @method paste_cell_above
1021 */
1026 */
1022 Notebook.prototype.paste_cell_above = function () {
1027 Notebook.prototype.paste_cell_above = function () {
1023 if (this.clipboard !== null && this.paste_enabled) {
1028 if (this.clipboard !== null && this.paste_enabled) {
1024 var cell_data = this.clipboard;
1029 var cell_data = this.clipboard;
1025 var new_cell = this.insert_cell_above(cell_data.cell_type);
1030 var new_cell = this.insert_cell_above(cell_data.cell_type);
1026 new_cell.fromJSON(cell_data);
1031 new_cell.fromJSON(cell_data);
1027 };
1032 };
1028 };
1033 };
1029
1034
1030 /**
1035 /**
1031 * Paste a cell from the clipboard below the selected cell.
1036 * Paste a cell from the clipboard below the selected cell.
1032 *
1037 *
1033 * @method paste_cell_below
1038 * @method paste_cell_below
1034 */
1039 */
1035 Notebook.prototype.paste_cell_below = function () {
1040 Notebook.prototype.paste_cell_below = function () {
1036 if (this.clipboard !== null && this.paste_enabled) {
1041 if (this.clipboard !== null && this.paste_enabled) {
1037 var cell_data = this.clipboard;
1042 var cell_data = this.clipboard;
1038 var new_cell = this.insert_cell_below(cell_data.cell_type);
1043 var new_cell = this.insert_cell_below(cell_data.cell_type);
1039 new_cell.fromJSON(cell_data);
1044 new_cell.fromJSON(cell_data);
1040 };
1045 };
1041 };
1046 };
1042
1047
1043 // Split/merge
1048 // Split/merge
1044
1049
1045 /**
1050 /**
1046 * Split the selected cell into two, at the cursor.
1051 * Split the selected cell into two, at the cursor.
1047 *
1052 *
1048 * @method split_cell
1053 * @method split_cell
1049 */
1054 */
1050 Notebook.prototype.split_cell = function () {
1055 Notebook.prototype.split_cell = function () {
1051 var mdc = IPython.MarkdownCell;
1056 var mdc = IPython.MarkdownCell;
1052 var rc = IPython.RawCell;
1057 var rc = IPython.RawCell;
1053 var cell = this.get_selected_cell();
1058 var cell = this.get_selected_cell();
1054 if (cell.is_splittable()) {
1059 if (cell.is_splittable()) {
1055 var texta = cell.get_pre_cursor();
1060 var texta = cell.get_pre_cursor();
1056 var textb = cell.get_post_cursor();
1061 var textb = cell.get_post_cursor();
1057 if (cell instanceof IPython.CodeCell) {
1062 if (cell instanceof IPython.CodeCell) {
1058 // In this case the operations keep the notebook in its existing mode
1063 // In this case the operations keep the notebook in its existing mode
1059 // so we don't need to do any post-op mode changes.
1064 // so we don't need to do any post-op mode changes.
1060 cell.set_text(textb);
1065 cell.set_text(textb);
1061 var new_cell = this.insert_cell_above('code');
1066 var new_cell = this.insert_cell_above('code');
1062 new_cell.set_text(texta);
1067 new_cell.set_text(texta);
1063 } else if (((cell instanceof mdc) || (cell instanceof rc)) && !cell.rendered) {
1068 } else if (((cell instanceof mdc) || (cell instanceof rc)) && !cell.rendered) {
1064 // We know cell is !rendered so we can use set_text.
1069 // We know cell is !rendered so we can use set_text.
1065 cell.set_text(textb);
1070 cell.set_text(textb);
1066 var new_cell = this.insert_cell_above(cell.cell_type);
1071 var new_cell = this.insert_cell_above(cell.cell_type);
1067 // Unrender the new cell so we can call set_text.
1072 // Unrender the new cell so we can call set_text.
1068 new_cell.unrender();
1073 new_cell.unrender();
1069 new_cell.set_text(texta);
1074 new_cell.set_text(texta);
1070 }
1075 }
1071 };
1076 };
1072 };
1077 };
1073
1078
1074 /**
1079 /**
1075 * Combine the selected cell into the cell above it.
1080 * Combine the selected cell into the cell above it.
1076 *
1081 *
1077 * @method merge_cell_above
1082 * @method merge_cell_above
1078 */
1083 */
1079 Notebook.prototype.merge_cell_above = function () {
1084 Notebook.prototype.merge_cell_above = function () {
1080 var mdc = IPython.MarkdownCell;
1085 var mdc = IPython.MarkdownCell;
1081 var rc = IPython.RawCell;
1086 var rc = IPython.RawCell;
1082 var index = this.get_selected_index();
1087 var index = this.get_selected_index();
1083 var cell = this.get_cell(index);
1088 var cell = this.get_cell(index);
1084 var render = cell.rendered;
1089 var render = cell.rendered;
1085 if (!cell.is_mergeable()) {
1090 if (!cell.is_mergeable()) {
1086 return;
1091 return;
1087 }
1092 }
1088 if (index > 0) {
1093 if (index > 0) {
1089 var upper_cell = this.get_cell(index-1);
1094 var upper_cell = this.get_cell(index-1);
1090 if (!upper_cell.is_mergeable()) {
1095 if (!upper_cell.is_mergeable()) {
1091 return;
1096 return;
1092 }
1097 }
1093 var upper_text = upper_cell.get_text();
1098 var upper_text = upper_cell.get_text();
1094 var text = cell.get_text();
1099 var text = cell.get_text();
1095 if (cell instanceof IPython.CodeCell) {
1100 if (cell instanceof IPython.CodeCell) {
1096 cell.set_text(upper_text+'\n'+text);
1101 cell.set_text(upper_text+'\n'+text);
1097 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1102 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1098 cell.unrender(); // Must unrender before we set_text.
1103 cell.unrender(); // Must unrender before we set_text.
1099 cell.set_text(upper_text+'\n\n'+text);
1104 cell.set_text(upper_text+'\n\n'+text);
1100 if (render) {
1105 if (render) {
1101 // The rendered state of the final cell should match
1106 // The rendered state of the final cell should match
1102 // that of the original selected cell;
1107 // that of the original selected cell;
1103 cell.render();
1108 cell.render();
1104 }
1109 }
1105 };
1110 };
1106 this.delete_cell(index-1);
1111 this.delete_cell(index-1);
1107 this.select(this.find_cell_index(cell));
1112 this.select(this.find_cell_index(cell));
1108 };
1113 };
1109 };
1114 };
1110
1115
1111 /**
1116 /**
1112 * Combine the selected cell into the cell below it.
1117 * Combine the selected cell into the cell below it.
1113 *
1118 *
1114 * @method merge_cell_below
1119 * @method merge_cell_below
1115 */
1120 */
1116 Notebook.prototype.merge_cell_below = function () {
1121 Notebook.prototype.merge_cell_below = function () {
1117 var mdc = IPython.MarkdownCell;
1122 var mdc = IPython.MarkdownCell;
1118 var rc = IPython.RawCell;
1123 var rc = IPython.RawCell;
1119 var index = this.get_selected_index();
1124 var index = this.get_selected_index();
1120 var cell = this.get_cell(index);
1125 var cell = this.get_cell(index);
1121 var render = cell.rendered;
1126 var render = cell.rendered;
1122 if (!cell.is_mergeable()) {
1127 if (!cell.is_mergeable()) {
1123 return;
1128 return;
1124 }
1129 }
1125 if (index < this.ncells()-1) {
1130 if (index < this.ncells()-1) {
1126 var lower_cell = this.get_cell(index+1);
1131 var lower_cell = this.get_cell(index+1);
1127 if (!lower_cell.is_mergeable()) {
1132 if (!lower_cell.is_mergeable()) {
1128 return;
1133 return;
1129 }
1134 }
1130 var lower_text = lower_cell.get_text();
1135 var lower_text = lower_cell.get_text();
1131 var text = cell.get_text();
1136 var text = cell.get_text();
1132 if (cell instanceof IPython.CodeCell) {
1137 if (cell instanceof IPython.CodeCell) {
1133 cell.set_text(text+'\n'+lower_text);
1138 cell.set_text(text+'\n'+lower_text);
1134 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1139 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1135 cell.unrender(); // Must unrender before we set_text.
1140 cell.unrender(); // Must unrender before we set_text.
1136 cell.set_text(text+'\n\n'+lower_text);
1141 cell.set_text(text+'\n\n'+lower_text);
1137 if (render) {
1142 if (render) {
1138 // The rendered state of the final cell should match
1143 // The rendered state of the final cell should match
1139 // that of the original selected cell;
1144 // that of the original selected cell;
1140 cell.render();
1145 cell.render();
1141 }
1146 }
1142 };
1147 };
1143 this.delete_cell(index+1);
1148 this.delete_cell(index+1);
1144 this.select(this.find_cell_index(cell));
1149 this.select(this.find_cell_index(cell));
1145 };
1150 };
1146 };
1151 };
1147
1152
1148
1153
1149 // Cell collapsing and output clearing
1154 // Cell collapsing and output clearing
1150
1155
1151 /**
1156 /**
1152 * Hide a cell's output.
1157 * Hide a cell's output.
1153 *
1158 *
1154 * @method collapse
1159 * @method collapse
1155 * @param {Number} index A cell's numeric index
1160 * @param {Number} index A cell's numeric index
1156 */
1161 */
1157 Notebook.prototype.collapse = function (index) {
1162 Notebook.prototype.collapse = function (index) {
1158 var i = this.index_or_selected(index);
1163 var i = this.index_or_selected(index);
1159 this.get_cell(i).collapse();
1164 this.get_cell(i).collapse();
1160 this.set_dirty(true);
1165 this.set_dirty(true);
1161 };
1166 };
1162
1167
1163 /**
1168 /**
1164 * Show a cell's output.
1169 * Show a cell's output.
1165 *
1170 *
1166 * @method expand
1171 * @method expand
1167 * @param {Number} index A cell's numeric index
1172 * @param {Number} index A cell's numeric index
1168 */
1173 */
1169 Notebook.prototype.expand = function (index) {
1174 Notebook.prototype.expand = function (index) {
1170 var i = this.index_or_selected(index);
1175 var i = this.index_or_selected(index);
1171 this.get_cell(i).expand();
1176 this.get_cell(i).expand();
1172 this.set_dirty(true);
1177 this.set_dirty(true);
1173 };
1178 };
1174
1179
1175 /** Toggle whether a cell's output is collapsed or expanded.
1180 /** Toggle whether a cell's output is collapsed or expanded.
1176 *
1181 *
1177 * @method toggle_output
1182 * @method toggle_output
1178 * @param {Number} index A cell's numeric index
1183 * @param {Number} index A cell's numeric index
1179 */
1184 */
1180 Notebook.prototype.toggle_output = function (index) {
1185 Notebook.prototype.toggle_output = function (index) {
1181 var i = this.index_or_selected(index);
1186 var i = this.index_or_selected(index);
1182 this.get_cell(i).toggle_output();
1187 this.get_cell(i).toggle_output();
1183 this.set_dirty(true);
1188 this.set_dirty(true);
1184 };
1189 };
1185
1190
1186 /**
1191 /**
1187 * Toggle a scrollbar for long cell outputs.
1192 * Toggle a scrollbar for long cell outputs.
1188 *
1193 *
1189 * @method toggle_output_scroll
1194 * @method toggle_output_scroll
1190 * @param {Number} index A cell's numeric index
1195 * @param {Number} index A cell's numeric index
1191 */
1196 */
1192 Notebook.prototype.toggle_output_scroll = function (index) {
1197 Notebook.prototype.toggle_output_scroll = function (index) {
1193 var i = this.index_or_selected(index);
1198 var i = this.index_or_selected(index);
1194 this.get_cell(i).toggle_output_scroll();
1199 this.get_cell(i).toggle_output_scroll();
1195 };
1200 };
1196
1201
1197 /**
1202 /**
1198 * Hide each code cell's output area.
1203 * Hide each code cell's output area.
1199 *
1204 *
1200 * @method collapse_all_output
1205 * @method collapse_all_output
1201 */
1206 */
1202 Notebook.prototype.collapse_all_output = function () {
1207 Notebook.prototype.collapse_all_output = function () {
1203 var ncells = this.ncells();
1208 var ncells = this.ncells();
1204 var cells = this.get_cells();
1209 var cells = this.get_cells();
1205 for (var i=0; i<ncells; i++) {
1210 for (var i=0; i<ncells; i++) {
1206 if (cells[i] instanceof IPython.CodeCell) {
1211 if (cells[i] instanceof IPython.CodeCell) {
1207 cells[i].output_area.collapse();
1212 cells[i].output_area.collapse();
1208 }
1213 }
1209 };
1214 };
1210 // this should not be set if the `collapse` key is removed from nbformat
1215 // this should not be set if the `collapse` key is removed from nbformat
1211 this.set_dirty(true);
1216 this.set_dirty(true);
1212 };
1217 };
1213
1218
1214 /**
1219 /**
1215 * Expand each code cell's output area, and add a scrollbar for long output.
1220 * Expand each code cell's output area, and add a scrollbar for long output.
1216 *
1221 *
1217 * @method scroll_all_output
1222 * @method scroll_all_output
1218 */
1223 */
1219 Notebook.prototype.scroll_all_output = function () {
1224 Notebook.prototype.scroll_all_output = function () {
1220 var ncells = this.ncells();
1225 var ncells = this.ncells();
1221 var cells = this.get_cells();
1226 var cells = this.get_cells();
1222 for (var i=0; i<ncells; i++) {
1227 for (var i=0; i<ncells; i++) {
1223 if (cells[i] instanceof IPython.CodeCell) {
1228 if (cells[i] instanceof IPython.CodeCell) {
1224 cells[i].output_area.expand();
1229 cells[i].output_area.expand();
1225 cells[i].output_area.scroll_if_long();
1230 cells[i].output_area.scroll_if_long();
1226 }
1231 }
1227 };
1232 };
1228 // this should not be set if the `collapse` key is removed from nbformat
1233 // this should not be set if the `collapse` key is removed from nbformat
1229 this.set_dirty(true);
1234 this.set_dirty(true);
1230 };
1235 };
1231
1236
1232 /**
1237 /**
1233 * Expand each code cell's output area, and remove scrollbars.
1238 * Expand each code cell's output area, and remove scrollbars.
1234 *
1239 *
1235 * @method expand_all_output
1240 * @method expand_all_output
1236 */
1241 */
1237 Notebook.prototype.expand_all_output = function () {
1242 Notebook.prototype.expand_all_output = function () {
1238 var ncells = this.ncells();
1243 var ncells = this.ncells();
1239 var cells = this.get_cells();
1244 var cells = this.get_cells();
1240 for (var i=0; i<ncells; i++) {
1245 for (var i=0; i<ncells; i++) {
1241 if (cells[i] instanceof IPython.CodeCell) {
1246 if (cells[i] instanceof IPython.CodeCell) {
1242 cells[i].output_area.expand();
1247 cells[i].output_area.expand();
1243 cells[i].output_area.unscroll_area();
1248 cells[i].output_area.unscroll_area();
1244 }
1249 }
1245 };
1250 };
1246 // this should not be set if the `collapse` key is removed from nbformat
1251 // this should not be set if the `collapse` key is removed from nbformat
1247 this.set_dirty(true);
1252 this.set_dirty(true);
1248 };
1253 };
1249
1254
1250 /**
1255 /**
1251 * Clear each code cell's output area.
1256 * Clear each code cell's output area.
1252 *
1257 *
1253 * @method clear_all_output
1258 * @method clear_all_output
1254 */
1259 */
1255 Notebook.prototype.clear_all_output = function () {
1260 Notebook.prototype.clear_all_output = function () {
1256 var ncells = this.ncells();
1261 var ncells = this.ncells();
1257 var cells = this.get_cells();
1262 var cells = this.get_cells();
1258 for (var i=0; i<ncells; i++) {
1263 for (var i=0; i<ncells; i++) {
1259 if (cells[i] instanceof IPython.CodeCell) {
1264 if (cells[i] instanceof IPython.CodeCell) {
1260 cells[i].clear_output();
1265 cells[i].clear_output();
1261 // Make all In[] prompts blank, as well
1266 // Make all In[] prompts blank, as well
1262 // TODO: make this configurable (via checkbox?)
1267 // TODO: make this configurable (via checkbox?)
1263 cells[i].set_input_prompt();
1268 cells[i].set_input_prompt();
1264 }
1269 }
1265 };
1270 };
1266 this.set_dirty(true);
1271 this.set_dirty(true);
1267 };
1272 };
1268
1273
1269
1274
1270 // Other cell functions: line numbers, ...
1275 // Other cell functions: line numbers, ...
1271
1276
1272 /**
1277 /**
1273 * Toggle line numbers in the selected cell's input area.
1278 * Toggle line numbers in the selected cell's input area.
1274 *
1279 *
1275 * @method cell_toggle_line_numbers
1280 * @method cell_toggle_line_numbers
1276 */
1281 */
1277 Notebook.prototype.cell_toggle_line_numbers = function() {
1282 Notebook.prototype.cell_toggle_line_numbers = function() {
1278 this.get_selected_cell().toggle_line_numbers();
1283 this.get_selected_cell().toggle_line_numbers();
1279 };
1284 };
1280
1285
1281 // Session related things
1286 // Session related things
1282
1287
1283 /**
1288 /**
1284 * Start a new session and set it on each code cell.
1289 * Start a new session and set it on each code cell.
1285 *
1290 *
1286 * @method start_session
1291 * @method start_session
1287 */
1292 */
1288 Notebook.prototype.start_session = function () {
1293 Notebook.prototype.start_session = function () {
1289 this.session = new IPython.Session(this.notebook_name, this.notebook_path, this);
1294 this.session = new IPython.Session(this.notebook_name, this.notebook_path, this);
1290 this.session.start($.proxy(this._session_started, this));
1295 this.session.start($.proxy(this._session_started, this));
1291 };
1296 };
1292
1297
1293
1298
1294 /**
1299 /**
1295 * Once a session is started, link the code cells to the kernel
1300 * Once a session is started, link the code cells to the kernel
1296 *
1301 *
1297 */
1302 */
1298 Notebook.prototype._session_started = function(){
1303 Notebook.prototype._session_started = function(){
1299 this.kernel = this.session.kernel;
1304 this.kernel = this.session.kernel;
1300 var ncells = this.ncells();
1305 var ncells = this.ncells();
1301 for (var i=0; i<ncells; i++) {
1306 for (var i=0; i<ncells; i++) {
1302 var cell = this.get_cell(i);
1307 var cell = this.get_cell(i);
1303 if (cell instanceof IPython.CodeCell) {
1308 if (cell instanceof IPython.CodeCell) {
1304 cell.set_kernel(this.session.kernel);
1309 cell.set_kernel(this.session.kernel);
1305 };
1310 };
1306 };
1311 };
1307 };
1312 };
1308
1313
1309 /**
1314 /**
1310 * Prompt the user to restart the IPython kernel.
1315 * Prompt the user to restart the IPython kernel.
1311 *
1316 *
1312 * @method restart_kernel
1317 * @method restart_kernel
1313 */
1318 */
1314 Notebook.prototype.restart_kernel = function () {
1319 Notebook.prototype.restart_kernel = function () {
1315 var that = this;
1320 var that = this;
1316 IPython.dialog.modal({
1321 IPython.dialog.modal({
1317 title : "Restart kernel or continue running?",
1322 title : "Restart kernel or continue running?",
1318 body : $("<p/>").html(
1323 body : $("<p/>").html(
1319 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1324 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1320 ),
1325 ),
1321 buttons : {
1326 buttons : {
1322 "Continue running" : {},
1327 "Continue running" : {},
1323 "Restart" : {
1328 "Restart" : {
1324 "class" : "btn-danger",
1329 "class" : "btn-danger",
1325 "click" : function() {
1330 "click" : function() {
1326 that.session.restart_kernel();
1331 that.session.restart_kernel();
1327 }
1332 }
1328 }
1333 }
1329 }
1334 }
1330 });
1335 });
1331 };
1336 };
1332
1337
1333 /**
1338 /**
1334 * Run the selected cell.
1339 * Run the selected cell.
1335 *
1340 *
1336 * Execute or render cell outputs.
1341 * Execute or render cell outputs.
1337 *
1342 *
1338 * @method execute_selected_cell
1343 * @method execute_selected_cell
1339 * @param {Object} options Customize post-execution behavior
1344 * @param {Object} options Customize post-execution behavior
1340 */
1345 */
1341 Notebook.prototype.execute_selected_cell = function (mode) {
1346 Notebook.prototype.execute_selected_cell = function (mode) {
1342 // mode = shift, ctrl, alt
1347 // mode = shift, ctrl, alt
1343 mode = mode || 'shift'
1348 mode = mode || 'shift'
1344 var cell = this.get_selected_cell();
1349 var cell = this.get_selected_cell();
1345 var cell_index = this.find_cell_index(cell);
1350 var cell_index = this.find_cell_index(cell);
1346
1351
1347 cell.execute();
1352 cell.execute();
1348
1353
1349 // If we are at the end always insert a new cell and return
1354 // If we are at the end always insert a new cell and return
1350 if (cell_index === (this.ncells()-1) && mode !== 'shift') {
1355 if (cell_index === (this.ncells()-1) && mode !== 'shift') {
1351 this.insert_cell_below('code');
1356 this.insert_cell_below('code');
1352 this.select(cell_index+1);
1357 this.select(cell_index+1);
1353 this.edit_mode();
1358 this.edit_mode();
1354 this.scroll_to_bottom();
1359 this.scroll_to_bottom();
1355 this.set_dirty(true);
1360 this.set_dirty(true);
1356 return;
1361 return;
1357 }
1362 }
1358
1363
1359 if (mode === 'shift') {
1364 if (mode === 'shift') {
1360 this.command_mode();
1365 this.command_mode();
1361 } else if (mode === 'ctrl') {
1366 } else if (mode === 'ctrl') {
1362 this.select(cell_index+1);
1367 this.select(cell_index+1);
1363 this.get_cell(cell_index+1).focus_cell();
1368 this.get_cell(cell_index+1).focus_cell();
1364 } else if (mode === 'alt') {
1369 } else if (mode === 'alt') {
1365 // Only insert a new cell, if we ended up in an already populated cell
1370 // Only insert a new cell, if we ended up in an already populated cell
1366 var next_text = this.get_cell(cell_index+1).get_text();
1371 var next_text = this.get_cell(cell_index+1).get_text();
1367 if (/\S/.test(next_text) === true) {
1372 if (/\S/.test(next_text) === true) {
1368 this.insert_cell_below('code');
1373 this.insert_cell_below('code');
1369 }
1374 }
1370 this.select(cell_index+1);
1375 this.select(cell_index+1);
1371 this.edit_mode();
1376 this.edit_mode();
1372 }
1377 }
1373 this.set_dirty(true);
1378 this.set_dirty(true);
1374 };
1379 };
1375
1380
1376
1381
1377 /**
1382 /**
1378 * Execute all cells below the selected cell.
1383 * Execute all cells below the selected cell.
1379 *
1384 *
1380 * @method execute_cells_below
1385 * @method execute_cells_below
1381 */
1386 */
1382 Notebook.prototype.execute_cells_below = function () {
1387 Notebook.prototype.execute_cells_below = function () {
1383 this.execute_cell_range(this.get_selected_index(), this.ncells());
1388 this.execute_cell_range(this.get_selected_index(), this.ncells());
1384 this.scroll_to_bottom();
1389 this.scroll_to_bottom();
1385 };
1390 };
1386
1391
1387 /**
1392 /**
1388 * Execute all cells above the selected cell.
1393 * Execute all cells above the selected cell.
1389 *
1394 *
1390 * @method execute_cells_above
1395 * @method execute_cells_above
1391 */
1396 */
1392 Notebook.prototype.execute_cells_above = function () {
1397 Notebook.prototype.execute_cells_above = function () {
1393 this.execute_cell_range(0, this.get_selected_index());
1398 this.execute_cell_range(0, this.get_selected_index());
1394 };
1399 };
1395
1400
1396 /**
1401 /**
1397 * Execute all cells.
1402 * Execute all cells.
1398 *
1403 *
1399 * @method execute_all_cells
1404 * @method execute_all_cells
1400 */
1405 */
1401 Notebook.prototype.execute_all_cells = function () {
1406 Notebook.prototype.execute_all_cells = function () {
1402 this.execute_cell_range(0, this.ncells());
1407 this.execute_cell_range(0, this.ncells());
1403 this.scroll_to_bottom();
1408 this.scroll_to_bottom();
1404 };
1409 };
1405
1410
1406 /**
1411 /**
1407 * Execute a contiguous range of cells.
1412 * Execute a contiguous range of cells.
1408 *
1413 *
1409 * @method execute_cell_range
1414 * @method execute_cell_range
1410 * @param {Number} start Index of the first cell to execute (inclusive)
1415 * @param {Number} start Index of the first cell to execute (inclusive)
1411 * @param {Number} end Index of the last cell to execute (exclusive)
1416 * @param {Number} end Index of the last cell to execute (exclusive)
1412 */
1417 */
1413 Notebook.prototype.execute_cell_range = function (start, end) {
1418 Notebook.prototype.execute_cell_range = function (start, end) {
1414 for (var i=start; i<end; i++) {
1419 for (var i=start; i<end; i++) {
1415 this.select(i);
1420 this.select(i);
1416 this.execute_selected_cell({add_new:false});
1421 this.execute_selected_cell({add_new:false});
1417 };
1422 };
1418 };
1423 };
1419
1424
1420 // Persistance and loading
1425 // Persistance and loading
1421
1426
1422 /**
1427 /**
1423 * Getter method for this notebook's name.
1428 * Getter method for this notebook's name.
1424 *
1429 *
1425 * @method get_notebook_name
1430 * @method get_notebook_name
1426 * @return {String} This notebook's name
1431 * @return {String} This notebook's name
1427 */
1432 */
1428 Notebook.prototype.get_notebook_name = function () {
1433 Notebook.prototype.get_notebook_name = function () {
1429 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1434 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1430 return nbname;
1435 return nbname;
1431 };
1436 };
1432
1437
1433 /**
1438 /**
1434 * Setter method for this notebook's name.
1439 * Setter method for this notebook's name.
1435 *
1440 *
1436 * @method set_notebook_name
1441 * @method set_notebook_name
1437 * @param {String} name A new name for this notebook
1442 * @param {String} name A new name for this notebook
1438 */
1443 */
1439 Notebook.prototype.set_notebook_name = function (name) {
1444 Notebook.prototype.set_notebook_name = function (name) {
1440 this.notebook_name = name;
1445 this.notebook_name = name;
1441 };
1446 };
1442
1447
1443 /**
1448 /**
1444 * Check that a notebook's name is valid.
1449 * Check that a notebook's name is valid.
1445 *
1450 *
1446 * @method test_notebook_name
1451 * @method test_notebook_name
1447 * @param {String} nbname A name for this notebook
1452 * @param {String} nbname A name for this notebook
1448 * @return {Boolean} True if the name is valid, false if invalid
1453 * @return {Boolean} True if the name is valid, false if invalid
1449 */
1454 */
1450 Notebook.prototype.test_notebook_name = function (nbname) {
1455 Notebook.prototype.test_notebook_name = function (nbname) {
1451 nbname = nbname || '';
1456 nbname = nbname || '';
1452 if (this.notebook_name_blacklist_re.test(nbname) == false && nbname.length>0) {
1457 if (this.notebook_name_blacklist_re.test(nbname) == false && nbname.length>0) {
1453 return true;
1458 return true;
1454 } else {
1459 } else {
1455 return false;
1460 return false;
1456 };
1461 };
1457 };
1462 };
1458
1463
1459 /**
1464 /**
1460 * Load a notebook from JSON (.ipynb).
1465 * Load a notebook from JSON (.ipynb).
1461 *
1466 *
1462 * This currently handles one worksheet: others are deleted.
1467 * This currently handles one worksheet: others are deleted.
1463 *
1468 *
1464 * @method fromJSON
1469 * @method fromJSON
1465 * @param {Object} data JSON representation of a notebook
1470 * @param {Object} data JSON representation of a notebook
1466 */
1471 */
1467 Notebook.prototype.fromJSON = function (data) {
1472 Notebook.prototype.fromJSON = function (data) {
1468 var content = data.content;
1473 var content = data.content;
1469 var ncells = this.ncells();
1474 var ncells = this.ncells();
1470 var i;
1475 var i;
1471 for (i=0; i<ncells; i++) {
1476 for (i=0; i<ncells; i++) {
1472 // Always delete cell 0 as they get renumbered as they are deleted.
1477 // Always delete cell 0 as they get renumbered as they are deleted.
1473 this.delete_cell(0);
1478 this.delete_cell(0);
1474 };
1479 };
1475 // Save the metadata and name.
1480 // Save the metadata and name.
1476 this.metadata = content.metadata;
1481 this.metadata = content.metadata;
1477 this.notebook_name = data.name;
1482 this.notebook_name = data.name;
1478 // Only handle 1 worksheet for now.
1483 // Only handle 1 worksheet for now.
1479 var worksheet = content.worksheets[0];
1484 var worksheet = content.worksheets[0];
1480 if (worksheet !== undefined) {
1485 if (worksheet !== undefined) {
1481 if (worksheet.metadata) {
1486 if (worksheet.metadata) {
1482 this.worksheet_metadata = worksheet.metadata;
1487 this.worksheet_metadata = worksheet.metadata;
1483 }
1488 }
1484 var new_cells = worksheet.cells;
1489 var new_cells = worksheet.cells;
1485 ncells = new_cells.length;
1490 ncells = new_cells.length;
1486 var cell_data = null;
1491 var cell_data = null;
1487 var new_cell = null;
1492 var new_cell = null;
1488 for (i=0; i<ncells; i++) {
1493 for (i=0; i<ncells; i++) {
1489 cell_data = new_cells[i];
1494 cell_data = new_cells[i];
1490 // VERSIONHACK: plaintext -> raw
1495 // VERSIONHACK: plaintext -> raw
1491 // handle never-released plaintext name for raw cells
1496 // handle never-released plaintext name for raw cells
1492 if (cell_data.cell_type === 'plaintext'){
1497 if (cell_data.cell_type === 'plaintext'){
1493 cell_data.cell_type = 'raw';
1498 cell_data.cell_type = 'raw';
1494 }
1499 }
1495
1500
1496 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1501 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1497 new_cell.fromJSON(cell_data);
1502 new_cell.fromJSON(cell_data);
1498 };
1503 };
1499 };
1504 };
1500 if (content.worksheets.length > 1) {
1505 if (content.worksheets.length > 1) {
1501 IPython.dialog.modal({
1506 IPython.dialog.modal({
1502 title : "Multiple worksheets",
1507 title : "Multiple worksheets",
1503 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1508 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1504 "but this version of IPython can only handle the first. " +
1509 "but this version of IPython can only handle the first. " +
1505 "If you save this notebook, worksheets after the first will be lost.",
1510 "If you save this notebook, worksheets after the first will be lost.",
1506 buttons : {
1511 buttons : {
1507 OK : {
1512 OK : {
1508 class : "btn-danger"
1513 class : "btn-danger"
1509 }
1514 }
1510 }
1515 }
1511 });
1516 });
1512 }
1517 }
1513 };
1518 };
1514
1519
1515 /**
1520 /**
1516 * Dump this notebook into a JSON-friendly object.
1521 * Dump this notebook into a JSON-friendly object.
1517 *
1522 *
1518 * @method toJSON
1523 * @method toJSON
1519 * @return {Object} A JSON-friendly representation of this notebook.
1524 * @return {Object} A JSON-friendly representation of this notebook.
1520 */
1525 */
1521 Notebook.prototype.toJSON = function () {
1526 Notebook.prototype.toJSON = function () {
1522 var cells = this.get_cells();
1527 var cells = this.get_cells();
1523 var ncells = cells.length;
1528 var ncells = cells.length;
1524 var cell_array = new Array(ncells);
1529 var cell_array = new Array(ncells);
1525 for (var i=0; i<ncells; i++) {
1530 for (var i=0; i<ncells; i++) {
1526 cell_array[i] = cells[i].toJSON();
1531 cell_array[i] = cells[i].toJSON();
1527 };
1532 };
1528 var data = {
1533 var data = {
1529 // Only handle 1 worksheet for now.
1534 // Only handle 1 worksheet for now.
1530 worksheets : [{
1535 worksheets : [{
1531 cells: cell_array,
1536 cells: cell_array,
1532 metadata: this.worksheet_metadata
1537 metadata: this.worksheet_metadata
1533 }],
1538 }],
1534 metadata : this.metadata
1539 metadata : this.metadata
1535 };
1540 };
1536 return data;
1541 return data;
1537 };
1542 };
1538
1543
1539 /**
1544 /**
1540 * Start an autosave timer, for periodically saving the notebook.
1545 * Start an autosave timer, for periodically saving the notebook.
1541 *
1546 *
1542 * @method set_autosave_interval
1547 * @method set_autosave_interval
1543 * @param {Integer} interval the autosave interval in milliseconds
1548 * @param {Integer} interval the autosave interval in milliseconds
1544 */
1549 */
1545 Notebook.prototype.set_autosave_interval = function (interval) {
1550 Notebook.prototype.set_autosave_interval = function (interval) {
1546 var that = this;
1551 var that = this;
1547 // clear previous interval, so we don't get simultaneous timers
1552 // clear previous interval, so we don't get simultaneous timers
1548 if (this.autosave_timer) {
1553 if (this.autosave_timer) {
1549 clearInterval(this.autosave_timer);
1554 clearInterval(this.autosave_timer);
1550 }
1555 }
1551
1556
1552 this.autosave_interval = this.minimum_autosave_interval = interval;
1557 this.autosave_interval = this.minimum_autosave_interval = interval;
1553 if (interval) {
1558 if (interval) {
1554 this.autosave_timer = setInterval(function() {
1559 this.autosave_timer = setInterval(function() {
1555 if (that.dirty) {
1560 if (that.dirty) {
1556 that.save_notebook();
1561 that.save_notebook();
1557 }
1562 }
1558 }, interval);
1563 }, interval);
1559 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1564 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1560 } else {
1565 } else {
1561 this.autosave_timer = null;
1566 this.autosave_timer = null;
1562 $([IPython.events]).trigger("autosave_disabled.Notebook");
1567 $([IPython.events]).trigger("autosave_disabled.Notebook");
1563 };
1568 };
1564 };
1569 };
1565
1570
1566 /**
1571 /**
1567 * Save this notebook on the server.
1572 * Save this notebook on the server.
1568 *
1573 *
1569 * @method save_notebook
1574 * @method save_notebook
1570 */
1575 */
1571 Notebook.prototype.save_notebook = function (extra_settings) {
1576 Notebook.prototype.save_notebook = function (extra_settings) {
1572 // Create a JSON model to be sent to the server.
1577 // Create a JSON model to be sent to the server.
1573 var model = {};
1578 var model = {};
1574 model.name = this.notebook_name;
1579 model.name = this.notebook_name;
1575 model.path = this.notebook_path;
1580 model.path = this.notebook_path;
1576 model.content = this.toJSON();
1581 model.content = this.toJSON();
1577 model.content.nbformat = this.nbformat;
1582 model.content.nbformat = this.nbformat;
1578 model.content.nbformat_minor = this.nbformat_minor;
1583 model.content.nbformat_minor = this.nbformat_minor;
1579 // time the ajax call for autosave tuning purposes.
1584 // time the ajax call for autosave tuning purposes.
1580 var start = new Date().getTime();
1585 var start = new Date().getTime();
1581 // We do the call with settings so we can set cache to false.
1586 // We do the call with settings so we can set cache to false.
1582 var settings = {
1587 var settings = {
1583 processData : false,
1588 processData : false,
1584 cache : false,
1589 cache : false,
1585 type : "PUT",
1590 type : "PUT",
1586 data : JSON.stringify(model),
1591 data : JSON.stringify(model),
1587 headers : {'Content-Type': 'application/json'},
1592 headers : {'Content-Type': 'application/json'},
1588 success : $.proxy(this.save_notebook_success, this, start),
1593 success : $.proxy(this.save_notebook_success, this, start),
1589 error : $.proxy(this.save_notebook_error, this)
1594 error : $.proxy(this.save_notebook_error, this)
1590 };
1595 };
1591 if (extra_settings) {
1596 if (extra_settings) {
1592 for (var key in extra_settings) {
1597 for (var key in extra_settings) {
1593 settings[key] = extra_settings[key];
1598 settings[key] = extra_settings[key];
1594 }
1599 }
1595 }
1600 }
1596 $([IPython.events]).trigger('notebook_saving.Notebook');
1601 $([IPython.events]).trigger('notebook_saving.Notebook');
1597 var url = utils.url_join_encode(
1602 var url = utils.url_join_encode(
1598 this._baseProjectUrl,
1603 this._baseProjectUrl,
1599 'api/notebooks',
1604 'api/notebooks',
1600 this.notebook_path,
1605 this.notebook_path,
1601 this.notebook_name
1606 this.notebook_name
1602 );
1607 );
1603 $.ajax(url, settings);
1608 $.ajax(url, settings);
1604 };
1609 };
1605
1610
1606 /**
1611 /**
1607 * Success callback for saving a notebook.
1612 * Success callback for saving a notebook.
1608 *
1613 *
1609 * @method save_notebook_success
1614 * @method save_notebook_success
1610 * @param {Integer} start the time when the save request started
1615 * @param {Integer} start the time when the save request started
1611 * @param {Object} data JSON representation of a notebook
1616 * @param {Object} data JSON representation of a notebook
1612 * @param {String} status Description of response status
1617 * @param {String} status Description of response status
1613 * @param {jqXHR} xhr jQuery Ajax object
1618 * @param {jqXHR} xhr jQuery Ajax object
1614 */
1619 */
1615 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1620 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1616 this.set_dirty(false);
1621 this.set_dirty(false);
1617 $([IPython.events]).trigger('notebook_saved.Notebook');
1622 $([IPython.events]).trigger('notebook_saved.Notebook');
1618 this._update_autosave_interval(start);
1623 this._update_autosave_interval(start);
1619 if (this._checkpoint_after_save) {
1624 if (this._checkpoint_after_save) {
1620 this.create_checkpoint();
1625 this.create_checkpoint();
1621 this._checkpoint_after_save = false;
1626 this._checkpoint_after_save = false;
1622 };
1627 };
1623 };
1628 };
1624
1629
1625 /**
1630 /**
1626 * update the autosave interval based on how long the last save took
1631 * update the autosave interval based on how long the last save took
1627 *
1632 *
1628 * @method _update_autosave_interval
1633 * @method _update_autosave_interval
1629 * @param {Integer} timestamp when the save request started
1634 * @param {Integer} timestamp when the save request started
1630 */
1635 */
1631 Notebook.prototype._update_autosave_interval = function (start) {
1636 Notebook.prototype._update_autosave_interval = function (start) {
1632 var duration = (new Date().getTime() - start);
1637 var duration = (new Date().getTime() - start);
1633 if (this.autosave_interval) {
1638 if (this.autosave_interval) {
1634 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1639 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1635 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1640 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1636 // round to 10 seconds, otherwise we will be setting a new interval too often
1641 // round to 10 seconds, otherwise we will be setting a new interval too often
1637 interval = 10000 * Math.round(interval / 10000);
1642 interval = 10000 * Math.round(interval / 10000);
1638 // set new interval, if it's changed
1643 // set new interval, if it's changed
1639 if (interval != this.autosave_interval) {
1644 if (interval != this.autosave_interval) {
1640 this.set_autosave_interval(interval);
1645 this.set_autosave_interval(interval);
1641 }
1646 }
1642 }
1647 }
1643 };
1648 };
1644
1649
1645 /**
1650 /**
1646 * Failure callback for saving a notebook.
1651 * Failure callback for saving a notebook.
1647 *
1652 *
1648 * @method save_notebook_error
1653 * @method save_notebook_error
1649 * @param {jqXHR} xhr jQuery Ajax object
1654 * @param {jqXHR} xhr jQuery Ajax object
1650 * @param {String} status Description of response status
1655 * @param {String} status Description of response status
1651 * @param {String} error HTTP error message
1656 * @param {String} error HTTP error message
1652 */
1657 */
1653 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1658 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1654 $([IPython.events]).trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1659 $([IPython.events]).trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1655 };
1660 };
1656
1661
1657 Notebook.prototype.new_notebook = function(){
1662 Notebook.prototype.new_notebook = function(){
1658 var path = this.notebook_path;
1663 var path = this.notebook_path;
1659 var base_project_url = this._baseProjectUrl;
1664 var base_project_url = this._baseProjectUrl;
1660 var settings = {
1665 var settings = {
1661 processData : false,
1666 processData : false,
1662 cache : false,
1667 cache : false,
1663 type : "POST",
1668 type : "POST",
1664 dataType : "json",
1669 dataType : "json",
1665 async : false,
1670 async : false,
1666 success : function (data, status, xhr){
1671 success : function (data, status, xhr){
1667 var notebook_name = data.name;
1672 var notebook_name = data.name;
1668 window.open(
1673 window.open(
1669 utils.url_join_encode(
1674 utils.url_join_encode(
1670 base_project_url,
1675 base_project_url,
1671 'notebooks',
1676 'notebooks',
1672 path,
1677 path,
1673 notebook_name
1678 notebook_name
1674 ),
1679 ),
1675 '_blank'
1680 '_blank'
1676 );
1681 );
1677 }
1682 }
1678 };
1683 };
1679 var url = utils.url_join_encode(
1684 var url = utils.url_join_encode(
1680 base_project_url,
1685 base_project_url,
1681 'api/notebooks',
1686 'api/notebooks',
1682 path
1687 path
1683 );
1688 );
1684 $.ajax(url,settings);
1689 $.ajax(url,settings);
1685 };
1690 };
1686
1691
1687
1692
1688 Notebook.prototype.copy_notebook = function(){
1693 Notebook.prototype.copy_notebook = function(){
1689 var path = this.notebook_path;
1694 var path = this.notebook_path;
1690 var base_project_url = this._baseProjectUrl;
1695 var base_project_url = this._baseProjectUrl;
1691 var settings = {
1696 var settings = {
1692 processData : false,
1697 processData : false,
1693 cache : false,
1698 cache : false,
1694 type : "POST",
1699 type : "POST",
1695 dataType : "json",
1700 dataType : "json",
1696 data : JSON.stringify({copy_from : this.notebook_name}),
1701 data : JSON.stringify({copy_from : this.notebook_name}),
1697 async : false,
1702 async : false,
1698 success : function (data, status, xhr) {
1703 success : function (data, status, xhr) {
1699 window.open(utils.url_join_encode(
1704 window.open(utils.url_join_encode(
1700 base_project_url,
1705 base_project_url,
1701 'notebooks',
1706 'notebooks',
1702 data.path,
1707 data.path,
1703 data.name
1708 data.name
1704 ), '_blank');
1709 ), '_blank');
1705 }
1710 }
1706 };
1711 };
1707 var url = utils.url_join_encode(
1712 var url = utils.url_join_encode(
1708 base_project_url,
1713 base_project_url,
1709 'api/notebooks',
1714 'api/notebooks',
1710 path
1715 path
1711 );
1716 );
1712 $.ajax(url,settings);
1717 $.ajax(url,settings);
1713 };
1718 };
1714
1719
1715 Notebook.prototype.rename = function (nbname) {
1720 Notebook.prototype.rename = function (nbname) {
1716 var that = this;
1721 var that = this;
1717 var data = {name: nbname + '.ipynb'};
1722 var data = {name: nbname + '.ipynb'};
1718 var settings = {
1723 var settings = {
1719 processData : false,
1724 processData : false,
1720 cache : false,
1725 cache : false,
1721 type : "PATCH",
1726 type : "PATCH",
1722 data : JSON.stringify(data),
1727 data : JSON.stringify(data),
1723 dataType: "json",
1728 dataType: "json",
1724 headers : {'Content-Type': 'application/json'},
1729 headers : {'Content-Type': 'application/json'},
1725 success : $.proxy(that.rename_success, this),
1730 success : $.proxy(that.rename_success, this),
1726 error : $.proxy(that.rename_error, this)
1731 error : $.proxy(that.rename_error, this)
1727 };
1732 };
1728 $([IPython.events]).trigger('rename_notebook.Notebook', data);
1733 $([IPython.events]).trigger('rename_notebook.Notebook', data);
1729 var url = utils.url_join_encode(
1734 var url = utils.url_join_encode(
1730 this._baseProjectUrl,
1735 this._baseProjectUrl,
1731 'api/notebooks',
1736 'api/notebooks',
1732 this.notebook_path,
1737 this.notebook_path,
1733 this.notebook_name
1738 this.notebook_name
1734 );
1739 );
1735 $.ajax(url, settings);
1740 $.ajax(url, settings);
1736 };
1741 };
1737
1742
1738
1743
1739 Notebook.prototype.rename_success = function (json, status, xhr) {
1744 Notebook.prototype.rename_success = function (json, status, xhr) {
1740 this.notebook_name = json.name;
1745 this.notebook_name = json.name;
1741 var name = this.notebook_name;
1746 var name = this.notebook_name;
1742 var path = json.path;
1747 var path = json.path;
1743 this.session.rename_notebook(name, path);
1748 this.session.rename_notebook(name, path);
1744 $([IPython.events]).trigger('notebook_renamed.Notebook', json);
1749 $([IPython.events]).trigger('notebook_renamed.Notebook', json);
1745 }
1750 }
1746
1751
1747 Notebook.prototype.rename_error = function (xhr, status, error) {
1752 Notebook.prototype.rename_error = function (xhr, status, error) {
1748 var that = this;
1753 var that = this;
1749 var dialog = $('<div/>').append(
1754 var dialog = $('<div/>').append(
1750 $("<p/>").addClass("rename-message")
1755 $("<p/>").addClass("rename-message")
1751 .html('This notebook name already exists.')
1756 .html('This notebook name already exists.')
1752 )
1757 )
1753 $([IPython.events]).trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
1758 $([IPython.events]).trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
1754 IPython.dialog.modal({
1759 IPython.dialog.modal({
1755 title: "Notebook Rename Error!",
1760 title: "Notebook Rename Error!",
1756 body: dialog,
1761 body: dialog,
1757 buttons : {
1762 buttons : {
1758 "Cancel": {},
1763 "Cancel": {},
1759 "OK": {
1764 "OK": {
1760 class: "btn-primary",
1765 class: "btn-primary",
1761 click: function () {
1766 click: function () {
1762 IPython.save_widget.rename_notebook();
1767 IPython.save_widget.rename_notebook();
1763 }}
1768 }}
1764 },
1769 },
1765 open : function (event, ui) {
1770 open : function (event, ui) {
1766 var that = $(this);
1771 var that = $(this);
1767 // Upon ENTER, click the OK button.
1772 // Upon ENTER, click the OK button.
1768 that.find('input[type="text"]').keydown(function (event, ui) {
1773 that.find('input[type="text"]').keydown(function (event, ui) {
1769 if (event.which === utils.keycodes.ENTER) {
1774 if (event.which === utils.keycodes.ENTER) {
1770 that.find('.btn-primary').first().click();
1775 that.find('.btn-primary').first().click();
1771 }
1776 }
1772 });
1777 });
1773 that.find('input[type="text"]').focus();
1778 that.find('input[type="text"]').focus();
1774 }
1779 }
1775 });
1780 });
1776 }
1781 }
1777
1782
1778 /**
1783 /**
1779 * Request a notebook's data from the server.
1784 * Request a notebook's data from the server.
1780 *
1785 *
1781 * @method load_notebook
1786 * @method load_notebook
1782 * @param {String} notebook_name and path A notebook to load
1787 * @param {String} notebook_name and path A notebook to load
1783 */
1788 */
1784 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
1789 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
1785 var that = this;
1790 var that = this;
1786 this.notebook_name = notebook_name;
1791 this.notebook_name = notebook_name;
1787 this.notebook_path = notebook_path;
1792 this.notebook_path = notebook_path;
1788 // We do the call with settings so we can set cache to false.
1793 // We do the call with settings so we can set cache to false.
1789 var settings = {
1794 var settings = {
1790 processData : false,
1795 processData : false,
1791 cache : false,
1796 cache : false,
1792 type : "GET",
1797 type : "GET",
1793 dataType : "json",
1798 dataType : "json",
1794 success : $.proxy(this.load_notebook_success,this),
1799 success : $.proxy(this.load_notebook_success,this),
1795 error : $.proxy(this.load_notebook_error,this),
1800 error : $.proxy(this.load_notebook_error,this),
1796 };
1801 };
1797 $([IPython.events]).trigger('notebook_loading.Notebook');
1802 $([IPython.events]).trigger('notebook_loading.Notebook');
1798 var url = utils.url_join_encode(
1803 var url = utils.url_join_encode(
1799 this._baseProjectUrl,
1804 this._baseProjectUrl,
1800 'api/notebooks',
1805 'api/notebooks',
1801 this.notebook_path,
1806 this.notebook_path,
1802 this.notebook_name
1807 this.notebook_name
1803 );
1808 );
1804 $.ajax(url, settings);
1809 $.ajax(url, settings);
1805 };
1810 };
1806
1811
1807 /**
1812 /**
1808 * Success callback for loading a notebook from the server.
1813 * Success callback for loading a notebook from the server.
1809 *
1814 *
1810 * Load notebook data from the JSON response.
1815 * Load notebook data from the JSON response.
1811 *
1816 *
1812 * @method load_notebook_success
1817 * @method load_notebook_success
1813 * @param {Object} data JSON representation of a notebook
1818 * @param {Object} data JSON representation of a notebook
1814 * @param {String} status Description of response status
1819 * @param {String} status Description of response status
1815 * @param {jqXHR} xhr jQuery Ajax object
1820 * @param {jqXHR} xhr jQuery Ajax object
1816 */
1821 */
1817 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1822 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1818 this.fromJSON(data);
1823 this.fromJSON(data);
1819 if (this.ncells() === 0) {
1824 if (this.ncells() === 0) {
1820 this.insert_cell_below('code');
1825 this.insert_cell_below('code');
1821 this.select(0);
1826 this.select(0);
1822 this.edit_mode();
1827 this.edit_mode();
1823 } else {
1828 } else {
1824 this.select(0);
1829 this.select(0);
1825 this.command_mode();
1830 this.command_mode();
1826 };
1831 };
1827 this.set_dirty(false);
1832 this.set_dirty(false);
1828 this.scroll_to_top();
1833 this.scroll_to_top();
1829 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1834 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1830 var msg = "This notebook has been converted from an older " +
1835 var msg = "This notebook has been converted from an older " +
1831 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1836 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1832 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1837 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1833 "newer notebook format will be used and older versions of IPython " +
1838 "newer notebook format will be used and older versions of IPython " +
1834 "may not be able to read it. To keep the older version, close the " +
1839 "may not be able to read it. To keep the older version, close the " +
1835 "notebook without saving it.";
1840 "notebook without saving it.";
1836 IPython.dialog.modal({
1841 IPython.dialog.modal({
1837 title : "Notebook converted",
1842 title : "Notebook converted",
1838 body : msg,
1843 body : msg,
1839 buttons : {
1844 buttons : {
1840 OK : {
1845 OK : {
1841 class : "btn-primary"
1846 class : "btn-primary"
1842 }
1847 }
1843 }
1848 }
1844 });
1849 });
1845 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
1850 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
1846 var that = this;
1851 var that = this;
1847 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
1852 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
1848 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
1853 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
1849 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
1854 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
1850 this_vs + ". You can still work with this notebook, but some features " +
1855 this_vs + ". You can still work with this notebook, but some features " +
1851 "introduced in later notebook versions may not be available."
1856 "introduced in later notebook versions may not be available."
1852
1857
1853 IPython.dialog.modal({
1858 IPython.dialog.modal({
1854 title : "Newer Notebook",
1859 title : "Newer Notebook",
1855 body : msg,
1860 body : msg,
1856 buttons : {
1861 buttons : {
1857 OK : {
1862 OK : {
1858 class : "btn-danger"
1863 class : "btn-danger"
1859 }
1864 }
1860 }
1865 }
1861 });
1866 });
1862
1867
1863 }
1868 }
1864
1869
1865 // Create the session after the notebook is completely loaded to prevent
1870 // Create the session after the notebook is completely loaded to prevent
1866 // code execution upon loading, which is a security risk.
1871 // code execution upon loading, which is a security risk.
1867 if (this.session == null) {
1872 if (this.session == null) {
1868 this.start_session();
1873 this.start_session();
1869 }
1874 }
1870 // load our checkpoint list
1875 // load our checkpoint list
1871 this.list_checkpoints();
1876 this.list_checkpoints();
1872
1877
1873 // load toolbar state
1878 // load toolbar state
1874 if (this.metadata.celltoolbar) {
1879 if (this.metadata.celltoolbar) {
1875 IPython.CellToolbar.global_show();
1880 IPython.CellToolbar.global_show();
1876 IPython.CellToolbar.activate_preset(this.metadata.celltoolbar);
1881 IPython.CellToolbar.activate_preset(this.metadata.celltoolbar);
1877 }
1882 }
1878
1883
1879 $([IPython.events]).trigger('notebook_loaded.Notebook');
1884 $([IPython.events]).trigger('notebook_loaded.Notebook');
1880 };
1885 };
1881
1886
1882 /**
1887 /**
1883 * Failure callback for loading a notebook from the server.
1888 * Failure callback for loading a notebook from the server.
1884 *
1889 *
1885 * @method load_notebook_error
1890 * @method load_notebook_error
1886 * @param {jqXHR} xhr jQuery Ajax object
1891 * @param {jqXHR} xhr jQuery Ajax object
1887 * @param {String} status Description of response status
1892 * @param {String} status Description of response status
1888 * @param {String} error HTTP error message
1893 * @param {String} error HTTP error message
1889 */
1894 */
1890 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
1895 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
1891 $([IPython.events]).trigger('notebook_load_failed.Notebook', [xhr, status, error]);
1896 $([IPython.events]).trigger('notebook_load_failed.Notebook', [xhr, status, error]);
1892 if (xhr.status === 400) {
1897 if (xhr.status === 400) {
1893 var msg = error;
1898 var msg = error;
1894 } else if (xhr.status === 500) {
1899 } else if (xhr.status === 500) {
1895 var msg = "An unknown error occurred while loading this notebook. " +
1900 var msg = "An unknown error occurred while loading this notebook. " +
1896 "This version can load notebook formats " +
1901 "This version can load notebook formats " +
1897 "v" + this.nbformat + " or earlier.";
1902 "v" + this.nbformat + " or earlier.";
1898 }
1903 }
1899 IPython.dialog.modal({
1904 IPython.dialog.modal({
1900 title: "Error loading notebook",
1905 title: "Error loading notebook",
1901 body : msg,
1906 body : msg,
1902 buttons : {
1907 buttons : {
1903 "OK": {}
1908 "OK": {}
1904 }
1909 }
1905 });
1910 });
1906 }
1911 }
1907
1912
1908 /********************* checkpoint-related *********************/
1913 /********************* checkpoint-related *********************/
1909
1914
1910 /**
1915 /**
1911 * Save the notebook then immediately create a checkpoint.
1916 * Save the notebook then immediately create a checkpoint.
1912 *
1917 *
1913 * @method save_checkpoint
1918 * @method save_checkpoint
1914 */
1919 */
1915 Notebook.prototype.save_checkpoint = function () {
1920 Notebook.prototype.save_checkpoint = function () {
1916 this._checkpoint_after_save = true;
1921 this._checkpoint_after_save = true;
1917 this.save_notebook();
1922 this.save_notebook();
1918 };
1923 };
1919
1924
1920 /**
1925 /**
1921 * Add a checkpoint for this notebook.
1926 * Add a checkpoint for this notebook.
1922 * for use as a callback from checkpoint creation.
1927 * for use as a callback from checkpoint creation.
1923 *
1928 *
1924 * @method add_checkpoint
1929 * @method add_checkpoint
1925 */
1930 */
1926 Notebook.prototype.add_checkpoint = function (checkpoint) {
1931 Notebook.prototype.add_checkpoint = function (checkpoint) {
1927 var found = false;
1932 var found = false;
1928 for (var i = 0; i < this.checkpoints.length; i++) {
1933 for (var i = 0; i < this.checkpoints.length; i++) {
1929 var existing = this.checkpoints[i];
1934 var existing = this.checkpoints[i];
1930 if (existing.id == checkpoint.id) {
1935 if (existing.id == checkpoint.id) {
1931 found = true;
1936 found = true;
1932 this.checkpoints[i] = checkpoint;
1937 this.checkpoints[i] = checkpoint;
1933 break;
1938 break;
1934 }
1939 }
1935 }
1940 }
1936 if (!found) {
1941 if (!found) {
1937 this.checkpoints.push(checkpoint);
1942 this.checkpoints.push(checkpoint);
1938 }
1943 }
1939 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
1944 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
1940 };
1945 };
1941
1946
1942 /**
1947 /**
1943 * List checkpoints for this notebook.
1948 * List checkpoints for this notebook.
1944 *
1949 *
1945 * @method list_checkpoints
1950 * @method list_checkpoints
1946 */
1951 */
1947 Notebook.prototype.list_checkpoints = function () {
1952 Notebook.prototype.list_checkpoints = function () {
1948 var url = utils.url_join_encode(
1953 var url = utils.url_join_encode(
1949 this._baseProjectUrl,
1954 this._baseProjectUrl,
1950 'api/notebooks',
1955 'api/notebooks',
1951 this.notebook_path,
1956 this.notebook_path,
1952 this.notebook_name,
1957 this.notebook_name,
1953 'checkpoints'
1958 'checkpoints'
1954 );
1959 );
1955 $.get(url).done(
1960 $.get(url).done(
1956 $.proxy(this.list_checkpoints_success, this)
1961 $.proxy(this.list_checkpoints_success, this)
1957 ).fail(
1962 ).fail(
1958 $.proxy(this.list_checkpoints_error, this)
1963 $.proxy(this.list_checkpoints_error, this)
1959 );
1964 );
1960 };
1965 };
1961
1966
1962 /**
1967 /**
1963 * Success callback for listing checkpoints.
1968 * Success callback for listing checkpoints.
1964 *
1969 *
1965 * @method list_checkpoint_success
1970 * @method list_checkpoint_success
1966 * @param {Object} data JSON representation of a checkpoint
1971 * @param {Object} data JSON representation of a checkpoint
1967 * @param {String} status Description of response status
1972 * @param {String} status Description of response status
1968 * @param {jqXHR} xhr jQuery Ajax object
1973 * @param {jqXHR} xhr jQuery Ajax object
1969 */
1974 */
1970 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
1975 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
1971 var data = $.parseJSON(data);
1976 var data = $.parseJSON(data);
1972 this.checkpoints = data;
1977 this.checkpoints = data;
1973 if (data.length) {
1978 if (data.length) {
1974 this.last_checkpoint = data[data.length - 1];
1979 this.last_checkpoint = data[data.length - 1];
1975 } else {
1980 } else {
1976 this.last_checkpoint = null;
1981 this.last_checkpoint = null;
1977 }
1982 }
1978 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
1983 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
1979 };
1984 };
1980
1985
1981 /**
1986 /**
1982 * Failure callback for listing a checkpoint.
1987 * Failure callback for listing a checkpoint.
1983 *
1988 *
1984 * @method list_checkpoint_error
1989 * @method list_checkpoint_error
1985 * @param {jqXHR} xhr jQuery Ajax object
1990 * @param {jqXHR} xhr jQuery Ajax object
1986 * @param {String} status Description of response status
1991 * @param {String} status Description of response status
1987 * @param {String} error_msg HTTP error message
1992 * @param {String} error_msg HTTP error message
1988 */
1993 */
1989 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
1994 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
1990 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
1995 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
1991 };
1996 };
1992
1997
1993 /**
1998 /**
1994 * Create a checkpoint of this notebook on the server from the most recent save.
1999 * Create a checkpoint of this notebook on the server from the most recent save.
1995 *
2000 *
1996 * @method create_checkpoint
2001 * @method create_checkpoint
1997 */
2002 */
1998 Notebook.prototype.create_checkpoint = function () {
2003 Notebook.prototype.create_checkpoint = function () {
1999 var url = utils.url_join_encode(
2004 var url = utils.url_join_encode(
2000 this._baseProjectUrl,
2005 this._baseProjectUrl,
2001 'api/notebooks',
2006 'api/notebooks',
2002 this.notebookPath(),
2007 this.notebookPath(),
2003 this.notebook_name,
2008 this.notebook_name,
2004 'checkpoints'
2009 'checkpoints'
2005 );
2010 );
2006 $.post(url).done(
2011 $.post(url).done(
2007 $.proxy(this.create_checkpoint_success, this)
2012 $.proxy(this.create_checkpoint_success, this)
2008 ).fail(
2013 ).fail(
2009 $.proxy(this.create_checkpoint_error, this)
2014 $.proxy(this.create_checkpoint_error, this)
2010 );
2015 );
2011 };
2016 };
2012
2017
2013 /**
2018 /**
2014 * Success callback for creating a checkpoint.
2019 * Success callback for creating a checkpoint.
2015 *
2020 *
2016 * @method create_checkpoint_success
2021 * @method create_checkpoint_success
2017 * @param {Object} data JSON representation of a checkpoint
2022 * @param {Object} data JSON representation of a checkpoint
2018 * @param {String} status Description of response status
2023 * @param {String} status Description of response status
2019 * @param {jqXHR} xhr jQuery Ajax object
2024 * @param {jqXHR} xhr jQuery Ajax object
2020 */
2025 */
2021 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2026 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2022 var data = $.parseJSON(data);
2027 var data = $.parseJSON(data);
2023 this.add_checkpoint(data);
2028 this.add_checkpoint(data);
2024 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
2029 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
2025 };
2030 };
2026
2031
2027 /**
2032 /**
2028 * Failure callback for creating a checkpoint.
2033 * Failure callback for creating a checkpoint.
2029 *
2034 *
2030 * @method create_checkpoint_error
2035 * @method create_checkpoint_error
2031 * @param {jqXHR} xhr jQuery Ajax object
2036 * @param {jqXHR} xhr jQuery Ajax object
2032 * @param {String} status Description of response status
2037 * @param {String} status Description of response status
2033 * @param {String} error_msg HTTP error message
2038 * @param {String} error_msg HTTP error message
2034 */
2039 */
2035 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2040 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2036 $([IPython.events]).trigger('checkpoint_failed.Notebook');
2041 $([IPython.events]).trigger('checkpoint_failed.Notebook');
2037 };
2042 };
2038
2043
2039 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2044 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2040 var that = this;
2045 var that = this;
2041 var checkpoint = checkpoint || this.last_checkpoint;
2046 var checkpoint = checkpoint || this.last_checkpoint;
2042 if ( ! checkpoint ) {
2047 if ( ! checkpoint ) {
2043 console.log("restore dialog, but no checkpoint to restore to!");
2048 console.log("restore dialog, but no checkpoint to restore to!");
2044 return;
2049 return;
2045 }
2050 }
2046 var body = $('<div/>').append(
2051 var body = $('<div/>').append(
2047 $('<p/>').addClass("p-space").text(
2052 $('<p/>').addClass("p-space").text(
2048 "Are you sure you want to revert the notebook to " +
2053 "Are you sure you want to revert the notebook to " +
2049 "the latest checkpoint?"
2054 "the latest checkpoint?"
2050 ).append(
2055 ).append(
2051 $("<strong/>").text(
2056 $("<strong/>").text(
2052 " This cannot be undone."
2057 " This cannot be undone."
2053 )
2058 )
2054 )
2059 )
2055 ).append(
2060 ).append(
2056 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2061 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2057 ).append(
2062 ).append(
2058 $('<p/>').addClass("p-space").text(
2063 $('<p/>').addClass("p-space").text(
2059 Date(checkpoint.last_modified)
2064 Date(checkpoint.last_modified)
2060 ).css("text-align", "center")
2065 ).css("text-align", "center")
2061 );
2066 );
2062
2067
2063 IPython.dialog.modal({
2068 IPython.dialog.modal({
2064 title : "Revert notebook to checkpoint",
2069 title : "Revert notebook to checkpoint",
2065 body : body,
2070 body : body,
2066 buttons : {
2071 buttons : {
2067 Revert : {
2072 Revert : {
2068 class : "btn-danger",
2073 class : "btn-danger",
2069 click : function () {
2074 click : function () {
2070 that.restore_checkpoint(checkpoint.id);
2075 that.restore_checkpoint(checkpoint.id);
2071 }
2076 }
2072 },
2077 },
2073 Cancel : {}
2078 Cancel : {}
2074 }
2079 }
2075 });
2080 });
2076 }
2081 }
2077
2082
2078 /**
2083 /**
2079 * Restore the notebook to a checkpoint state.
2084 * Restore the notebook to a checkpoint state.
2080 *
2085 *
2081 * @method restore_checkpoint
2086 * @method restore_checkpoint
2082 * @param {String} checkpoint ID
2087 * @param {String} checkpoint ID
2083 */
2088 */
2084 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2089 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2085 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2090 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2086 var url = utils.url_join_encode(
2091 var url = utils.url_join_encode(
2087 this._baseProjectUrl,
2092 this._baseProjectUrl,
2088 'api/notebooks',
2093 'api/notebooks',
2089 this.notebookPath(),
2094 this.notebookPath(),
2090 this.notebook_name,
2095 this.notebook_name,
2091 'checkpoints',
2096 'checkpoints',
2092 checkpoint
2097 checkpoint
2093 );
2098 );
2094 $.post(url).done(
2099 $.post(url).done(
2095 $.proxy(this.restore_checkpoint_success, this)
2100 $.proxy(this.restore_checkpoint_success, this)
2096 ).fail(
2101 ).fail(
2097 $.proxy(this.restore_checkpoint_error, this)
2102 $.proxy(this.restore_checkpoint_error, this)
2098 );
2103 );
2099 };
2104 };
2100
2105
2101 /**
2106 /**
2102 * Success callback for restoring a notebook to a checkpoint.
2107 * Success callback for restoring a notebook to a checkpoint.
2103 *
2108 *
2104 * @method restore_checkpoint_success
2109 * @method restore_checkpoint_success
2105 * @param {Object} data (ignored, should be empty)
2110 * @param {Object} data (ignored, should be empty)
2106 * @param {String} status Description of response status
2111 * @param {String} status Description of response status
2107 * @param {jqXHR} xhr jQuery Ajax object
2112 * @param {jqXHR} xhr jQuery Ajax object
2108 */
2113 */
2109 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2114 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2110 $([IPython.events]).trigger('checkpoint_restored.Notebook');
2115 $([IPython.events]).trigger('checkpoint_restored.Notebook');
2111 this.load_notebook(this.notebook_name, this.notebook_path);
2116 this.load_notebook(this.notebook_name, this.notebook_path);
2112 };
2117 };
2113
2118
2114 /**
2119 /**
2115 * Failure callback for restoring a notebook to a checkpoint.
2120 * Failure callback for restoring a notebook to a checkpoint.
2116 *
2121 *
2117 * @method restore_checkpoint_error
2122 * @method restore_checkpoint_error
2118 * @param {jqXHR} xhr jQuery Ajax object
2123 * @param {jqXHR} xhr jQuery Ajax object
2119 * @param {String} status Description of response status
2124 * @param {String} status Description of response status
2120 * @param {String} error_msg HTTP error message
2125 * @param {String} error_msg HTTP error message
2121 */
2126 */
2122 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2127 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2123 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
2128 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
2124 };
2129 };
2125
2130
2126 /**
2131 /**
2127 * Delete a notebook checkpoint.
2132 * Delete a notebook checkpoint.
2128 *
2133 *
2129 * @method delete_checkpoint
2134 * @method delete_checkpoint
2130 * @param {String} checkpoint ID
2135 * @param {String} checkpoint ID
2131 */
2136 */
2132 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2137 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2133 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2138 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2134 var url = utils.url_join_encode(
2139 var url = utils.url_join_encode(
2135 this._baseProjectUrl,
2140 this._baseProjectUrl,
2136 'api/notebooks',
2141 'api/notebooks',
2137 this.notebookPath(),
2142 this.notebookPath(),
2138 this.notebook_name,
2143 this.notebook_name,
2139 'checkpoints',
2144 'checkpoints',
2140 checkpoint
2145 checkpoint
2141 );
2146 );
2142 $.ajax(url, {
2147 $.ajax(url, {
2143 type: 'DELETE',
2148 type: 'DELETE',
2144 success: $.proxy(this.delete_checkpoint_success, this),
2149 success: $.proxy(this.delete_checkpoint_success, this),
2145 error: $.proxy(this.delete_notebook_error,this)
2150 error: $.proxy(this.delete_notebook_error,this)
2146 });
2151 });
2147 };
2152 };
2148
2153
2149 /**
2154 /**
2150 * Success callback for deleting a notebook checkpoint
2155 * Success callback for deleting a notebook checkpoint
2151 *
2156 *
2152 * @method delete_checkpoint_success
2157 * @method delete_checkpoint_success
2153 * @param {Object} data (ignored, should be empty)
2158 * @param {Object} data (ignored, should be empty)
2154 * @param {String} status Description of response status
2159 * @param {String} status Description of response status
2155 * @param {jqXHR} xhr jQuery Ajax object
2160 * @param {jqXHR} xhr jQuery Ajax object
2156 */
2161 */
2157 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2162 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2158 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2163 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2159 this.load_notebook(this.notebook_name, this.notebook_path);
2164 this.load_notebook(this.notebook_name, this.notebook_path);
2160 };
2165 };
2161
2166
2162 /**
2167 /**
2163 * Failure callback for deleting a notebook checkpoint.
2168 * Failure callback for deleting a notebook checkpoint.
2164 *
2169 *
2165 * @method delete_checkpoint_error
2170 * @method delete_checkpoint_error
2166 * @param {jqXHR} xhr jQuery Ajax object
2171 * @param {jqXHR} xhr jQuery Ajax object
2167 * @param {String} status Description of response status
2172 * @param {String} status Description of response status
2168 * @param {String} error_msg HTTP error message
2173 * @param {String} error_msg HTTP error message
2169 */
2174 */
2170 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2175 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2171 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2176 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2172 };
2177 };
2173
2178
2174
2179
2175 IPython.Notebook = Notebook;
2180 IPython.Notebook = Notebook;
2176
2181
2177
2182
2178 return IPython;
2183 return IPython;
2179
2184
2180 }(IPython));
2185 }(IPython));
@@ -1,601 +1,593
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2012 The IPython Development Team
2 // Copyright (C) 2008-2012 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // TextCell
9 // TextCell
10 //============================================================================
10 //============================================================================
11
11
12
12
13
13
14 /**
14 /**
15 A module that allow to create different type of Text Cell
15 A module that allow to create different type of Text Cell
16 @module IPython
16 @module IPython
17 @namespace IPython
17 @namespace IPython
18 */
18 */
19 var IPython = (function (IPython) {
19 var IPython = (function (IPython) {
20 "use strict";
20 "use strict";
21
21
22 // TextCell base class
22 // TextCell base class
23 var key = IPython.utils.keycodes;
23 var key = IPython.utils.keycodes;
24
24
25 /**
25 /**
26 * Construct a new TextCell, codemirror mode is by default 'htmlmixed', and cell type is 'text'
26 * Construct a new TextCell, codemirror mode is by default 'htmlmixed', and cell type is 'text'
27 * cell start as not redered.
27 * cell start as not redered.
28 *
28 *
29 * @class TextCell
29 * @class TextCell
30 * @constructor TextCell
30 * @constructor TextCell
31 * @extend IPython.Cell
31 * @extend IPython.Cell
32 * @param {object|undefined} [options]
32 * @param {object|undefined} [options]
33 * @param [options.cm_config] {object} config to pass to CodeMirror, will extend/overwrite default config
33 * @param [options.cm_config] {object} config to pass to CodeMirror, will extend/overwrite default config
34 * @param [options.placeholder] {string} default string to use when souce in empty for rendering (only use in some TextCell subclass)
34 * @param [options.placeholder] {string} default string to use when souce in empty for rendering (only use in some TextCell subclass)
35 */
35 */
36 var TextCell = function (options) {
36 var TextCell = function (options) {
37 // in all TextCell/Cell subclasses
37 // in all TextCell/Cell subclasses
38 // do not assign most of members here, just pass it down
38 // do not assign most of members here, just pass it down
39 // in the options dict potentially overwriting what you wish.
39 // in the options dict potentially overwriting what you wish.
40 // they will be assigned in the base class.
40 // they will be assigned in the base class.
41
41
42 // we cannot put this as a class key as it has handle to "this".
42 // we cannot put this as a class key as it has handle to "this".
43 var cm_overwrite_options = {
43 var cm_overwrite_options = {
44 onKeyEvent: $.proxy(this.handle_keyevent,this)
44 onKeyEvent: $.proxy(this.handle_keyevent,this)
45 };
45 };
46
46
47 options = this.mergeopt(TextCell,options,{cm_config:cm_overwrite_options});
47 options = this.mergeopt(TextCell,options,{cm_config:cm_overwrite_options});
48
48
49 this.cell_type = this.cell_type || 'text';
49 this.cell_type = this.cell_type || 'text';
50
50
51 IPython.Cell.apply(this, [options]);
51 IPython.Cell.apply(this, [options]);
52
52
53 this.rendered = false;
53 this.rendered = false;
54 };
54 };
55
55
56 TextCell.prototype = new IPython.Cell();
56 TextCell.prototype = new IPython.Cell();
57
57
58 TextCell.options_default = {
58 TextCell.options_default = {
59 cm_config : {
59 cm_config : {
60 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
60 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
61 mode: 'htmlmixed',
61 mode: 'htmlmixed',
62 lineWrapping : true,
62 lineWrapping : true,
63 }
63 }
64 };
64 };
65
65
66
66
67 /**
67 /**
68 * Create the DOM element of the TextCell
68 * Create the DOM element of the TextCell
69 * @method create_element
69 * @method create_element
70 * @private
70 * @private
71 */
71 */
72 TextCell.prototype.create_element = function () {
72 TextCell.prototype.create_element = function () {
73 IPython.Cell.prototype.create_element.apply(this, arguments);
73 IPython.Cell.prototype.create_element.apply(this, arguments);
74
74
75 var cell = $("<div>").addClass('cell text_cell border-box-sizing');
75 var cell = $("<div>").addClass('cell text_cell border-box-sizing');
76 cell.attr('tabindex','2');
76 cell.attr('tabindex','2');
77
77
78 var prompt = $('<div/>').addClass('prompt input_prompt');
78 var prompt = $('<div/>').addClass('prompt input_prompt');
79 cell.append(prompt);
79 cell.append(prompt);
80 var inner_cell = $('<div/>').addClass('inner_cell');
80 var inner_cell = $('<div/>').addClass('inner_cell');
81 this.celltoolbar = new IPython.CellToolbar(this);
81 this.celltoolbar = new IPython.CellToolbar(this);
82 inner_cell.append(this.celltoolbar.element);
82 inner_cell.append(this.celltoolbar.element);
83 var input_area = $('<div/>').addClass('text_cell_input border-box-sizing');
83 var input_area = $('<div/>').addClass('text_cell_input border-box-sizing');
84 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
84 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
85 // The tabindex=-1 makes this div focusable.
85 // The tabindex=-1 makes this div focusable.
86 var render_area = $('<div/>').addClass('text_cell_render border-box-sizing').
86 var render_area = $('<div/>').addClass('text_cell_render border-box-sizing').
87 addClass('rendered_html').attr('tabindex','-1');
87 addClass('rendered_html').attr('tabindex','-1');
88 inner_cell.append(input_area).append(render_area);
88 inner_cell.append(input_area).append(render_area);
89 cell.append(inner_cell);
89 cell.append(inner_cell);
90 this.element = cell;
90 this.element = cell;
91 };
91 };
92
92
93
93
94 /**
94 /**
95 * Bind the DOM evet to cell actions
95 * Bind the DOM evet to cell actions
96 * Need to be called after TextCell.create_element
96 * Need to be called after TextCell.create_element
97 * @private
97 * @private
98 * @method bind_event
98 * @method bind_event
99 */
99 */
100 TextCell.prototype.bind_events = function () {
100 TextCell.prototype.bind_events = function () {
101 IPython.Cell.prototype.bind_events.apply(this);
101 IPython.Cell.prototype.bind_events.apply(this);
102 var that = this;
102 var that = this;
103
103
104 this.element.dblclick(function () {
104 this.element.dblclick(function () {
105 if (that.selected === false) {
105 if (that.selected === false) {
106 $([IPython.events]).trigger('select.Cell', {'cell':that});
106 $([IPython.events]).trigger('select.Cell', {'cell':that});
107 };
107 };
108 $([IPython.events]).trigger('edit_mode.Cell', {cell: that});
108 $([IPython.events]).trigger('edit_mode.Cell', {cell: that});
109 });
109 });
110 };
110 };
111
111
112 TextCell.prototype.handle_keyevent = function (editor, event) {
112 TextCell.prototype.handle_keyevent = function (editor, event) {
113
113
114 console.log('CM', this.mode, event.which, event.type)
114 console.log('CM', this.mode, event.which, event.type)
115
115
116 if (this.mode === 'command') {
116 if (this.mode === 'command') {
117 return true;
117 return true;
118 } else if (this.mode === 'edit') {
118 } else if (this.mode === 'edit') {
119 return this.handle_codemirror_keyevent(editor, event);
119 return this.handle_codemirror_keyevent(editor, event);
120 }
120 }
121 };
121 };
122
122
123 /**
123 /**
124 * This method gets called in CodeMirror's onKeyDown/onKeyPress
124 * This method gets called in CodeMirror's onKeyDown/onKeyPress
125 * handlers and is used to provide custom key handling.
125 * handlers and is used to provide custom key handling.
126 *
126 *
127 * Subclass should override this method to have custom handeling
127 * Subclass should override this method to have custom handeling
128 *
128 *
129 * @method handle_codemirror_keyevent
129 * @method handle_codemirror_keyevent
130 * @param {CodeMirror} editor - The codemirror instance bound to the cell
130 * @param {CodeMirror} editor - The codemirror instance bound to the cell
131 * @param {event} event -
131 * @param {event} event -
132 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
132 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
133 */
133 */
134 TextCell.prototype.handle_codemirror_keyevent = function (editor, event) {
134 TextCell.prototype.handle_codemirror_keyevent = function (editor, event) {
135 var that = this;
135 var that = this;
136
136
137 if (event.keyCode === 13 && (event.shiftKey || event.ctrlKey || event.altKey)) {
137 if (event.keyCode === 13 && (event.shiftKey || event.ctrlKey || event.altKey)) {
138 // Always ignore shift-enter in CodeMirror as we handle it.
138 // Always ignore shift-enter in CodeMirror as we handle it.
139 return true;
139 return true;
140 } else if (event.which === key.UPARROW && event.type === 'keydown') {
140 } else if (event.which === key.UPARROW && event.type === 'keydown') {
141 // If we are not at the top, let CM handle the up arrow and
141 // If we are not at the top, let CM handle the up arrow and
142 // prevent the global keydown handler from handling it.
142 // prevent the global keydown handler from handling it.
143 if (!that.at_top()) {
143 if (!that.at_top()) {
144 event.stop();
144 event.stop();
145 return false;
145 return false;
146 } else {
146 } else {
147 return true;
147 return true;
148 };
148 };
149 } else if (event.which === key.DOWNARROW && event.type === 'keydown') {
149 } else if (event.which === key.DOWNARROW && event.type === 'keydown') {
150 // If we are not at the bottom, let CM handle the down arrow and
150 // If we are not at the bottom, let CM handle the down arrow and
151 // prevent the global keydown handler from handling it.
151 // prevent the global keydown handler from handling it.
152 if (!that.at_bottom()) {
152 if (!that.at_bottom()) {
153 event.stop();
153 event.stop();
154 return false;
154 return false;
155 } else {
155 } else {
156 return true;
156 return true;
157 };
157 };
158 } else if (event.which === key.ESC && event.type === 'keydown') {
158 } else if (event.which === key.ESC && event.type === 'keydown') {
159 if (that.code_mirror.options.keyMap === "vim-insert") {
159 if (that.code_mirror.options.keyMap === "vim-insert") {
160 // vim keyMap is active and in insert mode. In this case we leave vim
160 // vim keyMap is active and in insert mode. In this case we leave vim
161 // insert mode, but remain in notebook edit mode.
161 // insert mode, but remain in notebook edit mode.
162 // Let' CM handle this event and prevent global handling.
162 // Let' CM handle this event and prevent global handling.
163 event.stop();
163 event.stop();
164 return false;
164 return false;
165 } else {
165 } else {
166 // vim keyMap is not active. Leave notebook edit mode.
166 // vim keyMap is not active. Leave notebook edit mode.
167 // Don't let CM handle the event, defer to global handling.
167 // Don't let CM handle the event, defer to global handling.
168 return true;
168 return true;
169 }
169 }
170 }
170 }
171 return false;
171 return false;
172 };
172 };
173
173
174 // Cell level actions
174 // Cell level actions
175
175
176 TextCell.prototype.select = function () {
176 TextCell.prototype.select = function () {
177 var cont = IPython.Cell.prototype.select.apply(this);
177 var cont = IPython.Cell.prototype.select.apply(this);
178 if (cont) {
178 if (cont) {
179 if (this.mode === 'edit') {
179 if (this.mode === 'edit') {
180 this.code_mirror.refresh();
180 this.code_mirror.refresh();
181 }
181 }
182 };
182 };
183 return cont;
183 return cont;
184 };
184 };
185
185
186 TextCell.prototype.unrender = function () {
186 TextCell.prototype.unrender = function () {
187 if (this.read_only) return;
187 if (this.read_only) return;
188 var cont = IPython.Cell.prototype.unrender.apply(this);
188 var cont = IPython.Cell.prototype.unrender.apply(this);
189 if (cont) {
189 if (cont) {
190 var text_cell = this.element;
190 var text_cell = this.element;
191 var output = text_cell.find("div.text_cell_render");
191 var output = text_cell.find("div.text_cell_render");
192 output.hide();
192 output.hide();
193 text_cell.find('div.text_cell_input').show();
193 text_cell.find('div.text_cell_input').show();
194 if (this.get_text() === this.placeholder) {
194 if (this.get_text() === this.placeholder) {
195 this.set_text('');
195 this.set_text('');
196 this.refresh();
196 this.refresh();
197 }
197 }
198
198
199 };
199 };
200 return cont;
200 return cont;
201 };
201 };
202
202
203 TextCell.prototype.execute = function () {
203 TextCell.prototype.execute = function () {
204 this.render();
204 this.render();
205 };
205 };
206
206
207 TextCell.prototype.command_mode = function () {
208 var cont = IPython.Cell.prototype.command_mode.apply(this);
209 if (cont) {
210 this.focus_cell();
211 };
212 return cont;
213 }
214
215 TextCell.prototype.edit_mode = function () {
207 TextCell.prototype.edit_mode = function () {
216 var cont = IPython.Cell.prototype.edit_mode.apply(this);
208 var cont = IPython.Cell.prototype.edit_mode.apply(this);
217 if (cont) {
209 if (cont) {
218 this.unrender();
210 this.unrender();
219 this.focus_editor();
211 this.focus_editor();
220 };
212 };
221 return cont;
213 return cont;
222 }
214 }
223
215
224 /**
216 /**
225 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
217 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
226 * @method get_text
218 * @method get_text
227 * @retrun {string} CodeMirror current text value
219 * @retrun {string} CodeMirror current text value
228 */
220 */
229 TextCell.prototype.get_text = function() {
221 TextCell.prototype.get_text = function() {
230 return this.code_mirror.getValue();
222 return this.code_mirror.getValue();
231 };
223 };
232
224
233 /**
225 /**
234 * @param {string} text - Codemiror text value
226 * @param {string} text - Codemiror text value
235 * @see TextCell#get_text
227 * @see TextCell#get_text
236 * @method set_text
228 * @method set_text
237 * */
229 * */
238 TextCell.prototype.set_text = function(text) {
230 TextCell.prototype.set_text = function(text) {
239 this.code_mirror.setValue(text);
231 this.code_mirror.setValue(text);
240 this.code_mirror.refresh();
232 this.code_mirror.refresh();
241 };
233 };
242
234
243 /**
235 /**
244 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
236 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
245 * @method get_rendered
237 * @method get_rendered
246 * @return {html} html of rendered element
238 * @return {html} html of rendered element
247 * */
239 * */
248 TextCell.prototype.get_rendered = function() {
240 TextCell.prototype.get_rendered = function() {
249 return this.element.find('div.text_cell_render').html();
241 return this.element.find('div.text_cell_render').html();
250 };
242 };
251
243
252 /**
244 /**
253 * @method set_rendered
245 * @method set_rendered
254 */
246 */
255 TextCell.prototype.set_rendered = function(text) {
247 TextCell.prototype.set_rendered = function(text) {
256 this.element.find('div.text_cell_render').html(text);
248 this.element.find('div.text_cell_render').html(text);
257 };
249 };
258
250
259 /**
251 /**
260 * @method at_top
252 * @method at_top
261 * @return {Boolean}
253 * @return {Boolean}
262 */
254 */
263 TextCell.prototype.at_top = function () {
255 TextCell.prototype.at_top = function () {
264 if (this.rendered) {
256 if (this.rendered) {
265 return true;
257 return true;
266 } else {
258 } else {
267 var cursor = this.code_mirror.getCursor();
259 var cursor = this.code_mirror.getCursor();
268 if (cursor.line === 0 && cursor.ch === 0) {
260 if (cursor.line === 0 && cursor.ch === 0) {
269 return true;
261 return true;
270 } else {
262 } else {
271 return false;
263 return false;
272 };
264 };
273 };
265 };
274 };
266 };
275
267
276 /**
268 /**
277 * @method at_bottom
269 * @method at_bottom
278 * @return {Boolean}
270 * @return {Boolean}
279 * */
271 * */
280 TextCell.prototype.at_bottom = function () {
272 TextCell.prototype.at_bottom = function () {
281 if (this.rendered) {
273 if (this.rendered) {
282 return true;
274 return true;
283 } else {
275 } else {
284 var cursor = this.code_mirror.getCursor();
276 var cursor = this.code_mirror.getCursor();
285 if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) {
277 if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) {
286 return true;
278 return true;
287 } else {
279 } else {
288 return false;
280 return false;
289 };
281 };
290 };
282 };
291 };
283 };
292
284
293 /**
285 /**
294 * Create Text cell from JSON
286 * Create Text cell from JSON
295 * @param {json} data - JSON serialized text-cell
287 * @param {json} data - JSON serialized text-cell
296 * @method fromJSON
288 * @method fromJSON
297 */
289 */
298 TextCell.prototype.fromJSON = function (data) {
290 TextCell.prototype.fromJSON = function (data) {
299 IPython.Cell.prototype.fromJSON.apply(this, arguments);
291 IPython.Cell.prototype.fromJSON.apply(this, arguments);
300 if (data.cell_type === this.cell_type) {
292 if (data.cell_type === this.cell_type) {
301 if (data.source !== undefined) {
293 if (data.source !== undefined) {
302 this.set_text(data.source);
294 this.set_text(data.source);
303 // make this value the starting point, so that we can only undo
295 // make this value the starting point, so that we can only undo
304 // to this state, instead of a blank cell
296 // to this state, instead of a blank cell
305 this.code_mirror.clearHistory();
297 this.code_mirror.clearHistory();
306 this.set_rendered(data.rendered || '');
298 this.set_rendered(data.rendered || '');
307 this.rendered = false;
299 this.rendered = false;
308 this.render();
300 this.render();
309 }
301 }
310 }
302 }
311 };
303 };
312
304
313 /** Generate JSON from cell
305 /** Generate JSON from cell
314 * @return {object} cell data serialised to json
306 * @return {object} cell data serialised to json
315 */
307 */
316 TextCell.prototype.toJSON = function () {
308 TextCell.prototype.toJSON = function () {
317 var data = IPython.Cell.prototype.toJSON.apply(this);
309 var data = IPython.Cell.prototype.toJSON.apply(this);
318 data.source = this.get_text();
310 data.source = this.get_text();
319 if (data.source == this.placeholder) {
311 if (data.source == this.placeholder) {
320 data.source = "";
312 data.source = "";
321 }
313 }
322 return data;
314 return data;
323 };
315 };
324
316
325
317
326 /**
318 /**
327 * @class MarkdownCell
319 * @class MarkdownCell
328 * @constructor MarkdownCell
320 * @constructor MarkdownCell
329 * @extends IPython.HTMLCell
321 * @extends IPython.HTMLCell
330 */
322 */
331 var MarkdownCell = function (options) {
323 var MarkdownCell = function (options) {
332 options = this.mergeopt(MarkdownCell, options);
324 options = this.mergeopt(MarkdownCell, options);
333
325
334 this.cell_type = 'markdown';
326 this.cell_type = 'markdown';
335 TextCell.apply(this, [options]);
327 TextCell.apply(this, [options]);
336 };
328 };
337
329
338 MarkdownCell.options_default = {
330 MarkdownCell.options_default = {
339 cm_config: {
331 cm_config: {
340 mode: 'gfm'
332 mode: 'gfm'
341 },
333 },
342 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
334 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
343 }
335 }
344
336
345 MarkdownCell.prototype = new TextCell();
337 MarkdownCell.prototype = new TextCell();
346
338
347 /**
339 /**
348 * @method render
340 * @method render
349 */
341 */
350 MarkdownCell.prototype.render = function () {
342 MarkdownCell.prototype.render = function () {
351 var cont = IPython.TextCell.prototype.render.apply(this);
343 var cont = IPython.TextCell.prototype.render.apply(this);
352 if (cont) {
344 if (cont) {
353 var text = this.get_text();
345 var text = this.get_text();
354 var math = null;
346 var math = null;
355 if (text === "") { text = this.placeholder; }
347 if (text === "") { text = this.placeholder; }
356 var text_and_math = IPython.mathjaxutils.remove_math(text);
348 var text_and_math = IPython.mathjaxutils.remove_math(text);
357 text = text_and_math[0];
349 text = text_and_math[0];
358 math = text_and_math[1];
350 math = text_and_math[1];
359 var html = marked.parser(marked.lexer(text));
351 var html = marked.parser(marked.lexer(text));
360 html = $(IPython.mathjaxutils.replace_math(html, math));
352 html = $(IPython.mathjaxutils.replace_math(html, math));
361 // links in markdown cells should open in new tabs
353 // links in markdown cells should open in new tabs
362 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
354 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
363 try {
355 try {
364 this.set_rendered(html);
356 this.set_rendered(html);
365 } catch (e) {
357 } catch (e) {
366 console.log("Error running Javascript in Markdown:");
358 console.log("Error running Javascript in Markdown:");
367 console.log(e);
359 console.log(e);
368 this.set_rendered($("<div/>").addClass("js-error").html(
360 this.set_rendered($("<div/>").addClass("js-error").html(
369 "Error rendering Markdown!<br/>" + e.toString())
361 "Error rendering Markdown!<br/>" + e.toString())
370 );
362 );
371 }
363 }
372 this.element.find('div.text_cell_input').hide();
364 this.element.find('div.text_cell_input').hide();
373 this.element.find("div.text_cell_render").show();
365 this.element.find("div.text_cell_render").show();
374 this.typeset()
366 this.typeset()
375 };
367 };
376 return cont;
368 return cont;
377 };
369 };
378
370
379
371
380 // RawCell
372 // RawCell
381
373
382 /**
374 /**
383 * @class RawCell
375 * @class RawCell
384 * @constructor RawCell
376 * @constructor RawCell
385 * @extends IPython.TextCell
377 * @extends IPython.TextCell
386 */
378 */
387 var RawCell = function (options) {
379 var RawCell = function (options) {
388
380
389 options = this.mergeopt(RawCell,options)
381 options = this.mergeopt(RawCell,options)
390 TextCell.apply(this, [options]);
382 TextCell.apply(this, [options]);
391 this.cell_type = 'raw';
383 this.cell_type = 'raw';
392 // RawCell should always hide its rendered div
384 // RawCell should always hide its rendered div
393 this.element.find('div.text_cell_render').hide();
385 this.element.find('div.text_cell_render').hide();
394 };
386 };
395
387
396 RawCell.options_default = {
388 RawCell.options_default = {
397 placeholder : "Write raw LaTeX or other formats here, for use with nbconvert.\n" +
389 placeholder : "Write raw LaTeX or other formats here, for use with nbconvert.\n" +
398 "It will not be rendered in the notebook.\n" +
390 "It will not be rendered in the notebook.\n" +
399 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
391 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
400 };
392 };
401
393
402 RawCell.prototype = new TextCell();
394 RawCell.prototype = new TextCell();
403
395
404 /** @method bind_events **/
396 /** @method bind_events **/
405 RawCell.prototype.bind_events = function () {
397 RawCell.prototype.bind_events = function () {
406 TextCell.prototype.bind_events.apply(this);
398 TextCell.prototype.bind_events.apply(this);
407 var that = this
399 var that = this
408 this.element.focusout(function() {
400 this.element.focusout(function() {
409 that.auto_highlight();
401 that.auto_highlight();
410 });
402 });
411 };
403 };
412
404
413 /**
405 /**
414 * Trigger autodetection of highlight scheme for current cell
406 * Trigger autodetection of highlight scheme for current cell
415 * @method auto_highlight
407 * @method auto_highlight
416 */
408 */
417 RawCell.prototype.auto_highlight = function () {
409 RawCell.prototype.auto_highlight = function () {
418 this._auto_highlight(IPython.config.raw_cell_highlight);
410 this._auto_highlight(IPython.config.raw_cell_highlight);
419 };
411 };
420
412
421 /** @method render **/
413 /** @method render **/
422 RawCell.prototype.render = function () {
414 RawCell.prototype.render = function () {
423 // Make sure that this cell type can never be rendered
415 // Make sure that this cell type can never be rendered
424 if (this.rendered) {
416 if (this.rendered) {
425 this.unrender();
417 this.unrender();
426 }
418 }
427 var text = this.get_text();
419 var text = this.get_text();
428 if (text === "") { text = this.placeholder; }
420 if (text === "") { text = this.placeholder; }
429 this.set_text(text);
421 this.set_text(text);
430 };
422 };
431
423
432
424
433 /** @method handle_codemirror_keyevent **/
425 /** @method handle_codemirror_keyevent **/
434 RawCell.prototype.handle_codemirror_keyevent = function (editor, event) {
426 RawCell.prototype.handle_codemirror_keyevent = function (editor, event) {
435
427
436 var that = this;
428 var that = this;
437 if (this.mode === 'command') {
429 if (this.mode === 'command') {
438 return false
430 return false
439 } else if (this.mode === 'edit') {
431 } else if (this.mode === 'edit') {
440 // TODO: review these handlers...
432 // TODO: review these handlers...
441 if (event.which === key.UPARROW && event.type === 'keydown') {
433 if (event.which === key.UPARROW && event.type === 'keydown') {
442 // If we are not at the top, let CM handle the up arrow and
434 // If we are not at the top, let CM handle the up arrow and
443 // prevent the global keydown handler from handling it.
435 // prevent the global keydown handler from handling it.
444 if (!that.at_top()) {
436 if (!that.at_top()) {
445 event.stop();
437 event.stop();
446 return false;
438 return false;
447 } else {
439 } else {
448 return true;
440 return true;
449 };
441 };
450 } else if (event.which === key.DOWNARROW && event.type === 'keydown') {
442 } else if (event.which === key.DOWNARROW && event.type === 'keydown') {
451 // If we are not at the bottom, let CM handle the down arrow and
443 // If we are not at the bottom, let CM handle the down arrow and
452 // prevent the global keydown handler from handling it.
444 // prevent the global keydown handler from handling it.
453 if (!that.at_bottom()) {
445 if (!that.at_bottom()) {
454 event.stop();
446 event.stop();
455 return false;
447 return false;
456 } else {
448 } else {
457 return true;
449 return true;
458 };
450 };
459 };
451 };
460 return false;
452 return false;
461 };
453 };
462 return false;
454 return false;
463 };
455 };
464
456
465
457
466 /**
458 /**
467 * @class HeadingCell
459 * @class HeadingCell
468 * @extends IPython.TextCell
460 * @extends IPython.TextCell
469 */
461 */
470
462
471 /**
463 /**
472 * @constructor HeadingCell
464 * @constructor HeadingCell
473 * @extends IPython.TextCell
465 * @extends IPython.TextCell
474 */
466 */
475 var HeadingCell = function (options) {
467 var HeadingCell = function (options) {
476 options = this.mergeopt(HeadingCell, options);
468 options = this.mergeopt(HeadingCell, options);
477
469
478 this.level = 1;
470 this.level = 1;
479 this.cell_type = 'heading';
471 this.cell_type = 'heading';
480 TextCell.apply(this, [options]);
472 TextCell.apply(this, [options]);
481
473
482 /**
474 /**
483 * heading level of the cell, use getter and setter to access
475 * heading level of the cell, use getter and setter to access
484 * @property level
476 * @property level
485 */
477 */
486 };
478 };
487
479
488 HeadingCell.options_default = {
480 HeadingCell.options_default = {
489 placeholder: "Type Heading Here"
481 placeholder: "Type Heading Here"
490 };
482 };
491
483
492 HeadingCell.prototype = new TextCell();
484 HeadingCell.prototype = new TextCell();
493
485
494 /** @method fromJSON */
486 /** @method fromJSON */
495 HeadingCell.prototype.fromJSON = function (data) {
487 HeadingCell.prototype.fromJSON = function (data) {
496 if (data.level != undefined){
488 if (data.level != undefined){
497 this.level = data.level;
489 this.level = data.level;
498 }
490 }
499 TextCell.prototype.fromJSON.apply(this, arguments);
491 TextCell.prototype.fromJSON.apply(this, arguments);
500 };
492 };
501
493
502
494
503 /** @method toJSON */
495 /** @method toJSON */
504 HeadingCell.prototype.toJSON = function () {
496 HeadingCell.prototype.toJSON = function () {
505 var data = TextCell.prototype.toJSON.apply(this);
497 var data = TextCell.prototype.toJSON.apply(this);
506 data.level = this.get_level();
498 data.level = this.get_level();
507 return data;
499 return data;
508 };
500 };
509
501
510 /**
502 /**
511 * can the cell be split into two cells
503 * can the cell be split into two cells
512 * @method is_splittable
504 * @method is_splittable
513 **/
505 **/
514 HeadingCell.prototype.is_splittable = function () {
506 HeadingCell.prototype.is_splittable = function () {
515 return false;
507 return false;
516 };
508 };
517
509
518
510
519 /**
511 /**
520 * can the cell be merged with other cells
512 * can the cell be merged with other cells
521 * @method is_mergeable
513 * @method is_mergeable
522 **/
514 **/
523 HeadingCell.prototype.is_mergeable = function () {
515 HeadingCell.prototype.is_mergeable = function () {
524 return false;
516 return false;
525 };
517 };
526
518
527 /**
519 /**
528 * Change heading level of cell, and re-render
520 * Change heading level of cell, and re-render
529 * @method set_level
521 * @method set_level
530 */
522 */
531 HeadingCell.prototype.set_level = function (level) {
523 HeadingCell.prototype.set_level = function (level) {
532 this.level = level;
524 this.level = level;
533 if (this.rendered) {
525 if (this.rendered) {
534 this.rendered = false;
526 this.rendered = false;
535 this.render();
527 this.render();
536 };
528 };
537 };
529 };
538
530
539 /** The depth of header cell, based on html (h1 to h6)
531 /** The depth of header cell, based on html (h1 to h6)
540 * @method get_level
532 * @method get_level
541 * @return {integer} level - for 1 to 6
533 * @return {integer} level - for 1 to 6
542 */
534 */
543 HeadingCell.prototype.get_level = function () {
535 HeadingCell.prototype.get_level = function () {
544 return this.level;
536 return this.level;
545 };
537 };
546
538
547
539
548 HeadingCell.prototype.set_rendered = function (html) {
540 HeadingCell.prototype.set_rendered = function (html) {
549 this.element.find("div.text_cell_render").html(html);
541 this.element.find("div.text_cell_render").html(html);
550 };
542 };
551
543
552
544
553 HeadingCell.prototype.get_rendered = function () {
545 HeadingCell.prototype.get_rendered = function () {
554 var r = this.element.find("div.text_cell_render");
546 var r = this.element.find("div.text_cell_render");
555 return r.children().first().html();
547 return r.children().first().html();
556 };
548 };
557
549
558
550
559 HeadingCell.prototype.render = function () {
551 HeadingCell.prototype.render = function () {
560 var cont = IPython.TextCell.prototype.render.apply(this);
552 var cont = IPython.TextCell.prototype.render.apply(this);
561 if (cont) {
553 if (cont) {
562 var text = this.get_text();
554 var text = this.get_text();
563 var math = null;
555 var math = null;
564 // Markdown headings must be a single line
556 // Markdown headings must be a single line
565 text = text.replace(/\n/g, ' ');
557 text = text.replace(/\n/g, ' ');
566 if (text === "") { text = this.placeholder; }
558 if (text === "") { text = this.placeholder; }
567 text = Array(this.level + 1).join("#") + " " + text;
559 text = Array(this.level + 1).join("#") + " " + text;
568 var text_and_math = IPython.mathjaxutils.remove_math(text);
560 var text_and_math = IPython.mathjaxutils.remove_math(text);
569 text = text_and_math[0];
561 text = text_and_math[0];
570 math = text_and_math[1];
562 math = text_and_math[1];
571 var html = marked.parser(marked.lexer(text));
563 var html = marked.parser(marked.lexer(text));
572 var h = $(IPython.mathjaxutils.replace_math(html, math));
564 var h = $(IPython.mathjaxutils.replace_math(html, math));
573 // add id and linkback anchor
565 // add id and linkback anchor
574 var hash = h.text().replace(/ /g, '-');
566 var hash = h.text().replace(/ /g, '-');
575 h.attr('id', hash);
567 h.attr('id', hash);
576 h.append(
568 h.append(
577 $('<a/>')
569 $('<a/>')
578 .addClass('anchor-link')
570 .addClass('anchor-link')
579 .attr('href', '#' + hash)
571 .attr('href', '#' + hash)
580 .text('¶')
572 .text('¶')
581 );
573 );
582
574
583 this.set_rendered(h);
575 this.set_rendered(h);
584 this.typeset();
576 this.typeset();
585 this.element.find('div.text_cell_input').hide();
577 this.element.find('div.text_cell_input').hide();
586 this.element.find("div.text_cell_render").show();
578 this.element.find("div.text_cell_render").show();
587
579
588 };
580 };
589 return cont;
581 return cont;
590 };
582 };
591
583
592 IPython.TextCell = TextCell;
584 IPython.TextCell = TextCell;
593 IPython.MarkdownCell = MarkdownCell;
585 IPython.MarkdownCell = MarkdownCell;
594 IPython.RawCell = RawCell;
586 IPython.RawCell = RawCell;
595 IPython.HeadingCell = HeadingCell;
587 IPython.HeadingCell = HeadingCell;
596
588
597
589
598 return IPython;
590 return IPython;
599
591
600 }(IPython));
592 }(IPython));
601
593
General Comments 0
You need to be logged in to leave comments. Login now