##// END OF EJS Templates
add ctrlKey.which to utils...
Matthias BUSSONNIER -
Show More
@@ -1,681 +1,681
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 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 var key = IPython.utils.keycodes;
16 var key = IPython.utils.keycodes;
17
17
18 var CodeCell = function (notebook) {
18 var CodeCell = function (notebook) {
19 this.code_mirror = null;
19 this.code_mirror = null;
20 this.input_prompt_number = null;
20 this.input_prompt_number = null;
21 this.completion_cursor = null;
21 this.completion_cursor = null;
22 this.outputs = [];
22 this.outputs = [];
23 this.collapsed = false;
23 this.collapsed = false;
24 this.tooltip_timeout = null;
24 this.tooltip_timeout = null;
25 this.clear_out_timeout = null;
25 this.clear_out_timeout = null;
26 IPython.Cell.apply(this, arguments);
26 IPython.Cell.apply(this, arguments);
27 };
27 };
28
28
29
29
30 CodeCell.prototype = new IPython.Cell();
30 CodeCell.prototype = new IPython.Cell();
31
31
32
32
33 CodeCell.prototype.create_element = function () {
33 CodeCell.prototype.create_element = function () {
34 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell vbox');
34 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell vbox');
35 cell.attr('tabindex','2');
35 cell.attr('tabindex','2');
36 var input = $('<div></div>').addClass('input hbox');
36 var input = $('<div></div>').addClass('input hbox');
37 input.append($('<div/>').addClass('prompt input_prompt'));
37 input.append($('<div/>').addClass('prompt input_prompt'));
38 var input_area = $('<div/>').addClass('input_area box-flex1');
38 var input_area = $('<div/>').addClass('input_area box-flex1');
39 this.code_mirror = CodeMirror(input_area.get(0), {
39 this.code_mirror = CodeMirror(input_area.get(0), {
40 indentUnit : 4,
40 indentUnit : 4,
41 mode: 'python',
41 mode: 'python',
42 theme: 'ipython',
42 theme: 'ipython',
43 readOnly: this.read_only,
43 readOnly: this.read_only,
44 onKeyEvent: $.proxy(this.handle_codemirror_keyevent,this)
44 onKeyEvent: $.proxy(this.handle_codemirror_keyevent,this)
45 });
45 });
46 input.append(input_area);
46 input.append(input_area);
47 var output = $('<div></div>').addClass('output vbox');
47 var output = $('<div></div>').addClass('output vbox');
48 cell.append(input).append(output);
48 cell.append(input).append(output);
49 this.element = cell;
49 this.element = cell;
50 this.collapse();
50 this.collapse();
51
51
52 // construct a completer
52 // construct a completer
53 // And give it the function to call to get the completion list
53 // And give it the function to call to get the completion list
54 var that = this;
54 var that = this;
55 this.completer = new IPython.Completer(this.code_mirror,function(callback){that.requestCompletion(callback)});
55 this.completer = new IPython.Completer(this.code_mirror,function(callback){that.requestCompletion(callback)});
56 };
56 };
57
57
58 //TODO, try to diminish the number of parameters.
58 //TODO, try to diminish the number of parameters.
59 CodeCell.prototype.request_tooltip_after_time = function (pre_cursor,time){
59 CodeCell.prototype.request_tooltip_after_time = function (pre_cursor,time){
60 var that = this;
60 var that = this;
61 if (pre_cursor === "" || pre_cursor === "(" ) {
61 if (pre_cursor === "" || pre_cursor === "(" ) {
62 // don't do anything if line beggin with '(' or is empty
62 // don't do anything if line beggin with '(' or is empty
63 } else {
63 } else {
64 // Will set a timer to request tooltip in `time`
64 // Will set a timer to request tooltip in `time`
65 that.tooltip_timeout = setTimeout(function(){
65 that.tooltip_timeout = setTimeout(function(){
66 IPython.notebook.request_tool_tip(that, pre_cursor)
66 IPython.notebook.request_tool_tip(that, pre_cursor)
67 },time);
67 },time);
68 }
68 }
69 };
69 };
70
70
71 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
71 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
72 // This method gets called in CodeMirror's onKeyDown/onKeyPress
72 // This method gets called in CodeMirror's onKeyDown/onKeyPress
73 // handlers and is used to provide custom key handling. Its return
73 // handlers and is used to provide custom key handling. Its return
74 // value is used to determine if CodeMirror should ignore the event:
74 // value is used to determine if CodeMirror should ignore the event:
75 // true = ignore, false = don't ignore.
75 // true = ignore, false = don't ignore.
76
76
77 if (this.read_only){
77 if (this.read_only){
78 return false;
78 return false;
79 }
79 }
80
80
81 // note that we are comparing and setting the time to wait at each key press.
81 // note that we are comparing and setting the time to wait at each key press.
82 // a better wqy might be to generate a new function on each time change and
82 // a better wqy might be to generate a new function on each time change and
83 // assign it to CodeCell.prototype.request_tooltip_after_time
83 // assign it to CodeCell.prototype.request_tooltip_after_time
84 var tooltip_wait_time = this.notebook.time_before_tooltip;
84 var tooltip_wait_time = this.notebook.time_before_tooltip;
85 var tooltip_on_tab = this.notebook.tooltip_on_tab;
85 var tooltip_on_tab = this.notebook.tooltip_on_tab;
86 var that = this;
86 var that = this;
87 // whatever key is pressed, first, cancel the tooltip request before
87 // whatever key is pressed, first, cancel the tooltip request before
88 // they are sent, and remove tooltip if any
88 // they are sent, and remove tooltip if any
89 if(event.type === 'keydown' ) {
89 if(event.type === 'keydown' ) {
90 that.remove_and_cancel_tooltip();
90 that.remove_and_cancel_tooltip();
91 };
91 };
92
92
93
93
94 if (event.keyCode === 13 && (event.shiftKey || event.ctrlKey)) {
94 if (event.keyCode === key.enter && (event.shiftKey || event.ctrlKey)) {
95 // Always ignore shift-enter in CodeMirror as we handle it.
95 // Always ignore shift-enter in CodeMirror as we handle it.
96 return true;
96 return true;
97 } else if (event.which === 40 && event.type === 'keypress' && tooltip_wait_time >= 0) {
97 } else if (event.which === 40 && event.type === 'keypress' && tooltip_wait_time >= 0) {
98 // triger aon keypress (!) otherwise inconsistent event.which depending on plateform
98 // triger aon keypress (!) otherwise inconsistent event.which depending on plateform
99 // browser and keyboard layout !
99 // browser and keyboard layout !
100 // Pressing '(' , request tooltip, don't forget to reappend it
100 // Pressing '(' , request tooltip, don't forget to reappend it
101 var cursor = editor.getCursor();
101 var cursor = editor.getCursor();
102 var pre_cursor = editor.getRange({line:cursor.line,ch:0},cursor).trim()+'(';
102 var pre_cursor = editor.getRange({line:cursor.line,ch:0},cursor).trim()+'(';
103 that.request_tooltip_after_time(pre_cursor,tooltip_wait_time);
103 that.request_tooltip_after_time(pre_cursor,tooltip_wait_time);
104 } else if (event.which === key.upArrow) {
104 } else if (event.which === key.upArrow) {
105 // If we are not at the top, let CM handle the up arrow and
105 // If we are not at the top, let CM handle the up arrow and
106 // prevent the global keydown handler from handling it.
106 // prevent the global keydown handler from handling it.
107 if (!that.at_top()) {
107 if (!that.at_top()) {
108 event.stop();
108 event.stop();
109 return false;
109 return false;
110 } else {
110 } else {
111 return true;
111 return true;
112 };
112 };
113 } else if (event.which === key.downArrow) {
113 } else if (event.which === key.downArrow) {
114 // If we are not at the bottom, let CM handle the down arrow and
114 // If we are not at the bottom, let CM handle the down arrow and
115 // prevent the global keydown handler from handling it.
115 // prevent the global keydown handler from handling it.
116 if (!that.at_bottom()) {
116 if (!that.at_bottom()) {
117 event.stop();
117 event.stop();
118 return false;
118 return false;
119 } else {
119 } else {
120 return true;
120 return true;
121 };
121 };
122 } else if (event.keyCode === key.tab && event.type == 'keydown') {
122 } else if (event.keyCode === key.tab && event.type == 'keydown') {
123 // Tab completion.
123 // Tab completion.
124 var cur = editor.getCursor();
124 var cur = editor.getCursor();
125 //Do not trim here because of tooltip
125 //Do not trim here because of tooltip
126 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
126 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
127 if (pre_cursor.trim() === "") {
127 if (pre_cursor.trim() === "") {
128 // Don't autocomplete if the part of the line before the cursor
128 // Don't autocomplete if the part of the line before the cursor
129 // is empty. In this case, let CodeMirror handle indentation.
129 // is empty. In this case, let CodeMirror handle indentation.
130 return false;
130 return false;
131 } else if ((pre_cursor.substr(-1) === "("|| pre_cursor.substr(-1) === " ") && tooltip_on_tab ) {
131 } else if ((pre_cursor.substr(-1) === "("|| pre_cursor.substr(-1) === " ") && tooltip_on_tab ) {
132 that.request_tooltip_after_time(pre_cursor,0);
132 that.request_tooltip_after_time(pre_cursor,0);
133 // Prevent the event from bubbling up.
133 // Prevent the event from bubbling up.
134 event.stop();
134 event.stop();
135 // Prevent CodeMirror from handling the tab.
135 // Prevent CodeMirror from handling the tab.
136 return true;
136 return true;
137 } else {
137 } else {
138 event.stop();
138 event.stop();
139 this.completer.startCompletion();
139 this.completer.startCompletion();
140 return true;
140 return true;
141 };
141 };
142 } else if (event.keyCode === key.backspace && event.type == 'keydown') {
142 } else if (event.keyCode === key.backspace && event.type == 'keydown') {
143 // If backspace and the line ends with 4 spaces, remove them.
143 // If backspace and the line ends with 4 spaces, remove them.
144 var cur = editor.getCursor();
144 var cur = editor.getCursor();
145 var line = editor.getLine(cur.line);
145 var line = editor.getLine(cur.line);
146 var ending = line.slice(-4);
146 var ending = line.slice(-4);
147 if (ending === ' ') {
147 if (ending === ' ') {
148 editor.replaceRange('',
148 editor.replaceRange('',
149 {line: cur.line, ch: cur.ch-4},
149 {line: cur.line, ch: cur.ch-4},
150 {line: cur.line, ch: cur.ch}
150 {line: cur.line, ch: cur.ch}
151 );
151 );
152 event.stop();
152 event.stop();
153 return true;
153 return true;
154 } else {
154 } else {
155 return false;
155 return false;
156 };
156 };
157 } else {
157 } else {
158 // keypress/keyup also trigger on TAB press, and we don't want to
158 // keypress/keyup also trigger on TAB press, and we don't want to
159 // use those to disable tab completion.
159 // use those to disable tab completion.
160 return false;
160 return false;
161 };
161 };
162 return false;
162 return false;
163 };
163 };
164
164
165 CodeCell.prototype.remove_and_cancel_tooltip = function() {
165 CodeCell.prototype.remove_and_cancel_tooltip = function() {
166 // note that we don't handle closing directly inside the calltip
166 // note that we don't handle closing directly inside the calltip
167 // as in the completer, because it is not focusable, so won't
167 // as in the completer, because it is not focusable, so won't
168 // get the event.
168 // get the event.
169 if (this.tooltip_timeout != null){
169 if (this.tooltip_timeout != null){
170 clearTimeout(this.tooltip_timeout);
170 clearTimeout(this.tooltip_timeout);
171 $('#tooltip').remove();
171 $('#tooltip').remove();
172 this.tooltip_timeout = null;
172 this.tooltip_timeout = null;
173 }
173 }
174 }
174 }
175
175
176 CodeCell.prototype.finish_tooltip = function (reply) {
176 CodeCell.prototype.finish_tooltip = function (reply) {
177 // Extract call tip data; the priority is call, init, main.
177 // Extract call tip data; the priority is call, init, main.
178 defstring = reply.call_def;
178 defstring = reply.call_def;
179 if (defstring == null) { defstring = reply.init_definition; }
179 if (defstring == null) { defstring = reply.init_definition; }
180 if (defstring == null) { defstring = reply.definition; }
180 if (defstring == null) { defstring = reply.definition; }
181
181
182 docstring = reply.call_docstring;
182 docstring = reply.call_docstring;
183 if (docstring == null) { docstring = reply.init_docstring; }
183 if (docstring == null) { docstring = reply.init_docstring; }
184 if (docstring == null) { docstring = reply.docstring; }
184 if (docstring == null) { docstring = reply.docstring; }
185 if (docstring == null) { docstring = "<empty docstring>"; }
185 if (docstring == null) { docstring = "<empty docstring>"; }
186
186
187 name=reply.name;
187 name=reply.name;
188
188
189 var that = this;
189 var that = this;
190 var tooltip = $('<div/>').attr('id', 'tooltip').addClass('tooltip');
190 var tooltip = $('<div/>').attr('id', 'tooltip').addClass('tooltip');
191 // remove to have the tooltip not Limited in X and Y
191 // remove to have the tooltip not Limited in X and Y
192 tooltip.addClass('smalltooltip');
192 tooltip.addClass('smalltooltip');
193 var pre=$('<pre/>').html(utils.fixConsole(docstring));
193 var pre=$('<pre/>').html(utils.fixConsole(docstring));
194 var expandlink=$('<a/>').attr('href',"#");
194 var expandlink=$('<a/>').attr('href',"#");
195 expandlink.addClass("ui-corner-all"); //rounded corner
195 expandlink.addClass("ui-corner-all"); //rounded corner
196 expandlink.attr('role',"button");
196 expandlink.attr('role',"button");
197 //expandlink.addClass('ui-button');
197 //expandlink.addClass('ui-button');
198 //expandlink.addClass('ui-state-default');
198 //expandlink.addClass('ui-state-default');
199 var expandspan=$('<span/>').text('Expand');
199 var expandspan=$('<span/>').text('Expand');
200 expandspan.addClass('ui-icon');
200 expandspan.addClass('ui-icon');
201 expandspan.addClass('ui-icon-plus');
201 expandspan.addClass('ui-icon-plus');
202 expandlink.append(expandspan);
202 expandlink.append(expandspan);
203 expandlink.attr('id','expanbutton');
203 expandlink.attr('id','expanbutton');
204 expandlink.click(function(){
204 expandlink.click(function(){
205 tooltip.removeClass('smalltooltip');
205 tooltip.removeClass('smalltooltip');
206 tooltip.addClass('bigtooltip');
206 tooltip.addClass('bigtooltip');
207 $('#expanbutton').remove();
207 $('#expanbutton').remove();
208 setTimeout(function(){that.code_mirror.focus();}, 50);
208 setTimeout(function(){that.code_mirror.focus();}, 50);
209 });
209 });
210 var morelink=$('<a/>').attr('href',"#");
210 var morelink=$('<a/>').attr('href',"#");
211 morelink.attr('role',"button");
211 morelink.attr('role',"button");
212 morelink.addClass('ui-button');
212 morelink.addClass('ui-button');
213 //morelink.addClass("ui-corner-all"); //rounded corner
213 //morelink.addClass("ui-corner-all"); //rounded corner
214 //morelink.addClass('ui-state-default');
214 //morelink.addClass('ui-state-default');
215 var morespan=$('<span/>').text('Open in Pager');
215 var morespan=$('<span/>').text('Open in Pager');
216 morespan.addClass('ui-icon');
216 morespan.addClass('ui-icon');
217 morespan.addClass('ui-icon-arrowstop-l-n');
217 morespan.addClass('ui-icon-arrowstop-l-n');
218 morelink.append(morespan);
218 morelink.append(morespan);
219 morelink.click(function(){
219 morelink.click(function(){
220 var msg_id = IPython.notebook.kernel.execute(name+"?");
220 var msg_id = IPython.notebook.kernel.execute(name+"?");
221 IPython.notebook.msg_cell_map[msg_id] = IPython.notebook.get_selected_cell().cell_id;
221 IPython.notebook.msg_cell_map[msg_id] = IPython.notebook.get_selected_cell().cell_id;
222 that.remove_and_cancel_tooltip();
222 that.remove_and_cancel_tooltip();
223 setTimeout(function(){that.code_mirror.focus();}, 50);
223 setTimeout(function(){that.code_mirror.focus();}, 50);
224 });
224 });
225
225
226 var closelink=$('<a/>').attr('href',"#");
226 var closelink=$('<a/>').attr('href',"#");
227 closelink.attr('role',"button");
227 closelink.attr('role',"button");
228 closelink.addClass('ui-button');
228 closelink.addClass('ui-button');
229 //closelink.addClass("ui-corner-all"); //rounded corner
229 //closelink.addClass("ui-corner-all"); //rounded corner
230 //closelink.adClass('ui-state-default'); // grey background and blue cross
230 //closelink.adClass('ui-state-default'); // grey background and blue cross
231 var closespan=$('<span/>').text('Close');
231 var closespan=$('<span/>').text('Close');
232 closespan.addClass('ui-icon');
232 closespan.addClass('ui-icon');
233 closespan.addClass('ui-icon-close');
233 closespan.addClass('ui-icon-close');
234 closelink.append(closespan);
234 closelink.append(closespan);
235 closelink.click(function(){
235 closelink.click(function(){
236 that.remove_and_cancel_tooltip();
236 that.remove_and_cancel_tooltip();
237 setTimeout(function(){that.code_mirror.focus();}, 50);
237 setTimeout(function(){that.code_mirror.focus();}, 50);
238 });
238 });
239 //construct the tooltip
239 //construct the tooltip
240 tooltip.append(closelink);
240 tooltip.append(closelink);
241 tooltip.append(expandlink);
241 tooltip.append(expandlink);
242 tooltip.append(morelink);
242 tooltip.append(morelink);
243 if(defstring){
243 if(defstring){
244 defstring_html = $('<pre/>').html(utils.fixConsole(defstring));
244 defstring_html = $('<pre/>').html(utils.fixConsole(defstring));
245 tooltip.append(defstring_html);
245 tooltip.append(defstring_html);
246 }
246 }
247 tooltip.append(pre);
247 tooltip.append(pre);
248 var pos = this.code_mirror.cursorCoords();
248 var pos = this.code_mirror.cursorCoords();
249 tooltip.css('left',pos.x+'px');
249 tooltip.css('left',pos.x+'px');
250 tooltip.css('top',pos.yBot+'px');
250 tooltip.css('top',pos.yBot+'px');
251 $('body').append(tooltip);
251 $('body').append(tooltip);
252
252
253 // issues with cross-closing if multiple tooltip in less than 5sec
253 // issues with cross-closing if multiple tooltip in less than 5sec
254 // keep it comented for now
254 // keep it comented for now
255 // setTimeout(that.remove_and_cancel_tooltip, 5000);
255 // setTimeout(that.remove_and_cancel_tooltip, 5000);
256 };
256 };
257
257
258 // As you type completer
258 // As you type completer
259 // this should be called by the completer, that in return will
259 // this should be called by the completer, that in return will
260 // be reclled by finish_completing
260 // be reclled by finish_completing
261 CodeCell.prototype.requestCompletion= function(callback)
261 CodeCell.prototype.requestCompletion= function(callback)
262 {
262 {
263 this._compcallback = callback;
263 this._compcallback = callback;
264 var cur = this.code_mirror.getCursor();
264 var cur = this.code_mirror.getCursor();
265 var pre_cursor = this.code_mirror.getRange({line:cur.line,ch:0},cur);
265 var pre_cursor = this.code_mirror.getRange({line:cur.line,ch:0},cur);
266 pre_cursor.trim();
266 pre_cursor.trim();
267 // Autocomplete the current line.
267 // Autocomplete the current line.
268 var line = this.code_mirror.getLine(cur.line);
268 var line = this.code_mirror.getLine(cur.line);
269 // one could fork here and directly call finish completing
269 // one could fork here and directly call finish completing
270 // if kernel is busy
270 // if kernel is busy
271 IPython.notebook.complete_cell(this, line, cur.ch);
271 IPython.notebook.complete_cell(this, line, cur.ch);
272 }
272 }
273
273
274 // called when completion came back from the kernel. this will inspect the
274 // called when completion came back from the kernel. this will inspect the
275 // curent cell for (more) completion merge the resuults with the ones
275 // curent cell for (more) completion merge the resuults with the ones
276 // comming from the kernel and forward it to the completer
276 // comming from the kernel and forward it to the completer
277 CodeCell.prototype.finish_completing = function (matched_text, matches) {
277 CodeCell.prototype.finish_completing = function (matched_text, matches) {
278 // let's build a function that wrap all that stuff into what is needed for the
278 // let's build a function that wrap all that stuff into what is needed for the
279 // new completer:
279 // new completer:
280 //
280 //
281 var cur = this.code_mirror.getCursor();
281 var cur = this.code_mirror.getCursor();
282 var res = CodeMirror.contextHint(this.code_mirror);
282 var res = CodeMirror.contextHint(this.code_mirror);
283
283
284 // append the introspection result, in order, at
284 // append the introspection result, in order, at
285 // at the beginning of the table and compute the replacement rance
285 // at the beginning of the table and compute the replacement rance
286 // from current cursor positon and matched_text length.
286 // from current cursor positon and matched_text length.
287 for(var i= matches.length-1; i>=0 ;--i)
287 for(var i= matches.length-1; i>=0 ;--i)
288 {
288 {
289 res.unshift(
289 res.unshift(
290 {
290 {
291 str : matches[i],
291 str : matches[i],
292 type : "introspection",
292 type : "introspection",
293 from : {line: cur.line, ch: cur.ch-matched_text.length},
293 from : {line: cur.line, ch: cur.ch-matched_text.length},
294 to : {line: cur.line, ch: cur.ch}
294 to : {line: cur.line, ch: cur.ch}
295 }
295 }
296 )
296 )
297 }
297 }
298 this._compcallback(res);
298 this._compcallback(res);
299 };
299 };
300
300
301
301
302 CodeCell.prototype.select = function () {
302 CodeCell.prototype.select = function () {
303 IPython.Cell.prototype.select.apply(this);
303 IPython.Cell.prototype.select.apply(this);
304 this.code_mirror.refresh();
304 this.code_mirror.refresh();
305 this.code_mirror.focus();
305 this.code_mirror.focus();
306 // We used to need an additional refresh() after the focus, but
306 // We used to need an additional refresh() after the focus, but
307 // it appears that this has been fixed in CM. This bug would show
307 // it appears that this has been fixed in CM. This bug would show
308 // up on FF when a newly loaded markdown cell was edited.
308 // up on FF when a newly loaded markdown cell was edited.
309 };
309 };
310
310
311
311
312 CodeCell.prototype.select_all = function () {
312 CodeCell.prototype.select_all = function () {
313 var start = {line: 0, ch: 0};
313 var start = {line: 0, ch: 0};
314 var nlines = this.code_mirror.lineCount();
314 var nlines = this.code_mirror.lineCount();
315 var last_line = this.code_mirror.getLine(nlines-1);
315 var last_line = this.code_mirror.getLine(nlines-1);
316 var end = {line: nlines-1, ch: last_line.length};
316 var end = {line: nlines-1, ch: last_line.length};
317 this.code_mirror.setSelection(start, end);
317 this.code_mirror.setSelection(start, end);
318 };
318 };
319
319
320
320
321 CodeCell.prototype.append_output = function (json, dynamic) {
321 CodeCell.prototype.append_output = function (json, dynamic) {
322 // If dynamic is true, javascript output will be eval'd.
322 // If dynamic is true, javascript output will be eval'd.
323 this.expand();
323 this.expand();
324 this.flush_clear_timeout();
324 this.flush_clear_timeout();
325 if (json.output_type === 'pyout') {
325 if (json.output_type === 'pyout') {
326 this.append_pyout(json, dynamic);
326 this.append_pyout(json, dynamic);
327 } else if (json.output_type === 'pyerr') {
327 } else if (json.output_type === 'pyerr') {
328 this.append_pyerr(json);
328 this.append_pyerr(json);
329 } else if (json.output_type === 'display_data') {
329 } else if (json.output_type === 'display_data') {
330 this.append_display_data(json, dynamic);
330 this.append_display_data(json, dynamic);
331 } else if (json.output_type === 'stream') {
331 } else if (json.output_type === 'stream') {
332 this.append_stream(json);
332 this.append_stream(json);
333 };
333 };
334 this.outputs.push(json);
334 this.outputs.push(json);
335 };
335 };
336
336
337
337
338 CodeCell.prototype.create_output_area = function () {
338 CodeCell.prototype.create_output_area = function () {
339 var oa = $("<div/>").addClass("hbox output_area");
339 var oa = $("<div/>").addClass("hbox output_area");
340 oa.append($('<div/>').addClass('prompt'));
340 oa.append($('<div/>').addClass('prompt'));
341 return oa;
341 return oa;
342 };
342 };
343
343
344
344
345 CodeCell.prototype.append_pyout = function (json, dynamic) {
345 CodeCell.prototype.append_pyout = function (json, dynamic) {
346 var n = json.prompt_number || ' ';
346 var n = json.prompt_number || ' ';
347 var toinsert = this.create_output_area();
347 var toinsert = this.create_output_area();
348 toinsert.find('div.prompt').addClass('output_prompt').html('Out[' + n + ']:');
348 toinsert.find('div.prompt').addClass('output_prompt').html('Out[' + n + ']:');
349 this.append_mime_type(json, toinsert, dynamic);
349 this.append_mime_type(json, toinsert, dynamic);
350 this.element.find('div.output').append(toinsert);
350 this.element.find('div.output').append(toinsert);
351 // If we just output latex, typeset it.
351 // If we just output latex, typeset it.
352 if ((json.latex !== undefined) || (json.html !== undefined)) {
352 if ((json.latex !== undefined) || (json.html !== undefined)) {
353 this.typeset();
353 this.typeset();
354 };
354 };
355 };
355 };
356
356
357
357
358 CodeCell.prototype.append_pyerr = function (json) {
358 CodeCell.prototype.append_pyerr = function (json) {
359 var tb = json.traceback;
359 var tb = json.traceback;
360 if (tb !== undefined && tb.length > 0) {
360 if (tb !== undefined && tb.length > 0) {
361 var s = '';
361 var s = '';
362 var len = tb.length;
362 var len = tb.length;
363 for (var i=0; i<len; i++) {
363 for (var i=0; i<len; i++) {
364 s = s + tb[i] + '\n';
364 s = s + tb[i] + '\n';
365 }
365 }
366 s = s + '\n';
366 s = s + '\n';
367 var toinsert = this.create_output_area();
367 var toinsert = this.create_output_area();
368 this.append_text(s, toinsert);
368 this.append_text(s, toinsert);
369 this.element.find('div.output').append(toinsert);
369 this.element.find('div.output').append(toinsert);
370 };
370 };
371 };
371 };
372
372
373
373
374 CodeCell.prototype.append_stream = function (json) {
374 CodeCell.prototype.append_stream = function (json) {
375 // temporary fix: if stream undefined (json file written prior to this patch),
375 // temporary fix: if stream undefined (json file written prior to this patch),
376 // default to most likely stdout:
376 // default to most likely stdout:
377 if (json.stream == undefined){
377 if (json.stream == undefined){
378 json.stream = 'stdout';
378 json.stream = 'stdout';
379 }
379 }
380 if (!utils.fixConsole(json.text)){
380 if (!utils.fixConsole(json.text)){
381 // fixConsole gives nothing (empty string, \r, etc.)
381 // fixConsole gives nothing (empty string, \r, etc.)
382 // so don't append any elements, which might add undesirable space
382 // so don't append any elements, which might add undesirable space
383 return;
383 return;
384 }
384 }
385 var subclass = "output_"+json.stream;
385 var subclass = "output_"+json.stream;
386 if (this.outputs.length > 0){
386 if (this.outputs.length > 0){
387 // have at least one output to consider
387 // have at least one output to consider
388 var last = this.outputs[this.outputs.length-1];
388 var last = this.outputs[this.outputs.length-1];
389 if (last.output_type == 'stream' && json.stream == last.stream){
389 if (last.output_type == 'stream' && json.stream == last.stream){
390 // latest output was in the same stream,
390 // latest output was in the same stream,
391 // so append directly into its pre tag
391 // so append directly into its pre tag
392 // escape ANSI & HTML specials:
392 // escape ANSI & HTML specials:
393 var text = utils.fixConsole(json.text);
393 var text = utils.fixConsole(json.text);
394 this.element.find('div.'+subclass).last().find('pre').append(text);
394 this.element.find('div.'+subclass).last().find('pre').append(text);
395 return;
395 return;
396 }
396 }
397 }
397 }
398
398
399 // If we got here, attach a new div
399 // If we got here, attach a new div
400 var toinsert = this.create_output_area();
400 var toinsert = this.create_output_area();
401 this.append_text(json.text, toinsert, "output_stream "+subclass);
401 this.append_text(json.text, toinsert, "output_stream "+subclass);
402 this.element.find('div.output').append(toinsert);
402 this.element.find('div.output').append(toinsert);
403 };
403 };
404
404
405
405
406 CodeCell.prototype.append_display_data = function (json, dynamic) {
406 CodeCell.prototype.append_display_data = function (json, dynamic) {
407 var toinsert = this.create_output_area();
407 var toinsert = this.create_output_area();
408 this.append_mime_type(json, toinsert, dynamic);
408 this.append_mime_type(json, toinsert, dynamic);
409 this.element.find('div.output').append(toinsert);
409 this.element.find('div.output').append(toinsert);
410 // If we just output latex, typeset it.
410 // If we just output latex, typeset it.
411 if ( (json.latex !== undefined) || (json.html !== undefined) ) {
411 if ( (json.latex !== undefined) || (json.html !== undefined) ) {
412 this.typeset();
412 this.typeset();
413 };
413 };
414 };
414 };
415
415
416
416
417 CodeCell.prototype.append_mime_type = function (json, element, dynamic) {
417 CodeCell.prototype.append_mime_type = function (json, element, dynamic) {
418 if (json.javascript !== undefined && dynamic) {
418 if (json.javascript !== undefined && dynamic) {
419 this.append_javascript(json.javascript, element, dynamic);
419 this.append_javascript(json.javascript, element, dynamic);
420 } else if (json.html !== undefined) {
420 } else if (json.html !== undefined) {
421 this.append_html(json.html, element);
421 this.append_html(json.html, element);
422 } else if (json.latex !== undefined) {
422 } else if (json.latex !== undefined) {
423 this.append_latex(json.latex, element);
423 this.append_latex(json.latex, element);
424 } else if (json.svg !== undefined) {
424 } else if (json.svg !== undefined) {
425 this.append_svg(json.svg, element);
425 this.append_svg(json.svg, element);
426 } else if (json.png !== undefined) {
426 } else if (json.png !== undefined) {
427 this.append_png(json.png, element);
427 this.append_png(json.png, element);
428 } else if (json.jpeg !== undefined) {
428 } else if (json.jpeg !== undefined) {
429 this.append_jpeg(json.jpeg, element);
429 this.append_jpeg(json.jpeg, element);
430 } else if (json.text !== undefined) {
430 } else if (json.text !== undefined) {
431 this.append_text(json.text, element);
431 this.append_text(json.text, element);
432 };
432 };
433 };
433 };
434
434
435
435
436 CodeCell.prototype.append_html = function (html, element) {
436 CodeCell.prototype.append_html = function (html, element) {
437 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_html rendered_html");
437 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_html rendered_html");
438 toinsert.append(html);
438 toinsert.append(html);
439 element.append(toinsert);
439 element.append(toinsert);
440 };
440 };
441
441
442
442
443 CodeCell.prototype.append_javascript = function (js, container) {
443 CodeCell.prototype.append_javascript = function (js, container) {
444 // We just eval the JS code, element appears in the local scope.
444 // We just eval the JS code, element appears in the local scope.
445 var element = $("<div/>").addClass("box_flex1 output_subarea");
445 var element = $("<div/>").addClass("box_flex1 output_subarea");
446 container.append(element);
446 container.append(element);
447 // Div for js shouldn't be drawn, as it will add empty height to the area.
447 // Div for js shouldn't be drawn, as it will add empty height to the area.
448 container.hide();
448 container.hide();
449 // If the Javascript appends content to `element` that should be drawn, then
449 // If the Javascript appends content to `element` that should be drawn, then
450 // it must also call `container.show()`.
450 // it must also call `container.show()`.
451 eval(js);
451 eval(js);
452 }
452 }
453
453
454
454
455 CodeCell.prototype.append_text = function (data, element, extra_class) {
455 CodeCell.prototype.append_text = function (data, element, extra_class) {
456 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_text");
456 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_text");
457 // escape ANSI & HTML specials in plaintext:
457 // escape ANSI & HTML specials in plaintext:
458 data = utils.fixConsole(data);
458 data = utils.fixConsole(data);
459 if (extra_class){
459 if (extra_class){
460 toinsert.addClass(extra_class);
460 toinsert.addClass(extra_class);
461 }
461 }
462 toinsert.append($("<pre/>").html(data));
462 toinsert.append($("<pre/>").html(data));
463 element.append(toinsert);
463 element.append(toinsert);
464 };
464 };
465
465
466
466
467 CodeCell.prototype.append_svg = function (svg, element) {
467 CodeCell.prototype.append_svg = function (svg, element) {
468 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_svg");
468 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_svg");
469 toinsert.append(svg);
469 toinsert.append(svg);
470 element.append(toinsert);
470 element.append(toinsert);
471 };
471 };
472
472
473
473
474 CodeCell.prototype.append_png = function (png, element) {
474 CodeCell.prototype.append_png = function (png, element) {
475 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_png");
475 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_png");
476 toinsert.append($("<img/>").attr('src','data:image/png;base64,'+png));
476 toinsert.append($("<img/>").attr('src','data:image/png;base64,'+png));
477 element.append(toinsert);
477 element.append(toinsert);
478 };
478 };
479
479
480
480
481 CodeCell.prototype.append_jpeg = function (jpeg, element) {
481 CodeCell.prototype.append_jpeg = function (jpeg, element) {
482 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_jpeg");
482 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_jpeg");
483 toinsert.append($("<img/>").attr('src','data:image/jpeg;base64,'+jpeg));
483 toinsert.append($("<img/>").attr('src','data:image/jpeg;base64,'+jpeg));
484 element.append(toinsert);
484 element.append(toinsert);
485 };
485 };
486
486
487
487
488 CodeCell.prototype.append_latex = function (latex, element) {
488 CodeCell.prototype.append_latex = function (latex, element) {
489 // This method cannot do the typesetting because the latex first has to
489 // This method cannot do the typesetting because the latex first has to
490 // be on the page.
490 // be on the page.
491 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_latex");
491 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_latex");
492 toinsert.append(latex);
492 toinsert.append(latex);
493 element.append(toinsert);
493 element.append(toinsert);
494 };
494 };
495
495
496
496
497 CodeCell.prototype.clear_output = function (stdout, stderr, other) {
497 CodeCell.prototype.clear_output = function (stdout, stderr, other) {
498 var that = this;
498 var that = this;
499 if (this.clear_out_timeout != null){
499 if (this.clear_out_timeout != null){
500 // fire previous pending clear *immediately*
500 // fire previous pending clear *immediately*
501 clearTimeout(this.clear_out_timeout);
501 clearTimeout(this.clear_out_timeout);
502 this.clear_out_timeout = null;
502 this.clear_out_timeout = null;
503 this.clear_output_callback(this._clear_stdout, this._clear_stderr, this._clear_other);
503 this.clear_output_callback(this._clear_stdout, this._clear_stderr, this._clear_other);
504 }
504 }
505 // store flags for flushing the timeout
505 // store flags for flushing the timeout
506 this._clear_stdout = stdout;
506 this._clear_stdout = stdout;
507 this._clear_stderr = stderr;
507 this._clear_stderr = stderr;
508 this._clear_other = other;
508 this._clear_other = other;
509 this.clear_out_timeout = setTimeout(function(){
509 this.clear_out_timeout = setTimeout(function(){
510 // really clear timeout only after a short delay
510 // really clear timeout only after a short delay
511 // this reduces flicker in 'clear_output; print' cases
511 // this reduces flicker in 'clear_output; print' cases
512 that.clear_out_timeout = null;
512 that.clear_out_timeout = null;
513 that._clear_stdout = that._clear_stderr = that._clear_other = null;
513 that._clear_stdout = that._clear_stderr = that._clear_other = null;
514 that.clear_output_callback(stdout, stderr, other);
514 that.clear_output_callback(stdout, stderr, other);
515 }, 500
515 }, 500
516 );
516 );
517 };
517 };
518
518
519 CodeCell.prototype.clear_output_callback = function (stdout, stderr, other) {
519 CodeCell.prototype.clear_output_callback = function (stdout, stderr, other) {
520 var output_div = this.element.find("div.output");
520 var output_div = this.element.find("div.output");
521
521
522 if (stdout && stderr && other){
522 if (stdout && stderr && other){
523 // clear all, no need for logic
523 // clear all, no need for logic
524 output_div.html("");
524 output_div.html("");
525 this.outputs = [];
525 this.outputs = [];
526 return;
526 return;
527 }
527 }
528 // remove html output
528 // remove html output
529 // each output_subarea that has an identifying class is in an output_area
529 // each output_subarea that has an identifying class is in an output_area
530 // which is the element to be removed.
530 // which is the element to be removed.
531 if (stdout){
531 if (stdout){
532 output_div.find("div.output_stdout").parent().remove();
532 output_div.find("div.output_stdout").parent().remove();
533 }
533 }
534 if (stderr){
534 if (stderr){
535 output_div.find("div.output_stderr").parent().remove();
535 output_div.find("div.output_stderr").parent().remove();
536 }
536 }
537 if (other){
537 if (other){
538 output_div.find("div.output_subarea").not("div.output_stderr").not("div.output_stdout").parent().remove();
538 output_div.find("div.output_subarea").not("div.output_stderr").not("div.output_stdout").parent().remove();
539 }
539 }
540
540
541 // remove cleared outputs from JSON list:
541 // remove cleared outputs from JSON list:
542 for (var i = this.outputs.length - 1; i >= 0; i--){
542 for (var i = this.outputs.length - 1; i >= 0; i--){
543 var out = this.outputs[i];
543 var out = this.outputs[i];
544 var output_type = out.output_type;
544 var output_type = out.output_type;
545 if (output_type == "display_data" && other){
545 if (output_type == "display_data" && other){
546 this.outputs.splice(i,1);
546 this.outputs.splice(i,1);
547 }else if (output_type == "stream"){
547 }else if (output_type == "stream"){
548 if (stdout && out.stream == "stdout"){
548 if (stdout && out.stream == "stdout"){
549 this.outputs.splice(i,1);
549 this.outputs.splice(i,1);
550 }else if (stderr && out.stream == "stderr"){
550 }else if (stderr && out.stream == "stderr"){
551 this.outputs.splice(i,1);
551 this.outputs.splice(i,1);
552 }
552 }
553 }
553 }
554 }
554 }
555 };
555 };
556
556
557
557
558 CodeCell.prototype.clear_input = function () {
558 CodeCell.prototype.clear_input = function () {
559 this.code_mirror.setValue('');
559 this.code_mirror.setValue('');
560 };
560 };
561
561
562 CodeCell.prototype.flush_clear_timeout = function() {
562 CodeCell.prototype.flush_clear_timeout = function() {
563 var output_div = this.element.find('div.output');
563 var output_div = this.element.find('div.output');
564 if (this.clear_out_timeout){
564 if (this.clear_out_timeout){
565 clearTimeout(this.clear_out_timeout);
565 clearTimeout(this.clear_out_timeout);
566 this.clear_out_timeout = null;
566 this.clear_out_timeout = null;
567 this.clear_output_callback(this._clear_stdout, this._clear_stderr, this._clear_other);
567 this.clear_output_callback(this._clear_stdout, this._clear_stderr, this._clear_other);
568 };
568 };
569 }
569 }
570
570
571
571
572 CodeCell.prototype.collapse = function () {
572 CodeCell.prototype.collapse = function () {
573 if (!this.collapsed) {
573 if (!this.collapsed) {
574 this.element.find('div.output').hide();
574 this.element.find('div.output').hide();
575 this.collapsed = true;
575 this.collapsed = true;
576 };
576 };
577 };
577 };
578
578
579
579
580 CodeCell.prototype.expand = function () {
580 CodeCell.prototype.expand = function () {
581 if (this.collapsed) {
581 if (this.collapsed) {
582 this.element.find('div.output').show();
582 this.element.find('div.output').show();
583 this.collapsed = false;
583 this.collapsed = false;
584 };
584 };
585 };
585 };
586
586
587
587
588 CodeCell.prototype.toggle_output = function () {
588 CodeCell.prototype.toggle_output = function () {
589 if (this.collapsed) {
589 if (this.collapsed) {
590 this.expand();
590 this.expand();
591 } else {
591 } else {
592 this.collapse();
592 this.collapse();
593 };
593 };
594 };
594 };
595
595
596 CodeCell.prototype.set_input_prompt = function (number) {
596 CodeCell.prototype.set_input_prompt = function (number) {
597 this.input_prompt_number = number;
597 this.input_prompt_number = number;
598 var ns = number || "&nbsp;";
598 var ns = number || "&nbsp;";
599 this.element.find('div.input_prompt').html('In&nbsp;[' + ns + ']:');
599 this.element.find('div.input_prompt').html('In&nbsp;[' + ns + ']:');
600 };
600 };
601
601
602
602
603 CodeCell.prototype.get_text = function () {
603 CodeCell.prototype.get_text = function () {
604 return this.code_mirror.getValue();
604 return this.code_mirror.getValue();
605 };
605 };
606
606
607
607
608 CodeCell.prototype.set_text = function (code) {
608 CodeCell.prototype.set_text = function (code) {
609 return this.code_mirror.setValue(code);
609 return this.code_mirror.setValue(code);
610 };
610 };
611
611
612
612
613 CodeCell.prototype.at_top = function () {
613 CodeCell.prototype.at_top = function () {
614 var cursor = this.code_mirror.getCursor();
614 var cursor = this.code_mirror.getCursor();
615 if (cursor.line === 0) {
615 if (cursor.line === 0) {
616 return true;
616 return true;
617 } else {
617 } else {
618 return false;
618 return false;
619 }
619 }
620 };
620 };
621
621
622
622
623 CodeCell.prototype.at_bottom = function () {
623 CodeCell.prototype.at_bottom = function () {
624 var cursor = this.code_mirror.getCursor();
624 var cursor = this.code_mirror.getCursor();
625 if (cursor.line === (this.code_mirror.lineCount()-1)) {
625 if (cursor.line === (this.code_mirror.lineCount()-1)) {
626 return true;
626 return true;
627 } else {
627 } else {
628 return false;
628 return false;
629 }
629 }
630 };
630 };
631
631
632
632
633 CodeCell.prototype.fromJSON = function (data) {
633 CodeCell.prototype.fromJSON = function (data) {
634 if (data.cell_type === 'code') {
634 if (data.cell_type === 'code') {
635 if (data.input !== undefined) {
635 if (data.input !== undefined) {
636 this.set_text(data.input);
636 this.set_text(data.input);
637 }
637 }
638 if (data.prompt_number !== undefined) {
638 if (data.prompt_number !== undefined) {
639 this.set_input_prompt(data.prompt_number);
639 this.set_input_prompt(data.prompt_number);
640 } else {
640 } else {
641 this.set_input_prompt();
641 this.set_input_prompt();
642 };
642 };
643 var len = data.outputs.length;
643 var len = data.outputs.length;
644 for (var i=0; i<len; i++) {
644 for (var i=0; i<len; i++) {
645 // append with dynamic=false.
645 // append with dynamic=false.
646 this.append_output(data.outputs[i], false);
646 this.append_output(data.outputs[i], false);
647 };
647 };
648 if (data.collapsed !== undefined) {
648 if (data.collapsed !== undefined) {
649 if (data.collapsed) {
649 if (data.collapsed) {
650 this.collapse();
650 this.collapse();
651 } else {
651 } else {
652 this.expand();
652 this.expand();
653 };
653 };
654 };
654 };
655 };
655 };
656 };
656 };
657
657
658
658
659 CodeCell.prototype.toJSON = function () {
659 CodeCell.prototype.toJSON = function () {
660 var data = {};
660 var data = {};
661 data.input = this.get_text();
661 data.input = this.get_text();
662 data.cell_type = 'code';
662 data.cell_type = 'code';
663 if (this.input_prompt_number) {
663 if (this.input_prompt_number) {
664 data.prompt_number = this.input_prompt_number;
664 data.prompt_number = this.input_prompt_number;
665 };
665 };
666 var outputs = [];
666 var outputs = [];
667 var len = this.outputs.length;
667 var len = this.outputs.length;
668 for (var i=0; i<len; i++) {
668 for (var i=0; i<len; i++) {
669 outputs[i] = this.outputs[i];
669 outputs[i] = this.outputs[i];
670 };
670 };
671 data.outputs = outputs;
671 data.outputs = outputs;
672 data.language = 'python';
672 data.language = 'python';
673 data.collapsed = this.collapsed;
673 data.collapsed = this.collapsed;
674 return data;
674 return data;
675 };
675 };
676
676
677
677
678 IPython.CodeCell = CodeCell;
678 IPython.CodeCell = CodeCell;
679
679
680 return IPython;
680 return IPython;
681 }(IPython));
681 }(IPython));
@@ -1,1361 +1,1362
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 // Notebook
9 // Notebook
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13
13
14 var utils = IPython.utils;
14 var utils = IPython.utils;
15 var key = IPython.utils.keycodes;
15
16
16 var Notebook = function (selector) {
17 var Notebook = function (selector) {
17 this.read_only = IPython.read_only;
18 this.read_only = IPython.read_only;
18 this.element = $(selector);
19 this.element = $(selector);
19 this.element.scroll();
20 this.element.scroll();
20 this.element.data("notebook", this);
21 this.element.data("notebook", this);
21 this.next_prompt_number = 1;
22 this.next_prompt_number = 1;
22 this.kernel = null;
23 this.kernel = null;
23 this.clipboard = null;
24 this.clipboard = null;
24 this.paste_enabled = false;
25 this.paste_enabled = false;
25 this.dirty = false;
26 this.dirty = false;
26 this.msg_cell_map = {};
27 this.msg_cell_map = {};
27 this.metadata = {};
28 this.metadata = {};
28 this.control_key_active = false;
29 this.control_key_active = false;
29 this.notebook_id = null;
30 this.notebook_id = null;
30 this.notebook_name = null;
31 this.notebook_name = null;
31 this.notebook_name_blacklist_re = /[\/\\]/;
32 this.notebook_name_blacklist_re = /[\/\\]/;
32 this.nbformat = 3 // Increment this when changing the nbformat
33 this.nbformat = 3 // Increment this when changing the nbformat
33 this.style();
34 this.style();
34 this.create_elements();
35 this.create_elements();
35 this.bind_events();
36 this.bind_events();
36 this.set_tooltipontab(true);
37 this.set_tooltipontab(true);
37 this.set_smartcompleter(true);
38 this.set_smartcompleter(true);
38 this.set_timebeforetooltip(1200);
39 this.set_timebeforetooltip(1200);
39 };
40 };
40
41
41
42
42 Notebook.prototype.style = function () {
43 Notebook.prototype.style = function () {
43 $('div#notebook').addClass('border-box-sizing');
44 $('div#notebook').addClass('border-box-sizing');
44 };
45 };
45
46
46
47
47 Notebook.prototype.create_elements = function () {
48 Notebook.prototype.create_elements = function () {
48 // We add this end_space div to the end of the notebook div to:
49 // We add this end_space div to the end of the notebook div to:
49 // i) provide a margin between the last cell and the end of the notebook
50 // i) provide a margin between the last cell and the end of the notebook
50 // ii) to prevent the div from scrolling up when the last cell is being
51 // ii) to prevent the div from scrolling up when the last cell is being
51 // edited, but is too low on the page, which browsers will do automatically.
52 // edited, but is too low on the page, which browsers will do automatically.
52 var that = this;
53 var that = this;
53 var end_space = $('<div/>').addClass('end_space').height("30%");
54 var end_space = $('<div/>').addClass('end_space').height("30%");
54 end_space.dblclick(function (e) {
55 end_space.dblclick(function (e) {
55 if (that.read_only) return;
56 if (that.read_only) return;
56 var ncells = that.ncells();
57 var ncells = that.ncells();
57 that.insert_cell_below('code',ncells-1);
58 that.insert_cell_below('code',ncells-1);
58 });
59 });
59 this.element.append(end_space);
60 this.element.append(end_space);
60 $('div#notebook').addClass('border-box-sizing');
61 $('div#notebook').addClass('border-box-sizing');
61 };
62 };
62
63
63
64
64 Notebook.prototype.bind_events = function () {
65 Notebook.prototype.bind_events = function () {
65 var that = this;
66 var that = this;
66 $(document).keydown(function (event) {
67 $(document).keydown(function (event) {
67 // console.log(event);
68 // console.log(event);
68 if (that.read_only) return true;
69 if (that.read_only) return true;
69
70
70 // Save (CTRL+S) or (AppleKey+S)
71 // Save (CTRL+S) or (AppleKey+S)
71 //metaKey = applekey on mac
72 //metaKey = applekey on mac
72 if ((event.ctrlKey || event.metaKey) && event.keyCode==83) {
73 if ((event.ctrlKey || event.metaKey) && event.keyCode==83) {
73 that.save_notebook();
74 that.save_notebook();
74 event.preventDefault();
75 event.preventDefault();
75 return false;
76 return false;
76 } else if (event.which === 27) {
77 } else if (event.which === key.esc) {
77 // Intercept escape at highest level to avoid closing
78 // Intercept escape at highest level to avoid closing
78 // websocket connection with firefox
79 // websocket connection with firefox
79 event.preventDefault();
80 event.preventDefault();
80 }
81 }
81 if (event.which === 38 && !event.shiftKey) {
82 if (event.which === key.upArrow && !event.shiftKey) {
82 var cell = that.get_selected_cell();
83 var cell = that.get_selected_cell();
83 if (cell.at_top()) {
84 if (cell.at_top()) {
84 event.preventDefault();
85 event.preventDefault();
85 that.select_prev();
86 that.select_prev();
86 };
87 };
87 } else if (event.which === 40 && !event.shiftKey) {
88 } else if (event.which === key.downArrow && !event.shiftKey) {
88 var cell = that.get_selected_cell();
89 var cell = that.get_selected_cell();
89 if (cell.at_bottom()) {
90 if (cell.at_bottom()) {
90 event.preventDefault();
91 event.preventDefault();
91 that.select_next();
92 that.select_next();
92 };
93 };
93 } else if (event.which === 13 && event.shiftKey) {
94 } else if (event.which === key.enter && event.shiftKey) {
94 that.execute_selected_cell();
95 that.execute_selected_cell();
95 return false;
96 return false;
96 } else if (event.which === 13 && event.ctrlKey) {
97 } else if (event.which === key.enter && event.ctrlKey) {
97 that.execute_selected_cell({terminal:true});
98 that.execute_selected_cell({terminal:true});
98 return false;
99 return false;
99 } else if (event.which === 77 && event.ctrlKey && that.control_key_active == false) {
100 } else if (event.which === 77 && event.ctrlKey && that.control_key_active == false) {
100 that.control_key_active = true;
101 that.control_key_active = true;
101 return false;
102 return false;
102 } else if (event.which === 88 && that.control_key_active) {
103 } else if (event.which === 88 && that.control_key_active) {
103 // Cut selected cell = x
104 // Cut selected cell = x
104 that.cut_cell();
105 that.cut_cell();
105 that.control_key_active = false;
106 that.control_key_active = false;
106 return false;
107 return false;
107 } else if (event.which === 67 && that.control_key_active) {
108 } else if (event.which === 67 && that.control_key_active) {
108 // Copy selected cell = c
109 // Copy selected cell = c
109 that.copy_cell();
110 that.copy_cell();
110 that.control_key_active = false;
111 that.control_key_active = false;
111 return false;
112 return false;
112 } else if (event.which === 86 && that.control_key_active) {
113 } else if (event.which === 86 && that.control_key_active) {
113 // Paste selected cell = v
114 // Paste selected cell = v
114 that.paste_cell();
115 that.paste_cell();
115 that.control_key_active = false;
116 that.control_key_active = false;
116 return false;
117 return false;
117 } else if (event.which === 68 && that.control_key_active) {
118 } else if (event.which === 68 && that.control_key_active) {
118 // Delete selected cell = d
119 // Delete selected cell = d
119 that.delete_cell();
120 that.delete_cell();
120 that.control_key_active = false;
121 that.control_key_active = false;
121 return false;
122 return false;
122 } else if (event.which === 65 && that.control_key_active) {
123 } else if (event.which === 65 && that.control_key_active) {
123 // Insert code cell above selected = a
124 // Insert code cell above selected = a
124 that.insert_cell_above('code');
125 that.insert_cell_above('code');
125 that.control_key_active = false;
126 that.control_key_active = false;
126 return false;
127 return false;
127 } else if (event.which === 66 && that.control_key_active) {
128 } else if (event.which === 66 && that.control_key_active) {
128 // Insert code cell below selected = b
129 // Insert code cell below selected = b
129 that.insert_cell_below('code');
130 that.insert_cell_below('code');
130 that.control_key_active = false;
131 that.control_key_active = false;
131 return false;
132 return false;
132 } else if (event.which === 89 && that.control_key_active) {
133 } else if (event.which === 89 && that.control_key_active) {
133 // To code = y
134 // To code = y
134 that.to_code();
135 that.to_code();
135 that.control_key_active = false;
136 that.control_key_active = false;
136 return false;
137 return false;
137 } else if (event.which === 77 && that.control_key_active) {
138 } else if (event.which === 77 && that.control_key_active) {
138 // To markdown = m
139 // To markdown = m
139 that.to_markdown();
140 that.to_markdown();
140 that.control_key_active = false;
141 that.control_key_active = false;
141 return false;
142 return false;
142 } else if (event.which === 84 && that.control_key_active) {
143 } else if (event.which === 84 && that.control_key_active) {
143 // To Raw = t
144 // To Raw = t
144 that.to_raw();
145 that.to_raw();
145 that.control_key_active = false;
146 that.control_key_active = false;
146 return false;
147 return false;
147 } else if (event.which === 49 && that.control_key_active) {
148 } else if (event.which === 49 && that.control_key_active) {
148 // To Heading 1 = 1
149 // To Heading 1 = 1
149 that.to_heading(undefined, 1);
150 that.to_heading(undefined, 1);
150 that.control_key_active = false;
151 that.control_key_active = false;
151 return false;
152 return false;
152 } else if (event.which === 50 && that.control_key_active) {
153 } else if (event.which === 50 && that.control_key_active) {
153 // To Heading 2 = 2
154 // To Heading 2 = 2
154 that.to_heading(undefined, 2);
155 that.to_heading(undefined, 2);
155 that.control_key_active = false;
156 that.control_key_active = false;
156 return false;
157 return false;
157 } else if (event.which === 51 && that.control_key_active) {
158 } else if (event.which === 51 && that.control_key_active) {
158 // To Heading 3 = 3
159 // To Heading 3 = 3
159 that.to_heading(undefined, 3);
160 that.to_heading(undefined, 3);
160 that.control_key_active = false;
161 that.control_key_active = false;
161 return false;
162 return false;
162 } else if (event.which === 52 && that.control_key_active) {
163 } else if (event.which === 52 && that.control_key_active) {
163 // To Heading 4 = 4
164 // To Heading 4 = 4
164 that.to_heading(undefined, 4);
165 that.to_heading(undefined, 4);
165 that.control_key_active = false;
166 that.control_key_active = false;
166 return false;
167 return false;
167 } else if (event.which === 53 && that.control_key_active) {
168 } else if (event.which === 53 && that.control_key_active) {
168 // To Heading 5 = 5
169 // To Heading 5 = 5
169 that.to_heading(undefined, 5);
170 that.to_heading(undefined, 5);
170 that.control_key_active = false;
171 that.control_key_active = false;
171 return false;
172 return false;
172 } else if (event.which === 54 && that.control_key_active) {
173 } else if (event.which === 54 && that.control_key_active) {
173 // To Heading 6 = 6
174 // To Heading 6 = 6
174 that.to_heading(undefined, 6);
175 that.to_heading(undefined, 6);
175 that.control_key_active = false;
176 that.control_key_active = false;
176 return false;
177 return false;
177 } else if (event.which === 79 && that.control_key_active) {
178 } else if (event.which === 79 && that.control_key_active) {
178 // Toggle output = o
179 // Toggle output = o
179 that.toggle_output();
180 that.toggle_output();
180 that.control_key_active = false;
181 that.control_key_active = false;
181 return false;
182 return false;
182 } else if (event.which === 83 && that.control_key_active) {
183 } else if (event.which === 83 && that.control_key_active) {
183 // Save notebook = s
184 // Save notebook = s
184 that.save_notebook();
185 that.save_notebook();
185 that.control_key_active = false;
186 that.control_key_active = false;
186 return false;
187 return false;
187 } else if (event.which === 74 && that.control_key_active) {
188 } else if (event.which === 74 && that.control_key_active) {
188 // Move cell down = j
189 // Move cell down = j
189 that.move_cell_down();
190 that.move_cell_down();
190 that.control_key_active = false;
191 that.control_key_active = false;
191 return false;
192 return false;
192 } else if (event.which === 75 && that.control_key_active) {
193 } else if (event.which === 75 && that.control_key_active) {
193 // Move cell up = k
194 // Move cell up = k
194 that.move_cell_up();
195 that.move_cell_up();
195 that.control_key_active = false;
196 that.control_key_active = false;
196 return false;
197 return false;
197 } else if (event.which === 80 && that.control_key_active) {
198 } else if (event.which === 80 && that.control_key_active) {
198 // Select previous = p
199 // Select previous = p
199 that.select_prev();
200 that.select_prev();
200 that.control_key_active = false;
201 that.control_key_active = false;
201 return false;
202 return false;
202 } else if (event.which === 78 && that.control_key_active) {
203 } else if (event.which === 78 && that.control_key_active) {
203 // Select next = n
204 // Select next = n
204 that.select_next();
205 that.select_next();
205 that.control_key_active = false;
206 that.control_key_active = false;
206 return false;
207 return false;
207 } else if (event.which === 76 && that.control_key_active) {
208 } else if (event.which === 76 && that.control_key_active) {
208 // Toggle line numbers = l
209 // Toggle line numbers = l
209 that.cell_toggle_line_numbers();
210 that.cell_toggle_line_numbers();
210 that.control_key_active = false;
211 that.control_key_active = false;
211 return false;
212 return false;
212 } else if (event.which === 73 && that.control_key_active) {
213 } else if (event.which === 73 && that.control_key_active) {
213 // Interrupt kernel = i
214 // Interrupt kernel = i
214 that.kernel.interrupt();
215 that.kernel.interrupt();
215 that.control_key_active = false;
216 that.control_key_active = false;
216 return false;
217 return false;
217 } else if (event.which === 190 && that.control_key_active) {
218 } else if (event.which === 190 && that.control_key_active) {
218 // Restart kernel = . # matches qt console
219 // Restart kernel = . # matches qt console
219 that.restart_kernel();
220 that.restart_kernel();
220 that.control_key_active = false;
221 that.control_key_active = false;
221 return false;
222 return false;
222 } else if (event.which === 72 && that.control_key_active) {
223 } else if (event.which === 72 && that.control_key_active) {
223 // Show keyboard shortcuts = h
224 // Show keyboard shortcuts = h
224 IPython.quick_help.show_keyboard_shortcuts();
225 IPython.quick_help.show_keyboard_shortcuts();
225 that.control_key_active = false;
226 that.control_key_active = false;
226 return false;
227 return false;
227 } else if (that.control_key_active) {
228 } else if (that.control_key_active) {
228 that.control_key_active = false;
229 that.control_key_active = false;
229 return true;
230 return true;
230 };
231 };
231 return true;
232 return true;
232 });
233 });
233
234
234 var collapse_time = function(time){
235 var collapse_time = function(time){
235 var app_height = $('div#main_app').height(); // content height
236 var app_height = $('div#main_app').height(); // content height
236 var splitter_height = $('div#pager_splitter').outerHeight(true);
237 var splitter_height = $('div#pager_splitter').outerHeight(true);
237 var new_height = app_height - splitter_height;
238 var new_height = app_height - splitter_height;
238 that.element.animate({height : new_height + 'px'}, time);
239 that.element.animate({height : new_height + 'px'}, time);
239 }
240 }
240
241
241 this.element.bind('collapse_pager', function (event,extrap) {
242 this.element.bind('collapse_pager', function (event,extrap) {
242 time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
243 time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
243 collapse_time(time);
244 collapse_time(time);
244 });
245 });
245
246
246 var expand_time = function(time) {
247 var expand_time = function(time) {
247 var app_height = $('div#main_app').height(); // content height
248 var app_height = $('div#main_app').height(); // content height
248 var splitter_height = $('div#pager_splitter').outerHeight(true);
249 var splitter_height = $('div#pager_splitter').outerHeight(true);
249 var pager_height = $('div#pager').outerHeight(true);
250 var pager_height = $('div#pager').outerHeight(true);
250 var new_height = app_height - pager_height - splitter_height;
251 var new_height = app_height - pager_height - splitter_height;
251 that.element.animate({height : new_height + 'px'}, time);
252 that.element.animate({height : new_height + 'px'}, time);
252 }
253 }
253
254
254 this.element.bind('expand_pager', function (event, extrap) {
255 this.element.bind('expand_pager', function (event, extrap) {
255 time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
256 time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
256 expand_time(time);
257 expand_time(time);
257 });
258 });
258
259
259 $(window).bind('beforeunload', function () {
260 $(window).bind('beforeunload', function () {
260 // TODO: Make killing the kernel configurable.
261 // TODO: Make killing the kernel configurable.
261 var kill_kernel = false;
262 var kill_kernel = false;
262 if (kill_kernel) {
263 if (kill_kernel) {
263 that.kernel.kill();
264 that.kernel.kill();
264 }
265 }
265 if (that.dirty && ! that.read_only) {
266 if (that.dirty && ! that.read_only) {
266 return "You have unsaved changes that will be lost if you leave this page.";
267 return "You have unsaved changes that will be lost if you leave this page.";
267 };
268 };
268 // Null is the *only* return value that will make the browser not
269 // Null is the *only* return value that will make the browser not
269 // pop up the "don't leave" dialog.
270 // pop up the "don't leave" dialog.
270 return null;
271 return null;
271 });
272 });
272 };
273 };
273
274
274
275
275 Notebook.prototype.scroll_to_bottom = function () {
276 Notebook.prototype.scroll_to_bottom = function () {
276 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
277 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
277 };
278 };
278
279
279
280
280 Notebook.prototype.scroll_to_top = function () {
281 Notebook.prototype.scroll_to_top = function () {
281 this.element.animate({scrollTop:0}, 0);
282 this.element.animate({scrollTop:0}, 0);
282 };
283 };
283
284
284
285
285 // Cell indexing, retrieval, etc.
286 // Cell indexing, retrieval, etc.
286
287
287 Notebook.prototype.get_cell_elements = function () {
288 Notebook.prototype.get_cell_elements = function () {
288 return this.element.children("div.cell");
289 return this.element.children("div.cell");
289 };
290 };
290
291
291
292
292 Notebook.prototype.get_cell_element = function (index) {
293 Notebook.prototype.get_cell_element = function (index) {
293 var result = null;
294 var result = null;
294 var e = this.get_cell_elements().eq(index);
295 var e = this.get_cell_elements().eq(index);
295 if (e.length !== 0) {
296 if (e.length !== 0) {
296 result = e;
297 result = e;
297 }
298 }
298 return result;
299 return result;
299 };
300 };
300
301
301
302
302 Notebook.prototype.ncells = function (cell) {
303 Notebook.prototype.ncells = function (cell) {
303 return this.get_cell_elements().length;
304 return this.get_cell_elements().length;
304 };
305 };
305
306
306
307
307 // TODO: we are often calling cells as cells()[i], which we should optimize
308 // TODO: we are often calling cells as cells()[i], which we should optimize
308 // to cells(i) or a new method.
309 // to cells(i) or a new method.
309 Notebook.prototype.get_cells = function () {
310 Notebook.prototype.get_cells = function () {
310 return this.get_cell_elements().toArray().map(function (e) {
311 return this.get_cell_elements().toArray().map(function (e) {
311 return $(e).data("cell");
312 return $(e).data("cell");
312 });
313 });
313 };
314 };
314
315
315
316
316 Notebook.prototype.get_cell = function (index) {
317 Notebook.prototype.get_cell = function (index) {
317 var result = null;
318 var result = null;
318 var ce = this.get_cell_element(index);
319 var ce = this.get_cell_element(index);
319 if (ce !== null) {
320 if (ce !== null) {
320 result = ce.data('cell');
321 result = ce.data('cell');
321 }
322 }
322 return result;
323 return result;
323 }
324 }
324
325
325
326
326 Notebook.prototype.get_next_cell = function (cell) {
327 Notebook.prototype.get_next_cell = function (cell) {
327 var result = null;
328 var result = null;
328 var index = this.find_cell_index(cell);
329 var index = this.find_cell_index(cell);
329 if (index !== null && index < this.ncells()) {
330 if (index !== null && index < this.ncells()) {
330 result = this.get_cell(index+1);
331 result = this.get_cell(index+1);
331 }
332 }
332 return result;
333 return result;
333 }
334 }
334
335
335
336
336 Notebook.prototype.get_prev_cell = function (cell) {
337 Notebook.prototype.get_prev_cell = function (cell) {
337 var result = null;
338 var result = null;
338 var index = this.find_cell_index(cell);
339 var index = this.find_cell_index(cell);
339 if (index !== null && index > 1) {
340 if (index !== null && index > 1) {
340 result = this.get_cell(index-1);
341 result = this.get_cell(index-1);
341 }
342 }
342 return result;
343 return result;
343 }
344 }
344
345
345 Notebook.prototype.find_cell_index = function (cell) {
346 Notebook.prototype.find_cell_index = function (cell) {
346 var result = null;
347 var result = null;
347 this.get_cell_elements().filter(function (index) {
348 this.get_cell_elements().filter(function (index) {
348 if ($(this).data("cell") === cell) {
349 if ($(this).data("cell") === cell) {
349 result = index;
350 result = index;
350 };
351 };
351 });
352 });
352 return result;
353 return result;
353 };
354 };
354
355
355
356
356 Notebook.prototype.index_or_selected = function (index) {
357 Notebook.prototype.index_or_selected = function (index) {
357 var i;
358 var i;
358 if (index === undefined || index === null) {
359 if (index === undefined || index === null) {
359 i = this.get_selected_index();
360 i = this.get_selected_index();
360 if (i === null) {
361 if (i === null) {
361 i = 0;
362 i = 0;
362 }
363 }
363 } else {
364 } else {
364 i = index;
365 i = index;
365 }
366 }
366 return i;
367 return i;
367 };
368 };
368
369
369
370
370 Notebook.prototype.get_selected_cell = function () {
371 Notebook.prototype.get_selected_cell = function () {
371 var index = this.get_selected_index();
372 var index = this.get_selected_index();
372 return this.get_cell(index);
373 return this.get_cell(index);
373 };
374 };
374
375
375
376
376 Notebook.prototype.is_valid_cell_index = function (index) {
377 Notebook.prototype.is_valid_cell_index = function (index) {
377 if (index !== null && index >= 0 && index < this.ncells()) {
378 if (index !== null && index >= 0 && index < this.ncells()) {
378 return true;
379 return true;
379 } else {
380 } else {
380 return false;
381 return false;
381 };
382 };
382 }
383 }
383
384
384 Notebook.prototype.get_selected_index = function () {
385 Notebook.prototype.get_selected_index = function () {
385 var result = null;
386 var result = null;
386 this.get_cell_elements().filter(function (index) {
387 this.get_cell_elements().filter(function (index) {
387 if ($(this).data("cell").selected === true) {
388 if ($(this).data("cell").selected === true) {
388 result = index;
389 result = index;
389 };
390 };
390 });
391 });
391 return result;
392 return result;
392 };
393 };
393
394
394
395
395 Notebook.prototype.cell_for_msg = function (msg_id) {
396 Notebook.prototype.cell_for_msg = function (msg_id) {
396 var cell_id = this.msg_cell_map[msg_id];
397 var cell_id = this.msg_cell_map[msg_id];
397 var result = null;
398 var result = null;
398 this.get_cell_elements().filter(function (index) {
399 this.get_cell_elements().filter(function (index) {
399 cell = $(this).data("cell");
400 cell = $(this).data("cell");
400 if (cell.cell_id === cell_id) {
401 if (cell.cell_id === cell_id) {
401 result = cell;
402 result = cell;
402 };
403 };
403 });
404 });
404 return result;
405 return result;
405 };
406 };
406
407
407
408
408 // Cell selection.
409 // Cell selection.
409
410
410 Notebook.prototype.select = function (index) {
411 Notebook.prototype.select = function (index) {
411 if (index !== undefined && index >= 0 && index < this.ncells()) {
412 if (index !== undefined && index >= 0 && index < this.ncells()) {
412 sindex = this.get_selected_index()
413 sindex = this.get_selected_index()
413 if (sindex !== null && index !== sindex) {
414 if (sindex !== null && index !== sindex) {
414 this.get_cell(sindex).unselect();
415 this.get_cell(sindex).unselect();
415 };
416 };
416 var cell = this.get_cell(index)
417 var cell = this.get_cell(index)
417 cell.select();
418 cell.select();
418 if (cell.cell_type === 'heading') {
419 if (cell.cell_type === 'heading') {
419 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
420 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
420 {'cell_type':cell.cell_type,level:cell.level}
421 {'cell_type':cell.cell_type,level:cell.level}
421 );
422 );
422 } else {
423 } else {
423 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
424 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
424 {'cell_type':cell.cell_type}
425 {'cell_type':cell.cell_type}
425 );
426 );
426 };
427 };
427 };
428 };
428 return this;
429 return this;
429 };
430 };
430
431
431
432
432 Notebook.prototype.select_next = function () {
433 Notebook.prototype.select_next = function () {
433 var index = this.get_selected_index();
434 var index = this.get_selected_index();
434 if (index !== null && index >= 0 && (index+1) < this.ncells()) {
435 if (index !== null && index >= 0 && (index+1) < this.ncells()) {
435 this.select(index+1);
436 this.select(index+1);
436 };
437 };
437 return this;
438 return this;
438 };
439 };
439
440
440
441
441 Notebook.prototype.select_prev = function () {
442 Notebook.prototype.select_prev = function () {
442 var index = this.get_selected_index();
443 var index = this.get_selected_index();
443 if (index !== null && index >= 0 && (index-1) < this.ncells()) {
444 if (index !== null && index >= 0 && (index-1) < this.ncells()) {
444 this.select(index-1);
445 this.select(index-1);
445 };
446 };
446 return this;
447 return this;
447 };
448 };
448
449
449
450
450 // Cell movement
451 // Cell movement
451
452
452 Notebook.prototype.move_cell_up = function (index) {
453 Notebook.prototype.move_cell_up = function (index) {
453 var i = this.index_or_selected();
454 var i = this.index_or_selected();
454 if (i !== null && i < this.ncells() && i > 0) {
455 if (i !== null && i < this.ncells() && i > 0) {
455 var pivot = this.get_cell_element(i-1);
456 var pivot = this.get_cell_element(i-1);
456 var tomove = this.get_cell_element(i);
457 var tomove = this.get_cell_element(i);
457 if (pivot !== null && tomove !== null) {
458 if (pivot !== null && tomove !== null) {
458 tomove.detach();
459 tomove.detach();
459 pivot.before(tomove);
460 pivot.before(tomove);
460 this.select(i-1);
461 this.select(i-1);
461 };
462 };
462 };
463 };
463 this.dirty = true;
464 this.dirty = true;
464 return this;
465 return this;
465 };
466 };
466
467
467
468
468 Notebook.prototype.move_cell_down = function (index) {
469 Notebook.prototype.move_cell_down = function (index) {
469 var i = this.index_or_selected();
470 var i = this.index_or_selected();
470 if (i !== null && i < (this.ncells()-1) && i >= 0) {
471 if (i !== null && i < (this.ncells()-1) && i >= 0) {
471 var pivot = this.get_cell_element(i+1);
472 var pivot = this.get_cell_element(i+1);
472 var tomove = this.get_cell_element(i);
473 var tomove = this.get_cell_element(i);
473 if (pivot !== null && tomove !== null) {
474 if (pivot !== null && tomove !== null) {
474 tomove.detach();
475 tomove.detach();
475 pivot.after(tomove);
476 pivot.after(tomove);
476 this.select(i+1);
477 this.select(i+1);
477 };
478 };
478 };
479 };
479 this.dirty = true;
480 this.dirty = true;
480 return this;
481 return this;
481 };
482 };
482
483
483
484
484 Notebook.prototype.sort_cells = function () {
485 Notebook.prototype.sort_cells = function () {
485 // This is not working right now. Calling this will actually crash
486 // This is not working right now. Calling this will actually crash
486 // the browser. I think there is an infinite loop in here...
487 // the browser. I think there is an infinite loop in here...
487 var ncells = this.ncells();
488 var ncells = this.ncells();
488 var sindex = this.get_selected_index();
489 var sindex = this.get_selected_index();
489 var swapped;
490 var swapped;
490 do {
491 do {
491 swapped = false;
492 swapped = false;
492 for (var i=1; i<ncells; i++) {
493 for (var i=1; i<ncells; i++) {
493 current = this.get_cell(i);
494 current = this.get_cell(i);
494 previous = this.get_cell(i-1);
495 previous = this.get_cell(i-1);
495 if (previous.input_prompt_number > current.input_prompt_number) {
496 if (previous.input_prompt_number > current.input_prompt_number) {
496 this.move_cell_up(i);
497 this.move_cell_up(i);
497 swapped = true;
498 swapped = true;
498 };
499 };
499 };
500 };
500 } while (swapped);
501 } while (swapped);
501 this.select(sindex);
502 this.select(sindex);
502 return this;
503 return this;
503 };
504 };
504
505
505 // Insertion, deletion.
506 // Insertion, deletion.
506
507
507 Notebook.prototype.delete_cell = function (index) {
508 Notebook.prototype.delete_cell = function (index) {
508 var i = this.index_or_selected(index);
509 var i = this.index_or_selected(index);
509 if (this.is_valid_cell_index(i)) {
510 if (this.is_valid_cell_index(i)) {
510 var ce = this.get_cell_element(i);
511 var ce = this.get_cell_element(i);
511 ce.remove();
512 ce.remove();
512 if (i === (this.ncells())) {
513 if (i === (this.ncells())) {
513 this.select(i-1);
514 this.select(i-1);
514 } else {
515 } else {
515 this.select(i);
516 this.select(i);
516 };
517 };
517 this.dirty = true;
518 this.dirty = true;
518 };
519 };
519 return this;
520 return this;
520 };
521 };
521
522
522
523
523 Notebook.prototype.insert_cell_below = function (type, index) {
524 Notebook.prototype.insert_cell_below = function (type, index) {
524 // type = ('code','html','markdown')
525 // type = ('code','html','markdown')
525 // index = cell index or undefined to insert below selected
526 // index = cell index or undefined to insert below selected
526 index = this.index_or_selected(index);
527 index = this.index_or_selected(index);
527 var cell = null;
528 var cell = null;
528 if (this.ncells() === 0 || this.is_valid_cell_index(index)) {
529 if (this.ncells() === 0 || this.is_valid_cell_index(index)) {
529 if (type === 'code') {
530 if (type === 'code') {
530 cell = new IPython.CodeCell(this);
531 cell = new IPython.CodeCell(this);
531 cell.set_input_prompt();
532 cell.set_input_prompt();
532 } else if (type === 'markdown') {
533 } else if (type === 'markdown') {
533 cell = new IPython.MarkdownCell(this);
534 cell = new IPython.MarkdownCell(this);
534 } else if (type === 'html') {
535 } else if (type === 'html') {
535 cell = new IPython.HTMLCell(this);
536 cell = new IPython.HTMLCell(this);
536 } else if (type === 'raw') {
537 } else if (type === 'raw') {
537 cell = new IPython.RawCell(this);
538 cell = new IPython.RawCell(this);
538 } else if (type === 'heading') {
539 } else if (type === 'heading') {
539 cell = new IPython.HeadingCell(this);
540 cell = new IPython.HeadingCell(this);
540 };
541 };
541 if (cell !== null) {
542 if (cell !== null) {
542 if (this.ncells() === 0) {
543 if (this.ncells() === 0) {
543 this.element.find('div.end_space').before(cell.element);
544 this.element.find('div.end_space').before(cell.element);
544 } else if (this.is_valid_cell_index(index)) {
545 } else if (this.is_valid_cell_index(index)) {
545 this.get_cell_element(index).after(cell.element);
546 this.get_cell_element(index).after(cell.element);
546 };
547 };
547 cell.render();
548 cell.render();
548 this.select(this.find_cell_index(cell));
549 this.select(this.find_cell_index(cell));
549 this.dirty = true;
550 this.dirty = true;
550 return cell;
551 return cell;
551 };
552 };
552 };
553 };
553 return cell;
554 return cell;
554 };
555 };
555
556
556
557
557 Notebook.prototype.insert_cell_above = function (type, index) {
558 Notebook.prototype.insert_cell_above = function (type, index) {
558 // type = ('code','html','markdown')
559 // type = ('code','html','markdown')
559 // index = cell index or undefined to insert above selected
560 // index = cell index or undefined to insert above selected
560 index = this.index_or_selected(index);
561 index = this.index_or_selected(index);
561 var cell = null;
562 var cell = null;
562 if (this.ncells() === 0 || this.is_valid_cell_index(index)) {
563 if (this.ncells() === 0 || this.is_valid_cell_index(index)) {
563 if (type === 'code') {
564 if (type === 'code') {
564 cell = new IPython.CodeCell(this);
565 cell = new IPython.CodeCell(this);
565 cell.set_input_prompt();
566 cell.set_input_prompt();
566 } else if (type === 'markdown') {
567 } else if (type === 'markdown') {
567 cell = new IPython.MarkdownCell(this);
568 cell = new IPython.MarkdownCell(this);
568 } else if (type === 'html') {
569 } else if (type === 'html') {
569 cell = new IPython.HTMLCell(this);
570 cell = new IPython.HTMLCell(this);
570 } else if (type === 'raw') {
571 } else if (type === 'raw') {
571 cell = new IPython.RawCell(this);
572 cell = new IPython.RawCell(this);
572 } else if (type === 'heading') {
573 } else if (type === 'heading') {
573 cell = new IPython.HeadingCell(this);
574 cell = new IPython.HeadingCell(this);
574 };
575 };
575 if (cell !== null) {
576 if (cell !== null) {
576 if (this.ncells() === 0) {
577 if (this.ncells() === 0) {
577 this.element.find('div.end_space').before(cell.element);
578 this.element.find('div.end_space').before(cell.element);
578 } else if (this.is_valid_cell_index(index)) {
579 } else if (this.is_valid_cell_index(index)) {
579 this.get_cell_element(index).before(cell.element);
580 this.get_cell_element(index).before(cell.element);
580 };
581 };
581 cell.render();
582 cell.render();
582 this.select(this.find_cell_index(cell));
583 this.select(this.find_cell_index(cell));
583 this.dirty = true;
584 this.dirty = true;
584 return cell;
585 return cell;
585 };
586 };
586 };
587 };
587 return cell;
588 return cell;
588 };
589 };
589
590
590
591
591 Notebook.prototype.to_code = function (index) {
592 Notebook.prototype.to_code = function (index) {
592 var i = this.index_or_selected(index);
593 var i = this.index_or_selected(index);
593 if (this.is_valid_cell_index(i)) {
594 if (this.is_valid_cell_index(i)) {
594 var source_element = this.get_cell_element(i);
595 var source_element = this.get_cell_element(i);
595 var source_cell = source_element.data("cell");
596 var source_cell = source_element.data("cell");
596 if (!(source_cell instanceof IPython.CodeCell)) {
597 if (!(source_cell instanceof IPython.CodeCell)) {
597 target_cell = this.insert_cell_below('code',i);
598 target_cell = this.insert_cell_below('code',i);
598 var text = source_cell.get_text();
599 var text = source_cell.get_text();
599 if (text === source_cell.placeholder) {
600 if (text === source_cell.placeholder) {
600 text = '';
601 text = '';
601 }
602 }
602 target_cell.set_text(text);
603 target_cell.set_text(text);
603 source_element.remove();
604 source_element.remove();
604 this.dirty = true;
605 this.dirty = true;
605 };
606 };
606 };
607 };
607 };
608 };
608
609
609
610
610 Notebook.prototype.to_markdown = function (index) {
611 Notebook.prototype.to_markdown = function (index) {
611 var i = this.index_or_selected(index);
612 var i = this.index_or_selected(index);
612 if (this.is_valid_cell_index(i)) {
613 if (this.is_valid_cell_index(i)) {
613 var source_element = this.get_cell_element(i);
614 var source_element = this.get_cell_element(i);
614 var source_cell = source_element.data("cell");
615 var source_cell = source_element.data("cell");
615 if (!(source_cell instanceof IPython.MarkdownCell)) {
616 if (!(source_cell instanceof IPython.MarkdownCell)) {
616 target_cell = this.insert_cell_below('markdown',i);
617 target_cell = this.insert_cell_below('markdown',i);
617 var text = source_cell.get_text();
618 var text = source_cell.get_text();
618 if (text === source_cell.placeholder) {
619 if (text === source_cell.placeholder) {
619 text = '';
620 text = '';
620 };
621 };
621 // The edit must come before the set_text.
622 // The edit must come before the set_text.
622 target_cell.edit();
623 target_cell.edit();
623 target_cell.set_text(text);
624 target_cell.set_text(text);
624 source_element.remove();
625 source_element.remove();
625 this.dirty = true;
626 this.dirty = true;
626 };
627 };
627 };
628 };
628 };
629 };
629
630
630
631
631 Notebook.prototype.to_html = function (index) {
632 Notebook.prototype.to_html = function (index) {
632 var i = this.index_or_selected(index);
633 var i = this.index_or_selected(index);
633 if (this.is_valid_cell_index(i)) {
634 if (this.is_valid_cell_index(i)) {
634 var source_element = this.get_cell_element(i);
635 var source_element = this.get_cell_element(i);
635 var source_cell = source_element.data("cell");
636 var source_cell = source_element.data("cell");
636 var target_cell = null;
637 var target_cell = null;
637 if (!(source_cell instanceof IPython.HTMLCell)) {
638 if (!(source_cell instanceof IPython.HTMLCell)) {
638 target_cell = this.insert_cell_below('html',i);
639 target_cell = this.insert_cell_below('html',i);
639 var text = source_cell.get_text();
640 var text = source_cell.get_text();
640 if (text === source_cell.placeholder) {
641 if (text === source_cell.placeholder) {
641 text = '';
642 text = '';
642 };
643 };
643 // The edit must come before the set_text.
644 // The edit must come before the set_text.
644 target_cell.edit();
645 target_cell.edit();
645 target_cell.set_text(text);
646 target_cell.set_text(text);
646 source_element.remove();
647 source_element.remove();
647 this.dirty = true;
648 this.dirty = true;
648 };
649 };
649 };
650 };
650 };
651 };
651
652
652
653
653 Notebook.prototype.to_raw = function (index) {
654 Notebook.prototype.to_raw = function (index) {
654 var i = this.index_or_selected(index);
655 var i = this.index_or_selected(index);
655 if (this.is_valid_cell_index(i)) {
656 if (this.is_valid_cell_index(i)) {
656 var source_element = this.get_cell_element(i);
657 var source_element = this.get_cell_element(i);
657 var source_cell = source_element.data("cell");
658 var source_cell = source_element.data("cell");
658 var target_cell = null;
659 var target_cell = null;
659 if (!(source_cell instanceof IPython.RawCell)) {
660 if (!(source_cell instanceof IPython.RawCell)) {
660 target_cell = this.insert_cell_below('raw',i);
661 target_cell = this.insert_cell_below('raw',i);
661 var text = source_cell.get_text();
662 var text = source_cell.get_text();
662 if (text === source_cell.placeholder) {
663 if (text === source_cell.placeholder) {
663 text = '';
664 text = '';
664 };
665 };
665 // The edit must come before the set_text.
666 // The edit must come before the set_text.
666 target_cell.edit();
667 target_cell.edit();
667 target_cell.set_text(text);
668 target_cell.set_text(text);
668 source_element.remove();
669 source_element.remove();
669 this.dirty = true;
670 this.dirty = true;
670 };
671 };
671 };
672 };
672 };
673 };
673
674
674
675
675 Notebook.prototype.to_heading = function (index, level) {
676 Notebook.prototype.to_heading = function (index, level) {
676 level = level || 1;
677 level = level || 1;
677 var i = this.index_or_selected(index);
678 var i = this.index_or_selected(index);
678 if (this.is_valid_cell_index(i)) {
679 if (this.is_valid_cell_index(i)) {
679 var source_element = this.get_cell_element(i);
680 var source_element = this.get_cell_element(i);
680 var source_cell = source_element.data("cell");
681 var source_cell = source_element.data("cell");
681 var target_cell = null;
682 var target_cell = null;
682 if (source_cell instanceof IPython.HeadingCell) {
683 if (source_cell instanceof IPython.HeadingCell) {
683 source_cell.set_level(level);
684 source_cell.set_level(level);
684 } else {
685 } else {
685 target_cell = this.insert_cell_below('heading',i);
686 target_cell = this.insert_cell_below('heading',i);
686 var text = source_cell.get_text();
687 var text = source_cell.get_text();
687 if (text === source_cell.placeholder) {
688 if (text === source_cell.placeholder) {
688 text = '';
689 text = '';
689 };
690 };
690 // The edit must come before the set_text.
691 // The edit must come before the set_text.
691 target_cell.set_level(level);
692 target_cell.set_level(level);
692 target_cell.edit();
693 target_cell.edit();
693 target_cell.set_text(text);
694 target_cell.set_text(text);
694 source_element.remove();
695 source_element.remove();
695 this.dirty = true;
696 this.dirty = true;
696 };
697 };
697 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
698 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
698 {'cell_type':'heading',level:level}
699 {'cell_type':'heading',level:level}
699 );
700 );
700 };
701 };
701 };
702 };
702
703
703
704
704 // Cut/Copy/Paste
705 // Cut/Copy/Paste
705
706
706 Notebook.prototype.enable_paste = function () {
707 Notebook.prototype.enable_paste = function () {
707 var that = this;
708 var that = this;
708 if (!this.paste_enabled) {
709 if (!this.paste_enabled) {
709 $('#paste_cell').removeClass('ui-state-disabled')
710 $('#paste_cell').removeClass('ui-state-disabled')
710 .on('click', function () {that.paste_cell();});
711 .on('click', function () {that.paste_cell();});
711 $('#paste_cell_above').removeClass('ui-state-disabled')
712 $('#paste_cell_above').removeClass('ui-state-disabled')
712 .on('click', function () {that.paste_cell_above();});
713 .on('click', function () {that.paste_cell_above();});
713 $('#paste_cell_below').removeClass('ui-state-disabled')
714 $('#paste_cell_below').removeClass('ui-state-disabled')
714 .on('click', function () {that.paste_cell_below();});
715 .on('click', function () {that.paste_cell_below();});
715 this.paste_enabled = true;
716 this.paste_enabled = true;
716 };
717 };
717 };
718 };
718
719
719
720
720 Notebook.prototype.disable_paste = function () {
721 Notebook.prototype.disable_paste = function () {
721 if (this.paste_enabled) {
722 if (this.paste_enabled) {
722 $('#paste_cell').addClass('ui-state-disabled').off('click');
723 $('#paste_cell').addClass('ui-state-disabled').off('click');
723 $('#paste_cell_above').addClass('ui-state-disabled').off('click');
724 $('#paste_cell_above').addClass('ui-state-disabled').off('click');
724 $('#paste_cell_below').addClass('ui-state-disabled').off('click');
725 $('#paste_cell_below').addClass('ui-state-disabled').off('click');
725 this.paste_enabled = false;
726 this.paste_enabled = false;
726 };
727 };
727 };
728 };
728
729
729
730
730 Notebook.prototype.cut_cell = function () {
731 Notebook.prototype.cut_cell = function () {
731 this.copy_cell();
732 this.copy_cell();
732 this.delete_cell();
733 this.delete_cell();
733 }
734 }
734
735
735 Notebook.prototype.copy_cell = function () {
736 Notebook.prototype.copy_cell = function () {
736 var cell = this.get_selected_cell();
737 var cell = this.get_selected_cell();
737 this.clipboard = cell.toJSON();
738 this.clipboard = cell.toJSON();
738 this.enable_paste();
739 this.enable_paste();
739 };
740 };
740
741
741
742
742 Notebook.prototype.paste_cell = function () {
743 Notebook.prototype.paste_cell = function () {
743 if (this.clipboard !== null && this.paste_enabled) {
744 if (this.clipboard !== null && this.paste_enabled) {
744 var cell_data = this.clipboard;
745 var cell_data = this.clipboard;
745 var new_cell = this.insert_cell_above(cell_data.cell_type);
746 var new_cell = this.insert_cell_above(cell_data.cell_type);
746 new_cell.fromJSON(cell_data);
747 new_cell.fromJSON(cell_data);
747 old_cell = this.get_next_cell(new_cell);
748 old_cell = this.get_next_cell(new_cell);
748 this.delete_cell(this.find_cell_index(old_cell));
749 this.delete_cell(this.find_cell_index(old_cell));
749 this.select(this.find_cell_index(new_cell));
750 this.select(this.find_cell_index(new_cell));
750 };
751 };
751 };
752 };
752
753
753
754
754 Notebook.prototype.paste_cell_above = function () {
755 Notebook.prototype.paste_cell_above = function () {
755 if (this.clipboard !== null && this.paste_enabled) {
756 if (this.clipboard !== null && this.paste_enabled) {
756 var cell_data = this.clipboard;
757 var cell_data = this.clipboard;
757 var new_cell = this.insert_cell_above(cell_data.cell_type);
758 var new_cell = this.insert_cell_above(cell_data.cell_type);
758 new_cell.fromJSON(cell_data);
759 new_cell.fromJSON(cell_data);
759 };
760 };
760 };
761 };
761
762
762
763
763 Notebook.prototype.paste_cell_below = function () {
764 Notebook.prototype.paste_cell_below = function () {
764 if (this.clipboard !== null && this.paste_enabled) {
765 if (this.clipboard !== null && this.paste_enabled) {
765 var cell_data = this.clipboard;
766 var cell_data = this.clipboard;
766 var new_cell = this.insert_cell_below(cell_data.cell_type);
767 var new_cell = this.insert_cell_below(cell_data.cell_type);
767 new_cell.fromJSON(cell_data);
768 new_cell.fromJSON(cell_data);
768 };
769 };
769 };
770 };
770
771
771
772
772 // Split/merge
773 // Split/merge
773
774
774 Notebook.prototype.split_cell = function () {
775 Notebook.prototype.split_cell = function () {
775 // Todo: implement spliting for other cell types.
776 // Todo: implement spliting for other cell types.
776 var cell = this.get_selected_cell();
777 var cell = this.get_selected_cell();
777 if (cell.is_splittable()) {
778 if (cell.is_splittable()) {
778 texta = cell.get_pre_cursor();
779 texta = cell.get_pre_cursor();
779 textb = cell.get_post_cursor();
780 textb = cell.get_post_cursor();
780 if (cell instanceof IPython.CodeCell) {
781 if (cell instanceof IPython.CodeCell) {
781 cell.set_text(texta);
782 cell.set_text(texta);
782 var new_cell = this.insert_cell_below('code');
783 var new_cell = this.insert_cell_below('code');
783 new_cell.set_text(textb);
784 new_cell.set_text(textb);
784 } else if (cell instanceof IPython.MarkdownCell) {
785 } else if (cell instanceof IPython.MarkdownCell) {
785 cell.set_text(texta);
786 cell.set_text(texta);
786 cell.render();
787 cell.render();
787 var new_cell = this.insert_cell_below('markdown');
788 var new_cell = this.insert_cell_below('markdown');
788 new_cell.edit(); // editor must be visible to call set_text
789 new_cell.edit(); // editor must be visible to call set_text
789 new_cell.set_text(textb);
790 new_cell.set_text(textb);
790 new_cell.render();
791 new_cell.render();
791 } else if (cell instanceof IPython.HTMLCell) {
792 } else if (cell instanceof IPython.HTMLCell) {
792 cell.set_text(texta);
793 cell.set_text(texta);
793 cell.render();
794 cell.render();
794 var new_cell = this.insert_cell_below('html');
795 var new_cell = this.insert_cell_below('html');
795 new_cell.edit(); // editor must be visible to call set_text
796 new_cell.edit(); // editor must be visible to call set_text
796 new_cell.set_text(textb);
797 new_cell.set_text(textb);
797 new_cell.render();
798 new_cell.render();
798 };
799 };
799 };
800 };
800 };
801 };
801
802
802
803
803 Notebook.prototype.merge_cell_above = function () {
804 Notebook.prototype.merge_cell_above = function () {
804 var index = this.get_selected_index();
805 var index = this.get_selected_index();
805 var cell = this.get_cell(index);
806 var cell = this.get_cell(index);
806 if (index > 0) {
807 if (index > 0) {
807 upper_cell = this.get_cell(index-1);
808 upper_cell = this.get_cell(index-1);
808 upper_text = upper_cell.get_text();
809 upper_text = upper_cell.get_text();
809 text = cell.get_text();
810 text = cell.get_text();
810 if (cell instanceof IPython.CodeCell) {
811 if (cell instanceof IPython.CodeCell) {
811 cell.set_text(upper_text+'\n'+text);
812 cell.set_text(upper_text+'\n'+text);
812 } else if (cell instanceof IPython.MarkdownCell || cell instanceof IPython.HTMLCell) {
813 } else if (cell instanceof IPython.MarkdownCell || cell instanceof IPython.HTMLCell) {
813 cell.edit();
814 cell.edit();
814 cell.set_text(upper_text+'\n'+text);
815 cell.set_text(upper_text+'\n'+text);
815 cell.render();
816 cell.render();
816 };
817 };
817 this.delete_cell(index-1);
818 this.delete_cell(index-1);
818 this.select(this.find_cell_index(cell));
819 this.select(this.find_cell_index(cell));
819 };
820 };
820 };
821 };
821
822
822
823
823 Notebook.prototype.merge_cell_below = function () {
824 Notebook.prototype.merge_cell_below = function () {
824 var index = this.get_selected_index();
825 var index = this.get_selected_index();
825 var cell = this.get_cell(index);
826 var cell = this.get_cell(index);
826 if (index < this.ncells()-1) {
827 if (index < this.ncells()-1) {
827 lower_cell = this.get_cell(index+1);
828 lower_cell = this.get_cell(index+1);
828 lower_text = lower_cell.get_text();
829 lower_text = lower_cell.get_text();
829 text = cell.get_text();
830 text = cell.get_text();
830 if (cell instanceof IPython.CodeCell) {
831 if (cell instanceof IPython.CodeCell) {
831 cell.set_text(text+'\n'+lower_text);
832 cell.set_text(text+'\n'+lower_text);
832 } else if (cell instanceof IPython.MarkdownCell || cell instanceof IPython.HTMLCell) {
833 } else if (cell instanceof IPython.MarkdownCell || cell instanceof IPython.HTMLCell) {
833 cell.edit();
834 cell.edit();
834 cell.set_text(text+'\n'+lower_text);
835 cell.set_text(text+'\n'+lower_text);
835 cell.render();
836 cell.render();
836 };
837 };
837 this.delete_cell(index+1);
838 this.delete_cell(index+1);
838 this.select(this.find_cell_index(cell));
839 this.select(this.find_cell_index(cell));
839 };
840 };
840 };
841 };
841
842
842
843
843 // Cell collapsing and output clearing
844 // Cell collapsing and output clearing
844
845
845 Notebook.prototype.collapse = function (index) {
846 Notebook.prototype.collapse = function (index) {
846 var i = this.index_or_selected(index);
847 var i = this.index_or_selected(index);
847 this.get_cell(i).collapse();
848 this.get_cell(i).collapse();
848 this.dirty = true;
849 this.dirty = true;
849 };
850 };
850
851
851
852
852 Notebook.prototype.expand = function (index) {
853 Notebook.prototype.expand = function (index) {
853 var i = this.index_or_selected(index);
854 var i = this.index_or_selected(index);
854 this.get_cell(i).expand();
855 this.get_cell(i).expand();
855 this.dirty = true;
856 this.dirty = true;
856 };
857 };
857
858
858
859
859 Notebook.prototype.toggle_output = function (index) {
860 Notebook.prototype.toggle_output = function (index) {
860 var i = this.index_or_selected(index);
861 var i = this.index_or_selected(index);
861 this.get_cell(i).toggle_output();
862 this.get_cell(i).toggle_output();
862 this.dirty = true;
863 this.dirty = true;
863 };
864 };
864
865
865
866
866 Notebook.prototype.set_timebeforetooltip = function (time) {
867 Notebook.prototype.set_timebeforetooltip = function (time) {
867 this.time_before_tooltip = time;
868 this.time_before_tooltip = time;
868 };
869 };
869
870
870
871
871 Notebook.prototype.set_tooltipontab = function (state) {
872 Notebook.prototype.set_tooltipontab = function (state) {
872 this.tooltip_on_tab = state;
873 this.tooltip_on_tab = state;
873 };
874 };
874
875
875
876
876 Notebook.prototype.set_smartcompleter = function (state) {
877 Notebook.prototype.set_smartcompleter = function (state) {
877 this.smart_completer = state;
878 this.smart_completer = state;
878 };
879 };
879
880
880
881
881 Notebook.prototype.clear_all_output = function () {
882 Notebook.prototype.clear_all_output = function () {
882 var ncells = this.ncells();
883 var ncells = this.ncells();
883 var cells = this.get_cells();
884 var cells = this.get_cells();
884 for (var i=0; i<ncells; i++) {
885 for (var i=0; i<ncells; i++) {
885 if (cells[i] instanceof IPython.CodeCell) {
886 if (cells[i] instanceof IPython.CodeCell) {
886 cells[i].clear_output(true,true,true);
887 cells[i].clear_output(true,true,true);
887 // Make all In[] prompts blank, as well
888 // Make all In[] prompts blank, as well
888 // TODO: make this configurable (via checkbox?)
889 // TODO: make this configurable (via checkbox?)
889 cells[i].set_input_prompt();
890 cells[i].set_input_prompt();
890 }
891 }
891 };
892 };
892 this.dirty = true;
893 this.dirty = true;
893 };
894 };
894
895
895
896
896 // Other cell functions: line numbers, ...
897 // Other cell functions: line numbers, ...
897
898
898 Notebook.prototype.cell_toggle_line_numbers = function() {
899 Notebook.prototype.cell_toggle_line_numbers = function() {
899 this.get_selected_cell().toggle_line_numbers();
900 this.get_selected_cell().toggle_line_numbers();
900 };
901 };
901
902
902 // Kernel related things
903 // Kernel related things
903
904
904 Notebook.prototype.start_kernel = function () {
905 Notebook.prototype.start_kernel = function () {
905 this.kernel = new IPython.Kernel();
906 this.kernel = new IPython.Kernel();
906 this.kernel.start(this.notebook_id, $.proxy(this.kernel_started, this));
907 this.kernel.start(this.notebook_id, $.proxy(this.kernel_started, this));
907 };
908 };
908
909
909
910
910 Notebook.prototype.restart_kernel = function () {
911 Notebook.prototype.restart_kernel = function () {
911 var that = this;
912 var that = this;
912 var dialog = $('<div/>');
913 var dialog = $('<div/>');
913 dialog.html('Do you want to restart the current kernel? You will lose all variables defined in it.');
914 dialog.html('Do you want to restart the current kernel? You will lose all variables defined in it.');
914 $(document).append(dialog);
915 $(document).append(dialog);
915 dialog.dialog({
916 dialog.dialog({
916 resizable: false,
917 resizable: false,
917 modal: true,
918 modal: true,
918 title: "Restart kernel or continue running?",
919 title: "Restart kernel or continue running?",
919 closeText: '',
920 closeText: '',
920 buttons : {
921 buttons : {
921 "Restart": function () {
922 "Restart": function () {
922 that.kernel.restart($.proxy(that.kernel_started, that));
923 that.kernel.restart($.proxy(that.kernel_started, that));
923 $(this).dialog('close');
924 $(this).dialog('close');
924 },
925 },
925 "Continue running": function () {
926 "Continue running": function () {
926 $(this).dialog('close');
927 $(this).dialog('close');
927 }
928 }
928 }
929 }
929 });
930 });
930 };
931 };
931
932
932
933
933 Notebook.prototype.kernel_started = function () {
934 Notebook.prototype.kernel_started = function () {
934 console.log("Kernel started: ", this.kernel.kernel_id);
935 console.log("Kernel started: ", this.kernel.kernel_id);
935 this.kernel.shell_channel.onmessage = $.proxy(this.handle_shell_reply,this);
936 this.kernel.shell_channel.onmessage = $.proxy(this.handle_shell_reply,this);
936 this.kernel.iopub_channel.onmessage = $.proxy(this.handle_iopub_reply,this);
937 this.kernel.iopub_channel.onmessage = $.proxy(this.handle_iopub_reply,this);
937 };
938 };
938
939
939
940
940 Notebook.prototype.handle_shell_reply = function (e) {
941 Notebook.prototype.handle_shell_reply = function (e) {
941 reply = $.parseJSON(e.data);
942 reply = $.parseJSON(e.data);
942 var header = reply.header;
943 var header = reply.header;
943 var content = reply.content;
944 var content = reply.content;
944 var msg_type = header.msg_type;
945 var msg_type = header.msg_type;
945 // console.log(reply);
946 // console.log(reply);
946 var cell = this.cell_for_msg(reply.parent_header.msg_id);
947 var cell = this.cell_for_msg(reply.parent_header.msg_id);
947 if (msg_type === "execute_reply") {
948 if (msg_type === "execute_reply") {
948 cell.set_input_prompt(content.execution_count);
949 cell.set_input_prompt(content.execution_count);
949 cell.element.removeClass("running");
950 cell.element.removeClass("running");
950 this.dirty = true;
951 this.dirty = true;
951 } else if (msg_type === "complete_reply") {
952 } else if (msg_type === "complete_reply") {
952 cell.finish_completing(content.matched_text, content.matches);
953 cell.finish_completing(content.matched_text, content.matches);
953 } else if (msg_type === "object_info_reply"){
954 } else if (msg_type === "object_info_reply"){
954 //console.log('back from object_info_request : ')
955 //console.log('back from object_info_request : ')
955 rep = reply.content;
956 rep = reply.content;
956 if(rep.found)
957 if(rep.found)
957 {
958 {
958 cell.finish_tooltip(rep);
959 cell.finish_tooltip(rep);
959 }
960 }
960 } else {
961 } else {
961 //console.log("unknown reply:"+msg_type);
962 //console.log("unknown reply:"+msg_type);
962 }
963 }
963 // when having a rely from object_info_reply,
964 // when having a rely from object_info_reply,
964 // no payload so no nned to handle it
965 // no payload so no nned to handle it
965 if(typeof(content.payload)!='undefined') {
966 if(typeof(content.payload)!='undefined') {
966 var payload = content.payload || [];
967 var payload = content.payload || [];
967 this.handle_payload(cell, payload);
968 this.handle_payload(cell, payload);
968 }
969 }
969 };
970 };
970
971
971
972
972 Notebook.prototype.handle_payload = function (cell, payload) {
973 Notebook.prototype.handle_payload = function (cell, payload) {
973 var l = payload.length;
974 var l = payload.length;
974 for (var i=0; i<l; i++) {
975 for (var i=0; i<l; i++) {
975 if (payload[i].source === 'IPython.zmq.page.page') {
976 if (payload[i].source === 'IPython.zmq.page.page') {
976 if (payload[i].text.trim() !== '') {
977 if (payload[i].text.trim() !== '') {
977 IPython.pager.clear();
978 IPython.pager.clear();
978 IPython.pager.expand();
979 IPython.pager.expand();
979 IPython.pager.append_text(payload[i].text);
980 IPython.pager.append_text(payload[i].text);
980 }
981 }
981 } else if (payload[i].source === 'IPython.zmq.zmqshell.ZMQInteractiveShell.set_next_input') {
982 } else if (payload[i].source === 'IPython.zmq.zmqshell.ZMQInteractiveShell.set_next_input') {
982 var index = this.find_cell_index(cell);
983 var index = this.find_cell_index(cell);
983 var new_cell = this.insert_cell_below('code',index);
984 var new_cell = this.insert_cell_below('code',index);
984 new_cell.set_text(payload[i].text);
985 new_cell.set_text(payload[i].text);
985 this.dirty = true;
986 this.dirty = true;
986 }
987 }
987 };
988 };
988 };
989 };
989
990
990
991
991 Notebook.prototype.handle_iopub_reply = function (e) {
992 Notebook.prototype.handle_iopub_reply = function (e) {
992 reply = $.parseJSON(e.data);
993 reply = $.parseJSON(e.data);
993 var content = reply.content;
994 var content = reply.content;
994 // console.log(reply);
995 // console.log(reply);
995 var msg_type = reply.header.msg_type;
996 var msg_type = reply.header.msg_type;
996 var cell = this.cell_for_msg(reply.parent_header.msg_id);
997 var cell = this.cell_for_msg(reply.parent_header.msg_id);
997 if (msg_type !== 'status' && !cell){
998 if (msg_type !== 'status' && !cell){
998 // message not from this notebook, but should be attached to a cell
999 // message not from this notebook, but should be attached to a cell
999 // console.log("Received IOPub message not caused by one of my cells");
1000 // console.log("Received IOPub message not caused by one of my cells");
1000 // console.log(reply);
1001 // console.log(reply);
1001 return;
1002 return;
1002 }
1003 }
1003 var output_types = ['stream','display_data','pyout','pyerr'];
1004 var output_types = ['stream','display_data','pyout','pyerr'];
1004 if (output_types.indexOf(msg_type) >= 0) {
1005 if (output_types.indexOf(msg_type) >= 0) {
1005 this.handle_output(cell, msg_type, content);
1006 this.handle_output(cell, msg_type, content);
1006 } else if (msg_type === 'status') {
1007 } else if (msg_type === 'status') {
1007 if (content.execution_state === 'busy') {
1008 if (content.execution_state === 'busy') {
1008 $([IPython.events]).trigger('status_busy.Kernel');
1009 $([IPython.events]).trigger('status_busy.Kernel');
1009 } else if (content.execution_state === 'idle') {
1010 } else if (content.execution_state === 'idle') {
1010 $([IPython.events]).trigger('status_idle.Kernel');
1011 $([IPython.events]).trigger('status_idle.Kernel');
1011 } else if (content.execution_state === 'dead') {
1012 } else if (content.execution_state === 'dead') {
1012 this.handle_status_dead();
1013 this.handle_status_dead();
1013 };
1014 };
1014 } else if (msg_type === 'clear_output') {
1015 } else if (msg_type === 'clear_output') {
1015 cell.clear_output(content.stdout, content.stderr, content.other);
1016 cell.clear_output(content.stdout, content.stderr, content.other);
1016 };
1017 };
1017 };
1018 };
1018
1019
1019
1020
1020 Notebook.prototype.handle_status_dead = function () {
1021 Notebook.prototype.handle_status_dead = function () {
1021 var that = this;
1022 var that = this;
1022 this.kernel.stop_channels();
1023 this.kernel.stop_channels();
1023 var dialog = $('<div/>');
1024 var dialog = $('<div/>');
1024 dialog.html('The kernel has died, would you like to restart it? If you do not restart the kernel, you will be able to save the notebook, but running code will not work until the notebook is reopened.');
1025 dialog.html('The kernel has died, would you like to restart it? If you do not restart the kernel, you will be able to save the notebook, but running code will not work until the notebook is reopened.');
1025 $(document).append(dialog);
1026 $(document).append(dialog);
1026 dialog.dialog({
1027 dialog.dialog({
1027 resizable: false,
1028 resizable: false,
1028 modal: true,
1029 modal: true,
1029 title: "Dead kernel",
1030 title: "Dead kernel",
1030 buttons : {
1031 buttons : {
1031 "Restart": function () {
1032 "Restart": function () {
1032 that.start_kernel();
1033 that.start_kernel();
1033 $(this).dialog('close');
1034 $(this).dialog('close');
1034 },
1035 },
1035 "Continue running": function () {
1036 "Continue running": function () {
1036 $(this).dialog('close');
1037 $(this).dialog('close');
1037 }
1038 }
1038 }
1039 }
1039 });
1040 });
1040 };
1041 };
1041
1042
1042
1043
1043 Notebook.prototype.handle_output = function (cell, msg_type, content) {
1044 Notebook.prototype.handle_output = function (cell, msg_type, content) {
1044 var json = {};
1045 var json = {};
1045 json.output_type = msg_type;
1046 json.output_type = msg_type;
1046 if (msg_type === "stream") {
1047 if (msg_type === "stream") {
1047 json.text = content.data;
1048 json.text = content.data;
1048 json.stream = content.name;
1049 json.stream = content.name;
1049 } else if (msg_type === "display_data") {
1050 } else if (msg_type === "display_data") {
1050 json = this.convert_mime_types(json, content.data);
1051 json = this.convert_mime_types(json, content.data);
1051 } else if (msg_type === "pyout") {
1052 } else if (msg_type === "pyout") {
1052 json.prompt_number = content.execution_count;
1053 json.prompt_number = content.execution_count;
1053 json = this.convert_mime_types(json, content.data);
1054 json = this.convert_mime_types(json, content.data);
1054 } else if (msg_type === "pyerr") {
1055 } else if (msg_type === "pyerr") {
1055 json.ename = content.ename;
1056 json.ename = content.ename;
1056 json.evalue = content.evalue;
1057 json.evalue = content.evalue;
1057 json.traceback = content.traceback;
1058 json.traceback = content.traceback;
1058 };
1059 };
1059 // append with dynamic=true
1060 // append with dynamic=true
1060 cell.append_output(json, true);
1061 cell.append_output(json, true);
1061 this.dirty = true;
1062 this.dirty = true;
1062 };
1063 };
1063
1064
1064
1065
1065 Notebook.prototype.convert_mime_types = function (json, data) {
1066 Notebook.prototype.convert_mime_types = function (json, data) {
1066 if (data['text/plain'] !== undefined) {
1067 if (data['text/plain'] !== undefined) {
1067 json.text = data['text/plain'];
1068 json.text = data['text/plain'];
1068 };
1069 };
1069 if (data['text/html'] !== undefined) {
1070 if (data['text/html'] !== undefined) {
1070 json.html = data['text/html'];
1071 json.html = data['text/html'];
1071 };
1072 };
1072 if (data['image/svg+xml'] !== undefined) {
1073 if (data['image/svg+xml'] !== undefined) {
1073 json.svg = data['image/svg+xml'];
1074 json.svg = data['image/svg+xml'];
1074 };
1075 };
1075 if (data['image/png'] !== undefined) {
1076 if (data['image/png'] !== undefined) {
1076 json.png = data['image/png'];
1077 json.png = data['image/png'];
1077 };
1078 };
1078 if (data['image/jpeg'] !== undefined) {
1079 if (data['image/jpeg'] !== undefined) {
1079 json.jpeg = data['image/jpeg'];
1080 json.jpeg = data['image/jpeg'];
1080 };
1081 };
1081 if (data['text/latex'] !== undefined) {
1082 if (data['text/latex'] !== undefined) {
1082 json.latex = data['text/latex'];
1083 json.latex = data['text/latex'];
1083 };
1084 };
1084 if (data['application/json'] !== undefined) {
1085 if (data['application/json'] !== undefined) {
1085 json.json = data['application/json'];
1086 json.json = data['application/json'];
1086 };
1087 };
1087 if (data['application/javascript'] !== undefined) {
1088 if (data['application/javascript'] !== undefined) {
1088 json.javascript = data['application/javascript'];
1089 json.javascript = data['application/javascript'];
1089 }
1090 }
1090 return json;
1091 return json;
1091 };
1092 };
1092
1093
1093
1094
1094 Notebook.prototype.execute_selected_cell = function (options) {
1095 Notebook.prototype.execute_selected_cell = function (options) {
1095 // add_new: should a new cell be added if we are at the end of the nb
1096 // add_new: should a new cell be added if we are at the end of the nb
1096 // terminal: execute in terminal mode, which stays in the current cell
1097 // terminal: execute in terminal mode, which stays in the current cell
1097 default_options = {terminal: false, add_new: true};
1098 default_options = {terminal: false, add_new: true};
1098 $.extend(default_options, options);
1099 $.extend(default_options, options);
1099 var that = this;
1100 var that = this;
1100 var cell = that.get_selected_cell();
1101 var cell = that.get_selected_cell();
1101 var cell_index = that.find_cell_index(cell);
1102 var cell_index = that.find_cell_index(cell);
1102 if (cell instanceof IPython.CodeCell) {
1103 if (cell instanceof IPython.CodeCell) {
1103 cell.clear_output(true, true, true);
1104 cell.clear_output(true, true, true);
1104 cell.set_input_prompt('*');
1105 cell.set_input_prompt('*');
1105 cell.element.addClass("running");
1106 cell.element.addClass("running");
1106 var code = cell.get_text();
1107 var code = cell.get_text();
1107 var msg_id = that.kernel.execute(cell.get_text());
1108 var msg_id = that.kernel.execute(cell.get_text());
1108 that.msg_cell_map[msg_id] = cell.cell_id;
1109 that.msg_cell_map[msg_id] = cell.cell_id;
1109 } else if (cell instanceof IPython.HTMLCell) {
1110 } else if (cell instanceof IPython.HTMLCell) {
1110 cell.render();
1111 cell.render();
1111 }
1112 }
1112 if (default_options.terminal) {
1113 if (default_options.terminal) {
1113 cell.select_all();
1114 cell.select_all();
1114 } else {
1115 } else {
1115 if ((cell_index === (that.ncells()-1)) && default_options.add_new) {
1116 if ((cell_index === (that.ncells()-1)) && default_options.add_new) {
1116 that.insert_cell_below('code');
1117 that.insert_cell_below('code');
1117 // If we are adding a new cell at the end, scroll down to show it.
1118 // If we are adding a new cell at the end, scroll down to show it.
1118 that.scroll_to_bottom();
1119 that.scroll_to_bottom();
1119 } else {
1120 } else {
1120 that.select(cell_index+1);
1121 that.select(cell_index+1);
1121 };
1122 };
1122 };
1123 };
1123 this.dirty = true;
1124 this.dirty = true;
1124 };
1125 };
1125
1126
1126
1127
1127 Notebook.prototype.execute_all_cells = function () {
1128 Notebook.prototype.execute_all_cells = function () {
1128 var ncells = this.ncells();
1129 var ncells = this.ncells();
1129 for (var i=0; i<ncells; i++) {
1130 for (var i=0; i<ncells; i++) {
1130 this.select(i);
1131 this.select(i);
1131 this.execute_selected_cell({add_new:false});
1132 this.execute_selected_cell({add_new:false});
1132 };
1133 };
1133 this.scroll_to_bottom();
1134 this.scroll_to_bottom();
1134 };
1135 };
1135
1136
1136
1137
1137 Notebook.prototype.request_tool_tip = function (cell,func) {
1138 Notebook.prototype.request_tool_tip = function (cell,func) {
1138 // Feel free to shorten this logic if you are better
1139 // Feel free to shorten this logic if you are better
1139 // than me in regEx
1140 // than me in regEx
1140 // basicaly you shoul be able to get xxx.xxx.xxx from
1141 // basicaly you shoul be able to get xxx.xxx.xxx from
1141 // something(range(10), kwarg=smth) ; xxx.xxx.xxx( firstarg, rand(234,23), kwarg1=2,
1142 // something(range(10), kwarg=smth) ; xxx.xxx.xxx( firstarg, rand(234,23), kwarg1=2,
1142 // remove everything between matchin bracket (need to iterate)
1143 // remove everything between matchin bracket (need to iterate)
1143 matchBracket = /\([^\(\)]+\)/g;
1144 matchBracket = /\([^\(\)]+\)/g;
1144 oldfunc = func;
1145 oldfunc = func;
1145 func = func.replace(matchBracket,"");
1146 func = func.replace(matchBracket,"");
1146 while( oldfunc != func )
1147 while( oldfunc != func )
1147 {
1148 {
1148 oldfunc = func;
1149 oldfunc = func;
1149 func = func.replace(matchBracket,"");
1150 func = func.replace(matchBracket,"");
1150 }
1151 }
1151 // remove everythin after last open bracket
1152 // remove everythin after last open bracket
1152 endBracket = /\([^\(]*$/g;
1153 endBracket = /\([^\(]*$/g;
1153 func = func.replace(endBracket,"");
1154 func = func.replace(endBracket,"");
1154 var re = /[a-z_][0-9a-z._]+$/gi; // casse insensitive
1155 var re = /[a-z_][0-9a-z._]+$/gi; // casse insensitive
1155 var msg_id = this.kernel.object_info_request(re.exec(func));
1156 var msg_id = this.kernel.object_info_request(re.exec(func));
1156 if(typeof(msg_id)!='undefined'){
1157 if(typeof(msg_id)!='undefined'){
1157 this.msg_cell_map[msg_id] = cell.cell_id;
1158 this.msg_cell_map[msg_id] = cell.cell_id;
1158 }
1159 }
1159 };
1160 };
1160
1161
1161 Notebook.prototype.complete_cell = function (cell, line, cursor_pos) {
1162 Notebook.prototype.complete_cell = function (cell, line, cursor_pos) {
1162 var msg_id = this.kernel.complete(line, cursor_pos);
1163 var msg_id = this.kernel.complete(line, cursor_pos);
1163 this.msg_cell_map[msg_id] = cell.cell_id;
1164 this.msg_cell_map[msg_id] = cell.cell_id;
1164 };
1165 };
1165
1166
1166
1167
1167 // Persistance and loading
1168 // Persistance and loading
1168
1169
1169 Notebook.prototype.get_notebook_id = function () {
1170 Notebook.prototype.get_notebook_id = function () {
1170 return this.notebook_id;
1171 return this.notebook_id;
1171 };
1172 };
1172
1173
1173
1174
1174 Notebook.prototype.get_notebook_name = function () {
1175 Notebook.prototype.get_notebook_name = function () {
1175 return this.notebook_name;
1176 return this.notebook_name;
1176 };
1177 };
1177
1178
1178
1179
1179 Notebook.prototype.set_notebook_name = function (name) {
1180 Notebook.prototype.set_notebook_name = function (name) {
1180 this.notebook_name = name;
1181 this.notebook_name = name;
1181 };
1182 };
1182
1183
1183
1184
1184 Notebook.prototype.test_notebook_name = function (nbname) {
1185 Notebook.prototype.test_notebook_name = function (nbname) {
1185 nbname = nbname || '';
1186 nbname = nbname || '';
1186 if (this.notebook_name_blacklist_re.test(nbname) == false && nbname.length>0) {
1187 if (this.notebook_name_blacklist_re.test(nbname) == false && nbname.length>0) {
1187 return true;
1188 return true;
1188 } else {
1189 } else {
1189 return false;
1190 return false;
1190 };
1191 };
1191 };
1192 };
1192
1193
1193
1194
1194 Notebook.prototype.fromJSON = function (data) {
1195 Notebook.prototype.fromJSON = function (data) {
1195 var ncells = this.ncells();
1196 var ncells = this.ncells();
1196 var i;
1197 var i;
1197 for (i=0; i<ncells; i++) {
1198 for (i=0; i<ncells; i++) {
1198 // Always delete cell 0 as they get renumbered as they are deleted.
1199 // Always delete cell 0 as they get renumbered as they are deleted.
1199 this.delete_cell(0);
1200 this.delete_cell(0);
1200 };
1201 };
1201 // Save the metadata and name.
1202 // Save the metadata and name.
1202 this.metadata = data.metadata;
1203 this.metadata = data.metadata;
1203 this.notebook_name = data.metadata.name;
1204 this.notebook_name = data.metadata.name;
1204 // Only handle 1 worksheet for now.
1205 // Only handle 1 worksheet for now.
1205 var worksheet = data.worksheets[0];
1206 var worksheet = data.worksheets[0];
1206 if (worksheet !== undefined) {
1207 if (worksheet !== undefined) {
1207 var new_cells = worksheet.cells;
1208 var new_cells = worksheet.cells;
1208 ncells = new_cells.length;
1209 ncells = new_cells.length;
1209 var cell_data = null;
1210 var cell_data = null;
1210 var new_cell = null;
1211 var new_cell = null;
1211 for (i=0; i<ncells; i++) {
1212 for (i=0; i<ncells; i++) {
1212 cell_data = new_cells[i];
1213 cell_data = new_cells[i];
1213 // VERSIONHACK: plaintext -> raw
1214 // VERSIONHACK: plaintext -> raw
1214 // handle never-released plaintext name for raw cells
1215 // handle never-released plaintext name for raw cells
1215 if (cell_data.cell_type === 'plaintext'){
1216 if (cell_data.cell_type === 'plaintext'){
1216 cell_data.cell_type = 'raw';
1217 cell_data.cell_type = 'raw';
1217 }
1218 }
1218
1219
1219 new_cell = this.insert_cell_below(cell_data.cell_type);
1220 new_cell = this.insert_cell_below(cell_data.cell_type);
1220 new_cell.fromJSON(cell_data);
1221 new_cell.fromJSON(cell_data);
1221 };
1222 };
1222 };
1223 };
1223 };
1224 };
1224
1225
1225
1226
1226 Notebook.prototype.toJSON = function () {
1227 Notebook.prototype.toJSON = function () {
1227 var cells = this.get_cells();
1228 var cells = this.get_cells();
1228 var ncells = cells.length;
1229 var ncells = cells.length;
1229 cell_array = new Array(ncells);
1230 cell_array = new Array(ncells);
1230 for (var i=0; i<ncells; i++) {
1231 for (var i=0; i<ncells; i++) {
1231 cell_array[i] = cells[i].toJSON();
1232 cell_array[i] = cells[i].toJSON();
1232 };
1233 };
1233 data = {
1234 data = {
1234 // Only handle 1 worksheet for now.
1235 // Only handle 1 worksheet for now.
1235 worksheets : [{cells:cell_array}],
1236 worksheets : [{cells:cell_array}],
1236 metadata : this.metadata
1237 metadata : this.metadata
1237 };
1238 };
1238 return data;
1239 return data;
1239 };
1240 };
1240
1241
1241 Notebook.prototype.save_notebook = function () {
1242 Notebook.prototype.save_notebook = function () {
1242 // We may want to move the name/id/nbformat logic inside toJSON?
1243 // We may want to move the name/id/nbformat logic inside toJSON?
1243 var data = this.toJSON();
1244 var data = this.toJSON();
1244 data.metadata.name = this.notebook_name;
1245 data.metadata.name = this.notebook_name;
1245 data.nbformat = this.nbformat;
1246 data.nbformat = this.nbformat;
1246 // We do the call with settings so we can set cache to false.
1247 // We do the call with settings so we can set cache to false.
1247 var settings = {
1248 var settings = {
1248 processData : false,
1249 processData : false,
1249 cache : false,
1250 cache : false,
1250 type : "PUT",
1251 type : "PUT",
1251 data : JSON.stringify(data),
1252 data : JSON.stringify(data),
1252 headers : {'Content-Type': 'application/json'},
1253 headers : {'Content-Type': 'application/json'},
1253 success : $.proxy(this.save_notebook_success,this),
1254 success : $.proxy(this.save_notebook_success,this),
1254 error : $.proxy(this.save_notebook_error,this)
1255 error : $.proxy(this.save_notebook_error,this)
1255 };
1256 };
1256 $([IPython.events]).trigger('notebook_saving.Notebook');
1257 $([IPython.events]).trigger('notebook_saving.Notebook');
1257 var url = $('body').data('baseProjectUrl') + 'notebooks/' + this.notebook_id;
1258 var url = $('body').data('baseProjectUrl') + 'notebooks/' + this.notebook_id;
1258 $.ajax(url, settings);
1259 $.ajax(url, settings);
1259 };
1260 };
1260
1261
1261
1262
1262 Notebook.prototype.save_notebook_success = function (data, status, xhr) {
1263 Notebook.prototype.save_notebook_success = function (data, status, xhr) {
1263 this.dirty = false;
1264 this.dirty = false;
1264 $([IPython.events]).trigger('notebook_saved.Notebook');
1265 $([IPython.events]).trigger('notebook_saved.Notebook');
1265 };
1266 };
1266
1267
1267
1268
1268 Notebook.prototype.save_notebook_error = function (xhr, status, error_msg) {
1269 Notebook.prototype.save_notebook_error = function (xhr, status, error_msg) {
1269 $([IPython.events]).trigger('notebook_save_failed.Notebook');
1270 $([IPython.events]).trigger('notebook_save_failed.Notebook');
1270 };
1271 };
1271
1272
1272
1273
1273 Notebook.prototype.load_notebook = function (notebook_id) {
1274 Notebook.prototype.load_notebook = function (notebook_id) {
1274 var that = this;
1275 var that = this;
1275 this.notebook_id = notebook_id;
1276 this.notebook_id = notebook_id;
1276 // We do the call with settings so we can set cache to false.
1277 // We do the call with settings so we can set cache to false.
1277 var settings = {
1278 var settings = {
1278 processData : false,
1279 processData : false,
1279 cache : false,
1280 cache : false,
1280 type : "GET",
1281 type : "GET",
1281 dataType : "json",
1282 dataType : "json",
1282 success : $.proxy(this.load_notebook_success,this),
1283 success : $.proxy(this.load_notebook_success,this),
1283 error : $.proxy(this.load_notebook_error,this),
1284 error : $.proxy(this.load_notebook_error,this),
1284 };
1285 };
1285 $([IPython.events]).trigger('notebook_loading.Notebook');
1286 $([IPython.events]).trigger('notebook_loading.Notebook');
1286 var url = $('body').data('baseProjectUrl') + 'notebooks/' + this.notebook_id;
1287 var url = $('body').data('baseProjectUrl') + 'notebooks/' + this.notebook_id;
1287 $.ajax(url, settings);
1288 $.ajax(url, settings);
1288 };
1289 };
1289
1290
1290
1291
1291 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1292 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1292 this.fromJSON(data);
1293 this.fromJSON(data);
1293 if (this.ncells() === 0) {
1294 if (this.ncells() === 0) {
1294 this.insert_cell_below('code');
1295 this.insert_cell_below('code');
1295 };
1296 };
1296 this.dirty = false;
1297 this.dirty = false;
1297 if (! this.read_only) {
1298 if (! this.read_only) {
1298 this.start_kernel();
1299 this.start_kernel();
1299 }
1300 }
1300 this.select(0);
1301 this.select(0);
1301 this.scroll_to_top();
1302 this.scroll_to_top();
1302 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1303 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1303 msg = "This notebook has been converted from an older " +
1304 msg = "This notebook has been converted from an older " +
1304 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1305 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1305 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1306 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1306 "newer notebook format will be used and older verions of IPython " +
1307 "newer notebook format will be used and older verions of IPython " +
1307 "may not be able to read it. To keep the older version, close the " +
1308 "may not be able to read it. To keep the older version, close the " +
1308 "notebook without saving it.";
1309 "notebook without saving it.";
1309 var dialog = $('<div/>');
1310 var dialog = $('<div/>');
1310 dialog.html(msg);
1311 dialog.html(msg);
1311 this.element.append(dialog);
1312 this.element.append(dialog);
1312 dialog.dialog({
1313 dialog.dialog({
1313 resizable: false,
1314 resizable: false,
1314 modal: true,
1315 modal: true,
1315 title: "Notebook converted",
1316 title: "Notebook converted",
1316 closeText: "",
1317 closeText: "",
1317 close: function(event, ui) {$(this).dialog('destroy').remove();},
1318 close: function(event, ui) {$(this).dialog('destroy').remove();},
1318 buttons : {
1319 buttons : {
1319 "OK": function () {
1320 "OK": function () {
1320 $(this).dialog('close');
1321 $(this).dialog('close');
1321 }
1322 }
1322 },
1323 },
1323 width: 400
1324 width: 400
1324 });
1325 });
1325 }
1326 }
1326 $([IPython.events]).trigger('notebook_loaded.Notebook');
1327 $([IPython.events]).trigger('notebook_loaded.Notebook');
1327 };
1328 };
1328
1329
1329
1330
1330 Notebook.prototype.load_notebook_error = function (xhr, textStatus, errorThrow) {
1331 Notebook.prototype.load_notebook_error = function (xhr, textStatus, errorThrow) {
1331 if (xhr.status === 500) {
1332 if (xhr.status === 500) {
1332 msg = "An error occurred while loading this notebook. Most likely " +
1333 msg = "An error occurred while loading this notebook. Most likely " +
1333 "this notebook is in a newer format than is supported by this " +
1334 "this notebook is in a newer format than is supported by this " +
1334 "version of IPython. This version can load notebook formats " +
1335 "version of IPython. This version can load notebook formats " +
1335 "v"+this.nbformat+" or earlier.";
1336 "v"+this.nbformat+" or earlier.";
1336 var dialog = $('<div/>');
1337 var dialog = $('<div/>');
1337 dialog.html(msg);
1338 dialog.html(msg);
1338 this.element.append(dialog);
1339 this.element.append(dialog);
1339 dialog.dialog({
1340 dialog.dialog({
1340 resizable: false,
1341 resizable: false,
1341 modal: true,
1342 modal: true,
1342 title: "Error loading notebook",
1343 title: "Error loading notebook",
1343 closeText: "",
1344 closeText: "",
1344 close: function(event, ui) {$(this).dialog('destroy').remove();},
1345 close: function(event, ui) {$(this).dialog('destroy').remove();},
1345 buttons : {
1346 buttons : {
1346 "OK": function () {
1347 "OK": function () {
1347 $(this).dialog('close');
1348 $(this).dialog('close');
1348 }
1349 }
1349 },
1350 },
1350 width: 400
1351 width: 400
1351 });
1352 });
1352 }
1353 }
1353 }
1354 }
1354
1355
1355 IPython.Notebook = Notebook;
1356 IPython.Notebook = Notebook;
1356
1357
1357
1358
1358 return IPython;
1359 return IPython;
1359
1360
1360 }(IPython));
1361 }(IPython));
1361
1362
@@ -1,120 +1,122
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 // Utilities
9 // Utilities
10 //============================================================================
10 //============================================================================
11
11
12 IPython.namespace('IPython.utils');
12 IPython.namespace('IPython.utils');
13
13
14 IPython.utils = (function (IPython) {
14 IPython.utils = (function (IPython) {
15
15
16 var uuid = function () {
16 var uuid = function () {
17 // http://www.ietf.org/rfc/rfc4122.txt
17 // http://www.ietf.org/rfc/rfc4122.txt
18 var s = [];
18 var s = [];
19 var hexDigits = "0123456789ABCDEF";
19 var hexDigits = "0123456789ABCDEF";
20 for (var i = 0; i < 32; i++) {
20 for (var i = 0; i < 32; i++) {
21 s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
21 s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
22 }
22 }
23 s[12] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
23 s[12] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
24 s[16] = hexDigits.substr((s[16] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
24 s[16] = hexDigits.substr((s[16] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
25
25
26 var uuid = s.join("");
26 var uuid = s.join("");
27 return uuid;
27 return uuid;
28 };
28 };
29
29
30
30
31 //Fix raw text to parse correctly in crazy XML
31 //Fix raw text to parse correctly in crazy XML
32 function xmlencode(string) {
32 function xmlencode(string) {
33 return string.replace(/\&/g,'&'+'amp;')
33 return string.replace(/\&/g,'&'+'amp;')
34 .replace(/</g,'&'+'lt;')
34 .replace(/</g,'&'+'lt;')
35 .replace(/>/g,'&'+'gt;')
35 .replace(/>/g,'&'+'gt;')
36 .replace(/\'/g,'&'+'apos;')
36 .replace(/\'/g,'&'+'apos;')
37 .replace(/\"/g,'&'+'quot;')
37 .replace(/\"/g,'&'+'quot;')
38 .replace(/`/g,'&'+'#96;');
38 .replace(/`/g,'&'+'#96;');
39 }
39 }
40
40
41
41
42 //Map from terminal commands to CSS classes
42 //Map from terminal commands to CSS classes
43 ansi_colormap = {
43 ansi_colormap = {
44 "30":"ansiblack", "31":"ansired",
44 "30":"ansiblack", "31":"ansired",
45 "32":"ansigreen", "33":"ansiyellow",
45 "32":"ansigreen", "33":"ansiyellow",
46 "34":"ansiblue", "35":"ansipurple","36":"ansicyan",
46 "34":"ansiblue", "35":"ansipurple","36":"ansicyan",
47 "37":"ansigrey", "01":"ansibold"
47 "37":"ansigrey", "01":"ansibold"
48 };
48 };
49
49
50 // Transform ANI color escape codes into HTML <span> tags with css
50 // Transform ANI color escape codes into HTML <span> tags with css
51 // classes listed in the above ansi_colormap object. The actual color used
51 // classes listed in the above ansi_colormap object. The actual color used
52 // are set in the css file.
52 // are set in the css file.
53 function fixConsole(txt) {
53 function fixConsole(txt) {
54 txt = xmlencode(txt);
54 txt = xmlencode(txt);
55 var re = /\033\[([\dA-Fa-f;]*?)m/;
55 var re = /\033\[([\dA-Fa-f;]*?)m/;
56 var opened = false;
56 var opened = false;
57 var cmds = [];
57 var cmds = [];
58 var opener = "";
58 var opener = "";
59 var closer = "";
59 var closer = "";
60 // \r does nothing, so shouldn't be included
60 // \r does nothing, so shouldn't be included
61 txt = txt.replace('\r', '');
61 txt = txt.replace('\r', '');
62 while (re.test(txt)) {
62 while (re.test(txt)) {
63 var cmds = txt.match(re)[1].split(";");
63 var cmds = txt.match(re)[1].split(";");
64 closer = opened?"</span>":"";
64 closer = opened?"</span>":"";
65 opened = cmds.length > 1 || cmds[0] != 0;
65 opened = cmds.length > 1 || cmds[0] != 0;
66 var rep = [];
66 var rep = [];
67 for (var i in cmds)
67 for (var i in cmds)
68 if (typeof(ansi_colormap[cmds[i]]) != "undefined")
68 if (typeof(ansi_colormap[cmds[i]]) != "undefined")
69 rep.push(ansi_colormap[cmds[i]]);
69 rep.push(ansi_colormap[cmds[i]]);
70 opener = rep.length > 0?"<span class=\""+rep.join(" ")+"\">":"";
70 opener = rep.length > 0?"<span class=\""+rep.join(" ")+"\">":"";
71 txt = txt.replace(re, closer + opener);
71 txt = txt.replace(re, closer + opener);
72 }
72 }
73 if (opened) txt += "</span>";
73 if (opened) txt += "</span>";
74 return txt;
74 return txt;
75 }
75 }
76
76
77
77
78 grow = function(element) {
78 grow = function(element) {
79 // Grow the cell by hand. This is used upon reloading from JSON, when the
79 // Grow the cell by hand. This is used upon reloading from JSON, when the
80 // autogrow handler is not called.
80 // autogrow handler is not called.
81 var dom = element.get(0);
81 var dom = element.get(0);
82 var lines_count = 0;
82 var lines_count = 0;
83 // modified split rule from
83 // modified split rule from
84 // http://stackoverflow.com/questions/2035910/how-to-get-the-number-of-lines-in-a-textarea/2036424#2036424
84 // http://stackoverflow.com/questions/2035910/how-to-get-the-number-of-lines-in-a-textarea/2036424#2036424
85 var lines = dom.value.split(/\r|\r\n|\n/);
85 var lines = dom.value.split(/\r|\r\n|\n/);
86 lines_count = lines.length;
86 lines_count = lines.length;
87 if (lines_count >= 1) {
87 if (lines_count >= 1) {
88 dom.rows = lines_count;
88 dom.rows = lines_count;
89 } else {
89 } else {
90 dom.rows = 1;
90 dom.rows = 1;
91 }
91 }
92 };
92 };
93
93
94 // some keycodes that seem to be platform/browser independant
94 // some keycodes that seem to be platform/browser independant
95 var keycodes ={
95 var keycodes ={
96 backspace: 8,
96 backspace: 8,
97 tab : 9,
97 tab : 9,
98 enter : 13,
98 enter : 13,
99 shift : 16,
99 shift : 16,
100 ctrl : 17,
101 control : 17,
100 esc : 27,
102 esc : 27,
101 space : 32,
103 space : 32,
102 pgUp : 33,
104 pgUp : 33,
103 pgDown : 34,
105 pgDown : 34,
104 leftArrow: 37,
106 leftArrow: 37,
105 left : 37,
107 left : 37,
106 upArrow : 38,
108 upArrow : 38,
107 rightArrow:39,
109 rightArrow:39,
108 right : 39,
110 right : 39,
109 downArrow: 40,
111 downArrow: 40,
110 };
112 };
111
113
112 return {
114 return {
113 uuid : uuid,
115 uuid : uuid,
114 fixConsole : fixConsole,
116 fixConsole : fixConsole,
115 keycodes : keycodes,
117 keycodes : keycodes,
116 grow : grow,
118 grow : grow,
117 };
119 };
118
120
119 }(IPython));
121 }(IPython));
120
122
General Comments 0
You need to be logged in to leave comments. Login now