##// END OF EJS Templates
Merge pull request #1325 from Carreau/fix-tooltip-showpager...
Brian E. Granger -
r5968:bf552392 merge
parent child Browse files
Show More
@@ -1,850 +1,850
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // CodeCell
10 10 //============================================================================
11 11
12 12 var IPython = (function (IPython) {
13 13
14 14 var utils = IPython.utils;
15 15
16 16 var CodeCell = function (notebook) {
17 17 this.code_mirror = null;
18 18 this.input_prompt_number = ' ';
19 19 this.is_completing = false;
20 20 this.completion_cursor = null;
21 21 this.outputs = [];
22 22 this.collapsed = false;
23 23 this.tooltip_timeout = null;
24 24 IPython.Cell.apply(this, arguments);
25 25 };
26 26
27 27
28 28 CodeCell.prototype = new IPython.Cell();
29 29
30 30
31 31 CodeCell.prototype.create_element = function () {
32 32 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell vbox');
33 33 cell.attr('tabindex','2');
34 34 var input = $('<div></div>').addClass('input hbox');
35 35 input.append($('<div/>').addClass('prompt input_prompt'));
36 36 var input_area = $('<div/>').addClass('input_area box-flex1');
37 37 this.code_mirror = CodeMirror(input_area.get(0), {
38 38 indentUnit : 4,
39 39 mode: 'python',
40 40 theme: 'ipython',
41 41 readOnly: this.read_only,
42 42 onKeyEvent: $.proxy(this.handle_codemirror_keyevent,this)
43 43 });
44 44 input.append(input_area);
45 45 var output = $('<div></div>').addClass('output vbox');
46 46 cell.append(input).append(output);
47 47 this.element = cell;
48 48 this.collapse();
49 49 };
50 50
51 51 //TODO, try to diminish the number of parameters.
52 52 CodeCell.prototype.request_tooltip_after_time = function (pre_cursor,time){
53 53 var that = this;
54 54 if (pre_cursor === "" || pre_cursor === "(" ) {
55 55 // don't do anything if line beggin with '(' or is empty
56 56 } else {
57 57 // Will set a timer to request tooltip in `time`
58 58 that.tooltip_timeout = setTimeout(function(){
59 59 IPython.notebook.request_tool_tip(that, pre_cursor)
60 60 },time);
61 61 }
62 62 };
63 63
64 64 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
65 65 // This method gets called in CodeMirror's onKeyDown/onKeyPress
66 66 // handlers and is used to provide custom key handling. Its return
67 67 // value is used to determine if CodeMirror should ignore the event:
68 68 // true = ignore, false = don't ignore.
69 69
70 70 if (this.read_only){
71 71 return false;
72 72 }
73 73
74 74 // note that we are comparing and setting the time to wait at each key press.
75 75 // a better wqy might be to generate a new function on each time change and
76 76 // assign it to CodeCell.prototype.request_tooltip_after_time
77 77 tooltip_wait_time = this.notebook.time_before_tooltip;
78 78 tooltip_on_tab = this.notebook.tooltip_on_tab;
79 79 var that = this;
80 80 // whatever key is pressed, first, cancel the tooltip request before
81 81 // they are sent, and remove tooltip if any
82 82 if(event.type === 'keydown' ){
83 83 that.remove_and_cancel_tooltip();
84 84 }
85 85
86 86 if (event.keyCode === 13 && (event.shiftKey || event.ctrlKey)) {
87 87 // Always ignore shift-enter in CodeMirror as we handle it.
88 88 return true;
89 89 } else if (event.which === 40 && event.type === 'keypress' && tooltip_wait_time >= 0) {
90 90 // triger aon keypress (!) otherwise inconsistent event.which depending on plateform
91 91 // browser and keyboard layout !
92 92 // Pressing '(' , request tooltip, don't forget to reappend it
93 93 var cursor = editor.getCursor();
94 94 var pre_cursor = editor.getRange({line:cursor.line,ch:0},cursor).trim()+'(';
95 95 that.request_tooltip_after_time(pre_cursor,tooltip_wait_time);
96 96 } else if (event.which === 38) {
97 97 // If we are not at the top, let CM handle the up arrow and
98 98 // prevent the global keydown handler from handling it.
99 99 if (!that.at_top()) {
100 100 event.stop();
101 101 return false;
102 102 } else {
103 103 return true;
104 104 };
105 105 } else if (event.which === 40) {
106 106 // If we are not at the bottom, let CM handle the down arrow and
107 107 // prevent the global keydown handler from handling it.
108 108 if (!that.at_bottom()) {
109 109 event.stop();
110 110 return false;
111 111 } else {
112 112 return true;
113 113 };
114 114 } else if (event.keyCode === 9 && event.type == 'keydown') {
115 115 // Tab completion.
116 116 var cur = editor.getCursor();
117 117 //Do not trim here because of tooltip
118 118 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
119 119 if (pre_cursor.trim() === "") {
120 120 // Don't autocomplete if the part of the line before the cursor
121 121 // is empty. In this case, let CodeMirror handle indentation.
122 122 return false;
123 123 } else if ((pre_cursor.substr(-1) === "("|| pre_cursor.substr(-1) === " ") && tooltip_on_tab ) {
124 124 that.request_tooltip_after_time(pre_cursor,0);
125 125 } else {
126 126 pre_cursor.trim();
127 127 // Autocomplete the current line.
128 128 event.stop();
129 129 var line = editor.getLine(cur.line);
130 130 this.is_completing = true;
131 131 this.completion_cursor = cur;
132 132 IPython.notebook.complete_cell(this, line, cur.ch);
133 133 return true;
134 134 }
135 135 } else if (event.keyCode === 8 && event.type == 'keydown') {
136 136 // If backspace and the line ends with 4 spaces, remove them.
137 137 var cur = editor.getCursor();
138 138 var line = editor.getLine(cur.line);
139 139 var ending = line.slice(-4);
140 140 if (ending === ' ') {
141 141 editor.replaceRange('',
142 142 {line: cur.line, ch: cur.ch-4},
143 143 {line: cur.line, ch: cur.ch}
144 144 );
145 145 event.stop();
146 146 return true;
147 147 } else {
148 148 return false;
149 149 }
150 150 } else if (event.keyCode === 76 && event.ctrlKey && event.shiftKey
151 151 && event.type == 'keydown') {
152 152 // toggle line numbers with Ctrl-Shift-L
153 153 this.toggle_line_numbers();
154 154 }
155 155 else {
156 156 // keypress/keyup also trigger on TAB press, and we don't want to
157 157 // use those to disable tab completion.
158 158 if (this.is_completing && event.keyCode !== 9) {
159 159 var ed_cur = editor.getCursor();
160 160 var cc_cur = this.completion_cursor;
161 161 if (ed_cur.line !== cc_cur.line || ed_cur.ch !== cc_cur.ch) {
162 162 this.is_completing = false;
163 163 this.completion_cursor = null;
164 164 }
165 165 }
166 166 return false;
167 167 };
168 168 return false;
169 169 };
170 170
171 171 CodeCell.prototype.remove_and_cancel_tooltip = function() {
172 172 // note that we don't handle closing directly inside the calltip
173 173 // as in the completer, because it is not focusable, so won't
174 174 // get the event.
175 175 if (this.tooltip_timeout != null){
176 176 clearTimeout(this.tooltip_timeout);
177 177 $('#tooltip').remove();
178 178 this.tooltip_timeout = null;
179 179 }
180 180 }
181 181
182 182 CodeCell.prototype.finish_tooltip = function (reply) {
183 183 // Extract call tip data; the priority is call, init, main.
184 184 defstring = reply.call_def;
185 185 if (defstring == null) { defstring = reply.init_definition; }
186 186 if (defstring == null) { defstring = reply.definition; }
187 187
188 188 docstring = reply.call_docstring;
189 189 if (docstring == null) { docstring = reply.init_docstring; }
190 190 if (docstring == null) { docstring = reply.docstring; }
191 191 if (docstring == null) { docstring = "<empty docstring>"; }
192 192
193 193 name=reply.name;
194 194
195 195 var that = this;
196 196 var tooltip = $('<div/>').attr('id', 'tooltip').addClass('tooltip');
197 197 // remove to have the tooltip not Limited in X and Y
198 198 tooltip.addClass('smalltooltip');
199 199 var pre=$('<pre/>').html(utils.fixConsole(docstring));
200 200 var expandlink=$('<a/>').attr('href',"#");
201 201 expandlink.addClass("ui-corner-all"); //rounded corner
202 202 expandlink.attr('role',"button");
203 203 //expandlink.addClass('ui-button');
204 204 //expandlink.addClass('ui-state-default');
205 205 var expandspan=$('<span/>').text('Expand');
206 206 expandspan.addClass('ui-icon');
207 207 expandspan.addClass('ui-icon-plus');
208 208 expandlink.append(expandspan);
209 209 expandlink.attr('id','expanbutton');
210 210 expandlink.click(function(){
211 211 tooltip.removeClass('smalltooltip');
212 212 tooltip.addClass('bigtooltip');
213 213 $('#expanbutton').remove();
214 214 setTimeout(function(){that.code_mirror.focus();}, 50);
215 215 });
216 216 var morelink=$('<a/>').attr('href',"#");
217 217 morelink.attr('role',"button");
218 218 morelink.addClass('ui-button');
219 219 //morelink.addClass("ui-corner-all"); //rounded corner
220 220 //morelink.addClass('ui-state-default');
221 221 var morespan=$('<span/>').text('Open in Pager');
222 222 morespan.addClass('ui-icon');
223 223 morespan.addClass('ui-icon-arrowstop-l-n');
224 224 morelink.append(morespan);
225 225 morelink.click(function(){
226 226 var msg_id = IPython.notebook.kernel.execute(name+"?");
227 IPython.notebook.msg_cell_map[msg_id] = IPython.notebook.selected_cell().cell_id;
227 IPython.notebook.msg_cell_map[msg_id] = IPython.notebook.get_selected_cell().cell_id;
228 228 that.remove_and_cancel_tooltip();
229 229 setTimeout(function(){that.code_mirror.focus();}, 50);
230 230 });
231 231
232 232 var closelink=$('<a/>').attr('href',"#");
233 233 closelink.attr('role',"button");
234 234 closelink.addClass('ui-button');
235 235 //closelink.addClass("ui-corner-all"); //rounded corner
236 236 //closelink.adClass('ui-state-default'); // grey background and blue cross
237 237 var closespan=$('<span/>').text('Close');
238 238 closespan.addClass('ui-icon');
239 239 closespan.addClass('ui-icon-close');
240 240 closelink.append(closespan);
241 241 closelink.click(function(){
242 242 that.remove_and_cancel_tooltip();
243 243 setTimeout(function(){that.code_mirror.focus();}, 50);
244 244 });
245 245 //construct the tooltip
246 246 tooltip.append(closelink);
247 247 tooltip.append(expandlink);
248 248 tooltip.append(morelink);
249 249 if(defstring){
250 250 defstring_html = $('<pre/>').html(utils.fixConsole(defstring));
251 251 tooltip.append(defstring_html);
252 252 }
253 253 tooltip.append(pre);
254 254 var pos = this.code_mirror.cursorCoords();
255 255 tooltip.css('left',pos.x+'px');
256 256 tooltip.css('top',pos.yBot+'px');
257 257 $('body').append(tooltip);
258 258
259 259 // issues with cross-closing if multiple tooltip in less than 5sec
260 260 // keep it comented for now
261 261 // setTimeout(that.remove_and_cancel_tooltip, 5000);
262 262 };
263 263
264 264 // As you type completer
265 265 CodeCell.prototype.finish_completing = function (matched_text, matches) {
266 266 //return if not completing or nothing to complete
267 267 if (!this.is_completing || matches.length === 0) {return;}
268 268
269 269 // for later readability
270 270 var key = { tab:9,
271 271 esc:27,
272 272 backspace:8,
273 273 space:32,
274 274 shift:16,
275 275 enter:13,
276 276 // _ is 95
277 277 isCompSymbol : function (code)
278 278 {
279 279 return (code > 64 && code <= 90)
280 280 || (code >= 97 && code <= 122)
281 281 || (code == 95)
282 282 },
283 283 dismissAndAppend : function (code)
284 284 {
285 285 chararr = '()[]+-/\\. ,=*'.split("");
286 286 codearr = chararr.map(function(x){return x.charCodeAt(0)});
287 287 return jQuery.inArray(code, codearr) != -1;
288 288 }
289 289
290 290 }
291 291
292 292 // smart completion, sort kwarg ending with '='
293 293 var newm = new Array();
294 294 if(this.notebook.smart_completer)
295 295 {
296 296 kwargs = new Array();
297 297 other = new Array();
298 298 for(var i = 0 ; i<matches.length ; ++i){
299 299 if(matches[i].substr(-1) === '='){
300 300 kwargs.push(matches[i]);
301 301 }else{other.push(matches[i]);}
302 302 }
303 303 newm = kwargs.concat(other);
304 304 matches = newm;
305 305 }
306 306 // end sort kwargs
307 307
308 308 // give common prefix of a array of string
309 309 function sharedStart(A){
310 310 if(A.length == 1){return A[0]}
311 311 if(A.length > 1 ){
312 312 var tem1, tem2, s, A = A.slice(0).sort();
313 313 tem1 = A[0];
314 314 s = tem1.length;
315 315 tem2 = A.pop();
316 316 while(s && tem2.indexOf(tem1) == -1){
317 317 tem1 = tem1.substring(0, --s);
318 318 }
319 319 return tem1;
320 320 }
321 321 return "";
322 322 }
323 323
324 324
325 325 //try to check if the user is typing tab at least twice after a word
326 326 // and completion is "done"
327 327 fallback_on_tooltip_after = 2
328 328 if(matches.length == 1 && matched_text === matches[0])
329 329 {
330 330 if(this.npressed >fallback_on_tooltip_after && this.prevmatch==matched_text)
331 331 {
332 332 this.request_tooltip_after_time(matched_text+'(',0);
333 333 return;
334 334 }
335 335 this.prevmatch = matched_text
336 336 this.npressed = this.npressed+1;
337 337 }
338 338 else
339 339 {
340 340 this.prevmatch = "";
341 341 this.npressed = 0;
342 342 }
343 343 // end fallback on tooltip
344 344 //==================================
345 345 // Real completion logic start here
346 346 var that = this;
347 347 var cur = this.completion_cursor;
348 348 var done = false;
349 349
350 350 // call to dismmiss the completer
351 351 var close = function () {
352 352 if (done) return;
353 353 done = true;
354 354 if (complete != undefined)
355 355 {complete.remove();}
356 356 that.is_completing = false;
357 357 that.completion_cursor = null;
358 358 };
359 359
360 360 // update codemirror with the typed text
361 361 prev = matched_text
362 362 var update = function (inserted_text, event) {
363 363 that.code_mirror.replaceRange(
364 364 inserted_text,
365 365 {line: cur.line, ch: (cur.ch-matched_text.length)},
366 366 {line: cur.line, ch: (cur.ch+prev.length-matched_text.length)}
367 367 );
368 368 prev = inserted_text
369 369 if(event != null){
370 370 event.stopPropagation();
371 371 event.preventDefault();
372 372 }
373 373 };
374 374 // insert the given text and exit the completer
375 375 var insert = function (selected_text, event) {
376 376 update(selected_text)
377 377 close();
378 378 setTimeout(function(){that.code_mirror.focus();}, 50);
379 379 };
380 380
381 381 // insert the curent highlited selection and exit
382 382 var pick = function () {
383 383 insert(select.val()[0],null);
384 384 };
385 385
386 386
387 387 // Define function to clear the completer, refill it with the new
388 388 // matches, update the pseuso typing field. autopick insert match if
389 389 // only one left, in no matches (anymore) dismiss itself by pasting
390 390 // what the user have typed until then
391 391 var complete_with = function(matches,typed_text,autopick,event)
392 392 {
393 393 // If autopick an only one match, past.
394 394 // Used to 'pick' when pressing tab
395 395 if (matches.length < 1) {
396 396 insert(typed_text,event);
397 397 if(event != null){
398 398 event.stopPropagation();
399 399 event.preventDefault();
400 400 }
401 401 } else if (autopick && matches.length == 1) {
402 402 insert(matches[0],event);
403 403 if(event != null){
404 404 event.stopPropagation();
405 405 event.preventDefault();
406 406 }
407 407 }
408 408 //clear the previous completion if any
409 409 update(typed_text,event);
410 410 complete.children().children().remove();
411 411 $('#asyoutype').html("<b>"+matched_text+"</b>"+typed_text.substr(matched_text.length));
412 412 select = $('#asyoutypeselect');
413 413 for (var i = 0; i<matches.length; ++i) {
414 414 select.append($('<option/>').html(matches[i]));
415 415 }
416 416 select.children().first().attr('selected','true');
417 417 }
418 418
419 419 // create html for completer
420 420 var complete = $('<div/>').addClass('completions');
421 421 complete.attr('id','complete');
422 422 complete.append($('<p/>').attr('id', 'asyoutype').html('<b>fixed part</b>user part'));//pseudo input field
423 423
424 424 var select = $('<select/>').attr('multiple','true');
425 425 select.attr('id', 'asyoutypeselect')
426 426 select.attr('size',Math.min(10,matches.length));
427 427 var pos = this.code_mirror.cursorCoords();
428 428
429 429 // TODO: I propose to remove enough horizontal pixel
430 430 // to align the text later
431 431 complete.css('left',pos.x+'px');
432 432 complete.css('top',pos.yBot+'px');
433 433 complete.append(select);
434 434
435 435 $('body').append(complete);
436 436
437 437 // So a first actual completion. see if all the completion start wit
438 438 // the same letter and complete if necessary
439 439 fastForward = sharedStart(matches)
440 440 typed_characters = fastForward.substr(matched_text.length);
441 441 complete_with(matches,matched_text+typed_characters,true,null);
442 442 filterd = matches;
443 443 // Give focus to select, and make it filter the match as the user type
444 444 // by filtering the previous matches. Called by .keypress and .keydown
445 445 var downandpress = function (event,press_or_down) {
446 446 var code = event.which;
447 447 var autopick = false; // auto 'pick' if only one match
448 448 if (press_or_down === 0){
449 449 press = true; down = false; //Are we called from keypress or keydown
450 450 } else if (press_or_down == 1){
451 451 press = false; down = true;
452 452 }
453 453 if (code === key.shift) {
454 454 // nothing on Shift
455 455 return;
456 456 }
457 457 if (key.dismissAndAppend(code) && press) {
458 458 var newchar = String.fromCharCode(code);
459 459 typed_characters = typed_characters+newchar;
460 460 insert(matched_text+typed_characters,event);
461 461 return
462 462 }
463 463 if (code === key.enter) {
464 464 // Pressing ENTER will cause a pick
465 465 event.stopPropagation();
466 466 event.preventDefault();
467 467 pick();
468 468 } else if (code === 38 || code === 40) {
469 469 // We don't want the document keydown handler to handle UP/DOWN,
470 470 // but we want the default action.
471 471 event.stopPropagation();
472 472 } else if ( (code == key.backspace)||(code == key.tab && down) || press || key.isCompSymbol(code)){
473 473 if( key.isCompSymbol(code) && press)
474 474 {
475 475 var newchar = String.fromCharCode(code);
476 476 typed_characters = typed_characters+newchar;
477 477 } else if (code == key.tab) {
478 478 fastForward = sharedStart(filterd)
479 479 ffsub = fastForward.substr(matched_text.length+typed_characters.length);
480 480 typed_characters = typed_characters+ffsub;
481 481 autopick = true;
482 482 } else if (code == key.backspace && down) {
483 483 // cancel if user have erase everything, otherwise decrease
484 484 // what we filter with
485 485 event.preventDefault();
486 486 if (typed_characters.length <= 0)
487 487 {
488 488 insert(matched_text,event)
489 489 return
490 490 }
491 491 typed_characters = typed_characters.substr(0,typed_characters.length-1);
492 492 } else if (press && code != key.backspace && code != key.tab && code != 0){
493 493 insert(matched_text+typed_characters,event);
494 494 return
495 495 } else {
496 496 return
497 497 }
498 498 re = new RegExp("^"+"\%?"+matched_text+typed_characters,"");
499 499 filterd = matches.filter(function(x){return re.test(x)});
500 500 complete_with(filterd,matched_text+typed_characters,autopick,event);
501 501 } else if (code == key.esc) {
502 502 // dismiss the completer and go back to before invoking it
503 503 insert(matched_text,event);
504 504 } else if (press) { // abort only on .keypress or esc
505 505 }
506 506 }
507 507 select.keydown(function (event) {
508 508 downandpress(event,1)
509 509 });
510 510 select.keypress(function (event) {
511 511 downandpress(event,0)
512 512 });
513 513 // Double click also causes a pick.
514 514 // and bind the last actions.
515 515 select.dblclick(pick);
516 516 select.blur(close);
517 517 select.focus();
518 518 };
519 519
520 520
521 521 CodeCell.prototype.toggle_line_numbers = function () {
522 522 if (this.code_mirror.getOption('lineNumbers') == false) {
523 523 this.code_mirror.setOption('lineNumbers', true);
524 524 } else {
525 525 this.code_mirror.setOption('lineNumbers', false);
526 526 }
527 527 this.code_mirror.refresh();
528 528 };
529 529
530 530
531 531 CodeCell.prototype.select = function () {
532 532 IPython.Cell.prototype.select.apply(this);
533 533 // In some cases (inserting a new cell) we need a refresh before and
534 534 // after the focus. Not sure why this is the case.
535 535 this.code_mirror.refresh();
536 536 this.code_mirror.focus();
537 537 this.code_mirror.refresh();
538 538 };
539 539
540 540
541 541 CodeCell.prototype.select_all = function () {
542 542 var start = {line: 0, ch: 0};
543 543 var nlines = this.code_mirror.lineCount();
544 544 var last_line = this.code_mirror.getLine(nlines-1);
545 545 var end = {line: nlines-1, ch: last_line.length};
546 546 this.code_mirror.setSelection(start, end);
547 547 };
548 548
549 549
550 550 CodeCell.prototype.append_output = function (json) {
551 551 this.expand();
552 552 if (json.output_type === 'pyout') {
553 553 this.append_pyout(json);
554 554 } else if (json.output_type === 'pyerr') {
555 555 this.append_pyerr(json);
556 556 } else if (json.output_type === 'display_data') {
557 557 this.append_display_data(json);
558 558 } else if (json.output_type === 'stream') {
559 559 this.append_stream(json);
560 560 };
561 561 this.outputs.push(json);
562 562 };
563 563
564 564
565 565 CodeCell.prototype.create_output_area = function () {
566 566 var oa = $("<div/>").addClass("hbox output_area");
567 567 oa.append($('<div/>').addClass('prompt'));
568 568 return oa;
569 569 };
570 570
571 571
572 572 CodeCell.prototype.append_pyout = function (json) {
573 573 n = json.prompt_number || ' ';
574 574 var toinsert = this.create_output_area();
575 575 toinsert.find('div.prompt').addClass('output_prompt').html('Out[' + n + ']:');
576 576 this.append_mime_type(json, toinsert);
577 577 this.element.find('div.output').append(toinsert);
578 578 // If we just output latex, typeset it.
579 579 if ((json.latex !== undefined) || (json.html !== undefined)) {
580 580 this.typeset();
581 581 };
582 582 };
583 583
584 584
585 585 CodeCell.prototype.append_pyerr = function (json) {
586 586 var tb = json.traceback;
587 587 if (tb !== undefined && tb.length > 0) {
588 588 var s = '';
589 589 var len = tb.length;
590 590 for (var i=0; i<len; i++) {
591 591 s = s + tb[i] + '\n';
592 592 }
593 593 s = s + '\n';
594 594 var toinsert = this.create_output_area();
595 595 this.append_text(s, toinsert);
596 596 this.element.find('div.output').append(toinsert);
597 597 };
598 598 };
599 599
600 600
601 601 CodeCell.prototype.append_stream = function (json) {
602 602 // temporary fix: if stream undefined (json file written prior to this patch),
603 603 // default to most likely stdout:
604 604 if (json.stream == undefined){
605 605 json.stream = 'stdout';
606 606 }
607 607 var subclass = "output_"+json.stream;
608 608 if (this.outputs.length > 0){
609 609 // have at least one output to consider
610 610 var last = this.outputs[this.outputs.length-1];
611 611 if (last.output_type == 'stream' && json.stream == last.stream){
612 612 // latest output was in the same stream,
613 613 // so append directly into its pre tag
614 614 this.element.find('div.'+subclass).last().find('pre').append(json.text);
615 615 return;
616 616 }
617 617 }
618 618
619 619 // If we got here, attach a new div
620 620 var toinsert = this.create_output_area();
621 621 this.append_text(json.text, toinsert, "output_stream "+subclass);
622 622 this.element.find('div.output').append(toinsert);
623 623 };
624 624
625 625
626 626 CodeCell.prototype.append_display_data = function (json) {
627 627 var toinsert = this.create_output_area();
628 628 this.append_mime_type(json, toinsert);
629 629 this.element.find('div.output').append(toinsert);
630 630 // If we just output latex, typeset it.
631 631 if ( (json.latex !== undefined) || (json.html !== undefined) ) {
632 632 this.typeset();
633 633 };
634 634 };
635 635
636 636
637 637 CodeCell.prototype.append_mime_type = function (json, element) {
638 638 if (json.html !== undefined) {
639 639 this.append_html(json.html, element);
640 640 } else if (json.latex !== undefined) {
641 641 this.append_latex(json.latex, element);
642 642 } else if (json.svg !== undefined) {
643 643 this.append_svg(json.svg, element);
644 644 } else if (json.png !== undefined) {
645 645 this.append_png(json.png, element);
646 646 } else if (json.jpeg !== undefined) {
647 647 this.append_jpeg(json.jpeg, element);
648 648 } else if (json.text !== undefined) {
649 649 this.append_text(json.text, element);
650 650 };
651 651 };
652 652
653 653
654 654 CodeCell.prototype.append_html = function (html, element) {
655 655 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_html rendered_html");
656 656 toinsert.append(html);
657 657 element.append(toinsert);
658 658 };
659 659
660 660
661 661 CodeCell.prototype.append_text = function (data, element, extra_class) {
662 662 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_text");
663 663 if (extra_class){
664 664 toinsert.addClass(extra_class);
665 665 }
666 666 toinsert.append($("<pre/>").html(data));
667 667 element.append(toinsert);
668 668 };
669 669
670 670
671 671 CodeCell.prototype.append_svg = function (svg, element) {
672 672 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_svg");
673 673 toinsert.append(svg);
674 674 element.append(toinsert);
675 675 };
676 676
677 677
678 678 CodeCell.prototype.append_png = function (png, element) {
679 679 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_png");
680 680 toinsert.append($("<img/>").attr('src','data:image/png;base64,'+png));
681 681 element.append(toinsert);
682 682 };
683 683
684 684
685 685 CodeCell.prototype.append_jpeg = function (jpeg, element) {
686 686 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_jpeg");
687 687 toinsert.append($("<img/>").attr('src','data:image/jpeg;base64,'+jpeg));
688 688 element.append(toinsert);
689 689 };
690 690
691 691
692 692 CodeCell.prototype.append_latex = function (latex, element) {
693 693 // This method cannot do the typesetting because the latex first has to
694 694 // be on the page.
695 695 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_latex");
696 696 toinsert.append(latex);
697 697 element.append(toinsert);
698 698 };
699 699
700 700
701 701 CodeCell.prototype.clear_output = function (stdout, stderr, other) {
702 702 var output_div = this.element.find("div.output");
703 703 if (stdout && stderr && other){
704 704 // clear all, no need for logic
705 705 output_div.html("");
706 706 this.outputs = [];
707 707 return;
708 708 }
709 709 // remove html output
710 710 // each output_subarea that has an identifying class is in an output_area
711 711 // which is the element to be removed.
712 712 if (stdout){
713 713 output_div.find("div.output_stdout").parent().remove();
714 714 }
715 715 if (stderr){
716 716 output_div.find("div.output_stderr").parent().remove();
717 717 }
718 718 if (other){
719 719 output_div.find("div.output_subarea").not("div.output_stderr").not("div.output_stdout").parent().remove();
720 720 }
721 721
722 722 // remove cleared outputs from JSON list:
723 723 for (var i = this.outputs.length - 1; i >= 0; i--){
724 724 var out = this.outputs[i];
725 725 var output_type = out.output_type;
726 726 if (output_type == "display_data" && other){
727 727 this.outputs.splice(i,1);
728 728 }else if (output_type == "stream"){
729 729 if (stdout && out.stream == "stdout"){
730 730 this.outputs.splice(i,1);
731 731 }else if (stderr && out.stream == "stderr"){
732 732 this.outputs.splice(i,1);
733 733 }
734 734 }
735 735 }
736 736 };
737 737
738 738
739 739 CodeCell.prototype.clear_input = function () {
740 740 this.code_mirror.setValue('');
741 741 };
742 742
743 743
744 744 CodeCell.prototype.collapse = function () {
745 745 if (!this.collapsed) {
746 746 this.element.find('div.output').hide();
747 747 this.collapsed = true;
748 748 };
749 749 };
750 750
751 751
752 752 CodeCell.prototype.expand = function () {
753 753 if (this.collapsed) {
754 754 this.element.find('div.output').show();
755 755 this.collapsed = false;
756 756 };
757 757 };
758 758
759 759
760 760 CodeCell.prototype.toggle_output = function () {
761 761 if (this.collapsed) {
762 762 this.expand();
763 763 } else {
764 764 this.collapse();
765 765 };
766 766 };
767 767
768 768 CodeCell.prototype.set_input_prompt = function (number) {
769 769 var n = number || '&nbsp;';
770 770 this.input_prompt_number = n;
771 771 this.element.find('div.input_prompt').html('In&nbsp;[' + n + ']:');
772 772 };
773 773
774 774
775 775 CodeCell.prototype.get_text = function () {
776 776 return this.code_mirror.getValue();
777 777 };
778 778
779 779
780 780 CodeCell.prototype.set_text = function (code) {
781 781 return this.code_mirror.setValue(code);
782 782 };
783 783
784 784
785 785 CodeCell.prototype.at_top = function () {
786 786 var cursor = this.code_mirror.getCursor();
787 787 if (cursor.line === 0) {
788 788 return true;
789 789 } else {
790 790 return false;
791 791 }
792 792 };
793 793
794 794
795 795 CodeCell.prototype.at_bottom = function () {
796 796 var cursor = this.code_mirror.getCursor();
797 797 if (cursor.line === (this.code_mirror.lineCount()-1)) {
798 798 return true;
799 799 } else {
800 800 return false;
801 801 }
802 802 };
803 803
804 804
805 805 CodeCell.prototype.fromJSON = function (data) {
806 806 if (data.cell_type === 'code') {
807 807 if (data.input !== undefined) {
808 808 this.set_text(data.input);
809 809 }
810 810 if (data.prompt_number !== undefined) {
811 811 this.set_input_prompt(data.prompt_number);
812 812 } else {
813 813 this.set_input_prompt();
814 814 };
815 815 var len = data.outputs.length;
816 816 for (var i=0; i<len; i++) {
817 817 this.append_output(data.outputs[i]);
818 818 };
819 819 if (data.collapsed !== undefined) {
820 820 if (data.collapsed) {
821 821 this.collapse();
822 822 };
823 823 };
824 824 };
825 825 };
826 826
827 827
828 828 CodeCell.prototype.toJSON = function () {
829 829 var data = {};
830 830 data.input = this.get_text();
831 831 data.cell_type = 'code';
832 832 if (this.input_prompt_number !== ' ') {
833 833 data.prompt_number = this.input_prompt_number;
834 834 };
835 835 var outputs = [];
836 836 var len = this.outputs.length;
837 837 for (var i=0; i<len; i++) {
838 838 outputs[i] = this.outputs[i];
839 839 };
840 840 data.outputs = outputs;
841 841 data.language = 'python';
842 842 data.collapsed = this.collapsed;
843 843 return data;
844 844 };
845 845
846 846
847 847 IPython.CodeCell = CodeCell;
848 848
849 849 return IPython;
850 850 }(IPython));
@@ -1,1172 +1,1172
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // Notebook
10 10 //============================================================================
11 11
12 12 var IPython = (function (IPython) {
13 13
14 14 var utils = IPython.utils;
15 15
16 16 var Notebook = function (selector) {
17 17 this.read_only = IPython.read_only;
18 18 this.element = $(selector);
19 19 this.element.scroll();
20 20 this.element.data("notebook", this);
21 21 this.next_prompt_number = 1;
22 22 this.kernel = null;
23 23 this.clipboard = null;
24 24 this.paste_enabled = false;
25 25 this.dirty = false;
26 26 this.msg_cell_map = {};
27 27 this.metadata = {};
28 28 this.control_key_active = false;
29 29 this.style();
30 30 this.create_elements();
31 31 this.bind_events();
32 32 this.set_tooltipontab(true);
33 33 this.set_smartcompleter(true);
34 34 this.set_timebeforetooltip(1200);
35 35 };
36 36
37 37
38 38 Notebook.prototype.style = function () {
39 39 $('div#notebook').addClass('border-box-sizing');
40 40 };
41 41
42 42
43 43 Notebook.prototype.create_elements = function () {
44 44 // We add this end_space div to the end of the notebook div to:
45 45 // i) provide a margin between the last cell and the end of the notebook
46 46 // ii) to prevent the div from scrolling up when the last cell is being
47 47 // edited, but is too low on the page, which browsers will do automatically.
48 48 var that = this;
49 49 var end_space = $('<div/>').addClass('end_space').height("30%");
50 50 end_space.dblclick(function (e) {
51 51 if (that.read_only) return;
52 52 var ncells = that.ncells();
53 53 that.insert_cell_below('code',ncells-1);
54 54 });
55 55 this.element.append(end_space);
56 56 $('div#notebook').addClass('border-box-sizing');
57 57 };
58 58
59 59
60 60 Notebook.prototype.bind_events = function () {
61 61 var that = this;
62 62 $(document).keydown(function (event) {
63 63 // console.log(event);
64 64 if (that.read_only) return true;
65 65 if (event.which === 27) {
66 66 // Intercept escape at highest level to avoid closing
67 67 // websocket connection with firefox
68 68 event.preventDefault();
69 69 }
70 70 if (event.which === 38 && !event.shiftKey) {
71 71 var cell = that.get_selected_cell();
72 72 if (cell.at_top()) {
73 73 event.preventDefault();
74 74 that.select_prev();
75 75 };
76 76 } else if (event.which === 40 && !event.shiftKey) {
77 77 var cell = that.get_selected_cell();
78 78 if (cell.at_bottom()) {
79 79 event.preventDefault();
80 80 that.select_next();
81 81 };
82 82 } else if (event.which === 13 && event.shiftKey) {
83 83 that.execute_selected_cell();
84 84 return false;
85 85 } else if (event.which === 13 && event.ctrlKey) {
86 86 that.execute_selected_cell({terminal:true});
87 87 return false;
88 88 } else if (event.which === 77 && event.ctrlKey) {
89 89 that.control_key_active = true;
90 90 return false;
91 91 } else if (event.which === 88 && that.control_key_active) {
92 92 // Cut selected cell = x
93 93 that.cut_cell();
94 94 that.control_key_active = false;
95 95 return false;
96 96 } else if (event.which === 67 && that.control_key_active) {
97 97 // Copy selected cell = c
98 98 that.copy_cell();
99 99 that.control_key_active = false;
100 100 return false;
101 101 } else if (event.which === 86 && that.control_key_active) {
102 102 // Paste selected cell = v
103 103 that.paste_cell();
104 104 that.control_key_active = false;
105 105 return false;
106 106 } else if (event.which === 68 && that.control_key_active) {
107 107 // Delete selected cell = d
108 108 that.delete_cell();
109 109 that.control_key_active = false;
110 110 return false;
111 111 } else if (event.which === 65 && that.control_key_active) {
112 112 // Insert code cell above selected = a
113 113 that.insert_cell_above('code');
114 114 that.control_key_active = false;
115 115 return false;
116 116 } else if (event.which === 66 && that.control_key_active) {
117 117 // Insert code cell below selected = b
118 118 that.insert_cell_below('code');
119 119 that.control_key_active = false;
120 120 return false;
121 121 } else if (event.which === 89 && that.control_key_active) {
122 122 // To code = y
123 123 that.to_code();
124 124 that.control_key_active = false;
125 125 return false;
126 126 } else if (event.which === 77 && that.control_key_active) {
127 127 // To markdown = m
128 128 that.to_markdown();
129 129 that.control_key_active = false;
130 130 return false;
131 131 } else if (event.which === 84 && that.control_key_active) {
132 132 // Toggle output = t
133 133 that.toggle_output();
134 134 that.control_key_active = false;
135 135 return false;
136 136 } else if (event.which === 83 && that.control_key_active) {
137 137 // Save notebook = s
138 138 IPython.save_widget.save_notebook();
139 139 that.control_key_active = false;
140 140 return false;
141 141 } else if (event.which === 74 && that.control_key_active) {
142 142 // Move cell down = j
143 143 that.move_cell_down();
144 144 that.control_key_active = false;
145 145 return false;
146 146 } else if (event.which === 75 && that.control_key_active) {
147 147 // Move cell up = k
148 148 that.move_cell_up();
149 149 that.control_key_active = false;
150 150 return false;
151 151 } else if (event.which === 80 && that.control_key_active) {
152 152 // Select previous = p
153 153 that.select_prev();
154 154 that.control_key_active = false;
155 155 return false;
156 156 } else if (event.which === 78 && that.control_key_active) {
157 157 // Select next = n
158 158 that.select_next();
159 159 that.control_key_active = false;
160 160 return false;
161 161 } else if (event.which === 76 && that.control_key_active) {
162 162 // Toggle line numbers = l
163 163 that.cell_toggle_line_numbers();
164 164 that.control_key_active = false;
165 165 return false;
166 166 } else if (event.which === 73 && that.control_key_active) {
167 167 // Interrupt kernel = i
168 168 IPython.notebook.kernel.interrupt();
169 169 that.control_key_active = false;
170 170 return false;
171 171 } else if (event.which === 190 && that.control_key_active) {
172 172 // Restart kernel = . # matches qt console
173 173 IPython.notebook.restart_kernel();
174 174 that.control_key_active = false;
175 175 return false;
176 176 } else if (event.which === 72 && that.control_key_active) {
177 177 // Show keyboard shortcuts = h
178 178 IPython.quick_help.show_keyboard_shortcuts();
179 179 that.control_key_active = false;
180 180 return false;
181 181 } else if (event.which === 69 && that.control_key_active) {
182 182 // Edit in Ace = e
183 183 IPython.fulledit_widget.toggle();
184 184 that.control_key_active = false;
185 185 return false;
186 186 } else if (that.control_key_active) {
187 187 that.control_key_active = false;
188 188 return true;
189 189 };
190 190 return true;
191 191 });
192 192
193 193 this.element.bind('collapse_pager', function () {
194 194 var app_height = $('div#main_app').height(); // content height
195 195 var splitter_height = $('div#pager_splitter').outerHeight(true);
196 196 var new_height = app_height - splitter_height;
197 197 that.element.animate({height : new_height + 'px'}, 'fast');
198 198 });
199 199
200 200 this.element.bind('expand_pager', function () {
201 201 var app_height = $('div#main_app').height(); // content height
202 202 var splitter_height = $('div#pager_splitter').outerHeight(true);
203 203 var pager_height = $('div#pager').outerHeight(true);
204 204 var new_height = app_height - pager_height - splitter_height;
205 205 that.element.animate({height : new_height + 'px'}, 'fast');
206 206 });
207 207
208 208 $(window).bind('beforeunload', function () {
209 209 // TODO: Make killing the kernel configurable.
210 210 var kill_kernel = false;
211 211 if (kill_kernel) {
212 212 that.kernel.kill();
213 213 }
214 214 if (that.dirty && ! that.read_only) {
215 215 return "You have unsaved changes that will be lost if you leave this page.";
216 216 };
217 217 // Null is the *only* return value that will make the browser not
218 218 // pop up the "don't leave" dialog.
219 219 return null;
220 220 });
221 221 };
222 222
223 223
224 224 Notebook.prototype.scroll_to_bottom = function () {
225 225 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
226 226 };
227 227
228 228
229 229 Notebook.prototype.scroll_to_top = function () {
230 230 this.element.animate({scrollTop:0}, 0);
231 231 };
232 232
233 233
234 234 // Cell indexing, retrieval, etc.
235 235
236 236 Notebook.prototype.get_cell_elements = function () {
237 237 return this.element.children("div.cell");
238 238 };
239 239
240 240
241 241 Notebook.prototype.get_cell_element = function (index) {
242 242 var result = null;
243 243 var e = this.get_cell_elements().eq(index);
244 244 if (e.length !== 0) {
245 245 result = e;
246 246 }
247 247 return result;
248 248 };
249 249
250 250
251 251 Notebook.prototype.ncells = function (cell) {
252 252 return this.get_cell_elements().length;
253 253 };
254 254
255 255
256 256 // TODO: we are often calling cells as cells()[i], which we should optimize
257 257 // to cells(i) or a new method.
258 258 Notebook.prototype.get_cells = function () {
259 259 return this.get_cell_elements().toArray().map(function (e) {
260 260 return $(e).data("cell");
261 261 });
262 262 };
263 263
264 264
265 265 Notebook.prototype.get_cell = function (index) {
266 266 var result = null;
267 267 var ce = this.get_cell_element(index);
268 268 if (ce !== null) {
269 269 result = ce.data('cell');
270 270 }
271 271 return result;
272 272 }
273 273
274 274
275 275 Notebook.prototype.get_next_cell = function (cell) {
276 276 var result = null;
277 277 var index = this.find_cell_index(cell);
278 278 if (index !== null && index < this.ncells()) {
279 279 result = this.get_cell(index+1);
280 280 }
281 281 return result;
282 282 }
283 283
284 284
285 285 Notebook.prototype.get_prev_cell = function (cell) {
286 286 var result = null;
287 287 var index = this.find_cell_index(cell);
288 288 if (index !== null && index > 1) {
289 289 result = this.get_cell(index-1);
290 290 }
291 291 return result;
292 292 }
293 293
294 294 Notebook.prototype.find_cell_index = function (cell) {
295 295 var result = null;
296 296 this.get_cell_elements().filter(function (index) {
297 297 if ($(this).data("cell") === cell) {
298 298 result = index;
299 299 };
300 300 });
301 301 return result;
302 302 };
303 303
304 304
305 305 Notebook.prototype.index_or_selected = function (index) {
306 306 var i;
307 307 if (index === undefined || index === null) {
308 308 i = this.get_selected_index();
309 309 if (i === null) {
310 310 i = 0;
311 311 }
312 312 } else {
313 313 i = index;
314 314 }
315 315 return i;
316 316 };
317 317
318 318
319 319 Notebook.prototype.get_selected_cell = function () {
320 320 var index = this.get_selected_index();
321 321 return this.get_cell(index);
322 322 };
323 323
324 324
325 325 Notebook.prototype.is_valid_cell_index = function (index) {
326 326 if (index !== null && index >= 0 && index < this.ncells()) {
327 327 return true;
328 328 } else {
329 329 return false;
330 330 };
331 331 }
332 332
333 333 Notebook.prototype.get_selected_index = function () {
334 334 var result = null;
335 335 this.get_cell_elements().filter(function (index) {
336 336 if ($(this).data("cell").selected === true) {
337 337 result = index;
338 338 };
339 339 });
340 340 return result;
341 341 };
342 342
343 343
344 344 Notebook.prototype.cell_for_msg = function (msg_id) {
345 345 var cell_id = this.msg_cell_map[msg_id];
346 346 var result = null;
347 347 this.get_cell_elements().filter(function (index) {
348 348 cell = $(this).data("cell");
349 349 if (cell.cell_id === cell_id) {
350 350 result = cell;
351 351 };
352 352 });
353 353 return result;
354 354 };
355 355
356 356
357 357 // Cell selection.
358 358
359 359 Notebook.prototype.select = function (index) {
360 360 if (index !== undefined && index >= 0 && index < this.ncells()) {
361 361 sindex = this.get_selected_index()
362 362 if (sindex !== null && index !== sindex) {
363 363 this.get_cell(sindex).unselect();
364 364 };
365 365 this.get_cell(index).select();
366 366 };
367 367 return this;
368 368 };
369 369
370 370
371 371 Notebook.prototype.select_next = function () {
372 372 var index = this.get_selected_index();
373 373 if (index !== null && index >= 0 && (index+1) < this.ncells()) {
374 374 this.select(index+1);
375 375 };
376 376 return this;
377 377 };
378 378
379 379
380 380 Notebook.prototype.select_prev = function () {
381 381 var index = this.get_selected_index();
382 382 if (index !== null && index >= 0 && (index-1) < this.ncells()) {
383 383 this.select(index-1);
384 384 };
385 385 return this;
386 386 };
387 387
388 388
389 389 // Cell movement
390 390
391 391 Notebook.prototype.move_cell_up = function (index) {
392 392 var i = this.index_or_selected();
393 393 if (i !== null && i < this.ncells() && i > 0) {
394 394 var pivot = this.get_cell_element(i-1);
395 395 var tomove = this.get_cell_element(i);
396 396 if (pivot !== null && tomove !== null) {
397 397 tomove.detach();
398 398 pivot.before(tomove);
399 399 this.select(i-1);
400 400 };
401 401 };
402 402 this.dirty = true;
403 403 return this;
404 404 };
405 405
406 406
407 407 Notebook.prototype.move_cell_down = function (index) {
408 408 var i = this.index_or_selected();
409 409 if (i !== null && i < (this.ncells()-1) && i >= 0) {
410 410 var pivot = this.get_cell_element(i+1);
411 411 var tomove = this.get_cell_element(i);
412 412 if (pivot !== null && tomove !== null) {
413 413 tomove.detach();
414 414 pivot.after(tomove);
415 415 this.select(i+1);
416 416 };
417 417 };
418 418 this.dirty = true;
419 419 return this;
420 420 };
421 421
422 422
423 423 Notebook.prototype.sort_cells = function () {
424 424 // This is not working right now. Calling this will actually crash
425 425 // the browser. I think there is an infinite loop in here...
426 426 var ncells = this.ncells();
427 427 var sindex = this.get_selected_index();
428 428 var swapped;
429 429 do {
430 430 swapped = false;
431 431 for (var i=1; i<ncells; i++) {
432 432 current = this.get_cell(i);
433 433 previous = this.get_cell(i-1);
434 434 if (previous.input_prompt_number > current.input_prompt_number) {
435 435 this.move_cell_up(i);
436 436 swapped = true;
437 437 };
438 438 };
439 439 } while (swapped);
440 440 this.select(sindex);
441 441 return this;
442 442 };
443 443
444 444 // Insertion, deletion.
445 445
446 446 Notebook.prototype.delete_cell = function (index) {
447 447 var i = this.index_or_selected(index);
448 448 if (this.is_valid_cell_index(i)) {
449 449 var ce = this.get_cell_element(i);
450 450 ce.remove();
451 451 if (i === (this.ncells())) {
452 452 this.select(i-1);
453 453 } else {
454 454 this.select(i);
455 455 };
456 456 this.dirty = true;
457 457 };
458 458 return this;
459 459 };
460 460
461 461
462 462 Notebook.prototype.insert_cell_below = function (type, index) {
463 463 // type = ('code','html','markdown')
464 464 // index = cell index or undefined to insert below selected
465 465 index = this.index_or_selected(index);
466 466 if (this.ncells() === 0 || this.is_valid_cell_index(index)) {
467 467 var cell = null;
468 468 if (type === 'code') {
469 469 var cell = new IPython.CodeCell(this);
470 470 cell.set_input_prompt();
471 471 } else if (type === 'markdown') {
472 472 var cell = new IPython.MarkdownCell(this);
473 473 } else if (type === 'html') {
474 474 var cell = new IPython.HTMLCell(this);
475 475 };
476 476 if (cell !== null) {
477 477 if (this.ncells() === 0) {
478 478 this.element.find('div.end_space').before(cell.element);
479 479 } else if (this.is_valid_cell_index(index)) {
480 480 this.get_cell_element(index).after(cell.element);
481 481 };
482 482 cell.render();
483 483 this.select(this.find_cell_index(cell));
484 484 this.dirty = true;
485 485 return cell;
486 486 };
487 487 };
488 488 };
489 489
490 490
491 491 Notebook.prototype.insert_cell_above = function (type, index) {
492 492 // type = ('code','html','markdown')
493 493 // index = cell index or undefined to insert above selected
494 494 index = this.index_or_selected(index);
495 495 if (this.ncells() === 0 || this.is_valid_cell_index(index)) {
496 496 var cell = null;
497 497 if (type === 'code') {
498 498 var cell = new IPython.CodeCell(this);
499 499 cell.set_input_prompt();
500 500 } else if (type === 'markdown') {
501 501 var cell = new IPython.MarkdownCell(this);
502 502 } else if (type === 'html') {
503 503 var cell = new IPython.HTMLCell(this);
504 504 };
505 505 if (cell !== null) {
506 506 if (this.ncells() === 0) {
507 507 this.element.find('div.end_space').before(cell.element);
508 508 } else if (this.is_valid_cell_index(index)) {
509 509 this.get_cell_element(index).before(cell.element);
510 510 };
511 511 cell.render();
512 512 this.select(this.find_cell_index(cell));
513 513 this.dirty = true;
514 514 return cell;
515 515 };
516 516 };
517 517 };
518 518
519 519
520 520 Notebook.prototype.to_code = function (index) {
521 521 var i = this.index_or_selected(index);
522 522 if (this.is_valid_cell_index(i)) {
523 523 var source_element = this.get_cell_element(i);
524 524 var source_cell = source_element.data("cell");
525 525 if (!(source_cell instanceof IPython.CodeCell)) {
526 526 target_cell = this.insert_cell_below('code',i);
527 527 var text = source_cell.get_text();
528 528 if (text === source_cell.placeholder) {
529 529 text = '';
530 530 }
531 531 target_cell.set_text(text);
532 532 source_element.remove();
533 533 };
534 534 this.dirty = true;
535 535 };
536 536 };
537 537
538 538
539 539 Notebook.prototype.to_markdown = function (index) {
540 540 var i = this.index_or_selected(index);
541 541 if (this.is_valid_cell_index(i)) {
542 542 var source_element = this.get_cell_element(i);
543 543 var source_cell = source_element.data("cell");
544 544 var target_cell = null;
545 545 if (!(source_cell instanceof IPython.MarkdownCell)) {
546 546 target_cell = this.insert_cell_below('markdown',i);
547 547 var text = source_cell.get_text();
548 548 if (text === source_cell.placeholder) {
549 549 text = '';
550 550 };
551 551 if (target_cell !== null) {
552 552 // The edit must come before the set_text.
553 553 target_cell.edit();
554 554 target_cell.set_text(text);
555 555 source_element.remove();
556 556 }
557 557 this.dirty = true;
558 558 };
559 559 };
560 560 };
561 561
562 562
563 563 Notebook.prototype.to_html = function (index) {
564 564 var i = this.index_or_selected(index);
565 565 if (this.is_valid_cell_index(i)) {
566 566 var source_element = this.get_cell_element(i);
567 567 var source_cell = source_element.data("cell");
568 568 var target_cell = null;
569 569 if (!(source_cell instanceof IPython.HTMLCell)) {
570 570 target_cell = this.insert_cell_below('html',i);
571 571 var text = source_cell.get_text();
572 572 if (text === source_cell.placeholder) {
573 573 text = '';
574 574 };
575 575 if (target_cell !== null) {
576 576 // The edit must come before the set_text.
577 577 target_cell.edit();
578 578 target_cell.set_text(text);
579 579 source_element.remove();
580 580 }
581 581 this.dirty = true;
582 582 };
583 583 };
584 584 };
585 585
586 586
587 587 // Cut/Copy/Paste
588 588
589 589 Notebook.prototype.enable_paste = function () {
590 590 var that = this;
591 591 if (!this.paste_enabled) {
592 592 $('#paste_cell').removeClass('ui-state-disabled')
593 593 .on('click', function () {that.paste_cell();});
594 594 $('#paste_cell_above').removeClass('ui-state-disabled')
595 595 .on('click', function () {that.paste_cell_above();});
596 596 $('#paste_cell_below').removeClass('ui-state-disabled')
597 597 .on('click', function () {that.paste_cell_below();});
598 598 this.paste_enabled = true;
599 599 };
600 600 };
601 601
602 602
603 603 Notebook.prototype.disable_paste = function () {
604 604 if (this.paste_enabled) {
605 605 $('#paste_cell').addClass('ui-state-disabled').off('click');
606 606 $('#paste_cell_above').addClass('ui-state-disabled').off('click');
607 607 $('#paste_cell_below').addClass('ui-state-disabled').off('click');
608 608 this.paste_enabled = false;
609 609 };
610 610 };
611 611
612 612
613 613 Notebook.prototype.cut_cell = function () {
614 614 this.copy_cell();
615 615 this.delete_cell();
616 616 }
617 617
618 618 Notebook.prototype.copy_cell = function () {
619 619 var cell = this.get_selected_cell();
620 620 this.clipboard = cell.toJSON();
621 621 this.enable_paste();
622 622 };
623 623
624 624
625 625 Notebook.prototype.paste_cell = function () {
626 626 if (this.clipboard !== null && this.paste_enabled) {
627 627 var cell_data = this.clipboard;
628 628 var new_cell = this.insert_cell_above(cell_data.cell_type);
629 629 new_cell.fromJSON(cell_data);
630 630 old_cell = this.get_next_cell(new_cell);
631 631 this.delete_cell(this.find_cell_index(old_cell));
632 632 this.select(this.find_cell_index(new_cell));
633 633 };
634 634 };
635 635
636 636
637 637 Notebook.prototype.paste_cell_above = function () {
638 638 if (this.clipboard !== null && this.paste_enabled) {
639 639 var cell_data = this.clipboard;
640 640 var new_cell = this.insert_cell_above(cell_data.cell_type);
641 641 new_cell.fromJSON(cell_data);
642 642 };
643 643 };
644 644
645 645
646 646 Notebook.prototype.paste_cell_below = function () {
647 647 if (this.clipboard !== null && this.paste_enabled) {
648 648 var cell_data = this.clipboard;
649 649 var new_cell = this.insert_cell_below(cell_data.cell_type);
650 650 new_cell.fromJSON(cell_data);
651 651 };
652 652 };
653 653
654 654
655 655 // Split/merge
656 656
657 657 Notebook.prototype.split_cell = function () {
658 658 // Todo: implement spliting for other cell types.
659 659 var cell = this.get_selected_cell();
660 660 if (cell.is_splittable()) {
661 661 texta = cell.get_pre_cursor();
662 662 textb = cell.get_post_cursor();
663 663 if (cell instanceof IPython.CodeCell) {
664 664 cell.set_text(texta);
665 665 var new_cell = this.insert_cell_below('code');
666 666 new_cell.set_text(textb);
667 667 } else if (cell instanceof IPython.MarkdownCell) {
668 668 cell.set_text(texta);
669 669 cell.render();
670 670 var new_cell = this.insert_cell_below('markdown');
671 671 new_cell.edit(); // editor must be visible to call set_text
672 672 new_cell.set_text(textb);
673 673 new_cell.render();
674 674 } else if (cell instanceof IPython.HTMLCell) {
675 675 cell.set_text(texta);
676 676 cell.render();
677 677 var new_cell = this.insert_cell_below('html');
678 678 new_cell.edit(); // editor must be visible to call set_text
679 679 new_cell.set_text(textb);
680 680 new_cell.render();
681 681 };
682 682 };
683 683 };
684 684
685 685
686 686 Notebook.prototype.merge_cell_above = function () {
687 687 var index = this.get_selected_index();
688 688 var cell = this.get_cell(index);
689 689 if (index > 0) {
690 690 upper_cell = this.get_cell(index-1);
691 691 upper_text = upper_cell.get_text();
692 692 text = cell.get_text();
693 693 if (cell instanceof IPython.CodeCell) {
694 694 cell.set_text(upper_text+'\n'+text);
695 695 } else if (cell instanceof IPython.MarkdownCell || cell instanceof IPython.HTMLCell) {
696 696 cell.edit();
697 697 cell.set_text(upper_text+'\n'+text);
698 698 cell.render();
699 699 };
700 700 this.delete_cell(index-1);
701 701 this.select(this.find_cell_index(cell));
702 702 };
703 703 };
704 704
705 705
706 706 Notebook.prototype.merge_cell_below = function () {
707 707 var index = this.get_selected_index();
708 708 var cell = this.get_cell(index);
709 709 if (index < this.ncells()-1) {
710 710 lower_cell = this.get_cell(index+1);
711 711 lower_text = lower_cell.get_text();
712 712 text = cell.get_text();
713 713 if (cell instanceof IPython.CodeCell) {
714 714 cell.set_text(text+'\n'+lower_text);
715 715 } else if (cell instanceof IPython.MarkdownCell || cell instanceof IPython.HTMLCell) {
716 716 cell.edit();
717 717 cell.set_text(text+'\n'+lower_text);
718 718 cell.render();
719 719 };
720 720 this.delete_cell(index+1);
721 721 this.select(this.find_cell_index(cell));
722 722 };
723 723 };
724 724
725 725
726 726 // Cell collapsing and output clearing
727 727
728 728 Notebook.prototype.collapse = function (index) {
729 729 var i = this.index_or_selected(index);
730 730 this.get_cell(i).collapse();
731 731 this.dirty = true;
732 732 };
733 733
734 734
735 735 Notebook.prototype.expand = function (index) {
736 736 var i = this.index_or_selected(index);
737 737 this.get_cell(i).expand();
738 738 this.dirty = true;
739 739 };
740 740
741 741
742 742 Notebook.prototype.toggle_output = function (index) {
743 743 var i = this.index_or_selected(index);
744 744 this.get_cell(i).toggle_output();
745 745 this.dirty = true;
746 746 };
747 747
748 748
749 749 Notebook.prototype.set_timebeforetooltip = function (time) {
750 750 this.time_before_tooltip = time;
751 751 };
752 752
753 753
754 754 Notebook.prototype.set_tooltipontab = function (state) {
755 755 this.tooltip_on_tab = state;
756 756 };
757 757
758 758
759 759 Notebook.prototype.set_smartcompleter = function (state) {
760 760 this.smart_completer = state;
761 761 };
762 762
763 763
764 764 Notebook.prototype.clear_all_output = function () {
765 765 var ncells = this.ncells();
766 766 var cells = this.get_cells();
767 767 for (var i=0; i<ncells; i++) {
768 768 if (cells[i] instanceof IPython.CodeCell) {
769 769 cells[i].clear_output(true,true,true);
770 770 }
771 771 };
772 772 this.dirty = true;
773 773 };
774 774
775 775
776 776 // Other cell functions: line numbers, ...
777 777
778 778 Notebook.prototype.cell_toggle_line_numbers = function() {
779 779 this.get_selected_cell().toggle_line_numbers();
780 780 };
781 781
782 782 // Kernel related things
783 783
784 784 Notebook.prototype.start_kernel = function () {
785 785 this.kernel = new IPython.Kernel();
786 786 var notebook_id = IPython.save_widget.get_notebook_id();
787 787 this.kernel.start(notebook_id, $.proxy(this.kernel_started, this));
788 788 };
789 789
790 790
791 791 Notebook.prototype.restart_kernel = function () {
792 792 var that = this;
793 793 var notebook_id = IPython.save_widget.get_notebook_id();
794 794
795 795 var dialog = $('<div/>');
796 796 dialog.html('Do you want to restart the current kernel? You will lose all variables defined in it.');
797 797 $(document).append(dialog);
798 798 dialog.dialog({
799 799 resizable: false,
800 800 modal: true,
801 801 title: "Restart kernel or continue running?",
802 802 closeText: '',
803 803 buttons : {
804 804 "Restart": function () {
805 805 that.kernel.restart($.proxy(that.kernel_started, that));
806 806 $(this).dialog('close');
807 807 },
808 808 "Continue running": function () {
809 809 $(this).dialog('close');
810 810 }
811 811 }
812 812 });
813 813 };
814 814
815 815
816 816 Notebook.prototype.kernel_started = function () {
817 817 console.log("Kernel started: ", this.kernel.kernel_id);
818 818 this.kernel.shell_channel.onmessage = $.proxy(this.handle_shell_reply,this);
819 819 this.kernel.iopub_channel.onmessage = $.proxy(this.handle_iopub_reply,this);
820 820 };
821 821
822 822
823 823 Notebook.prototype.handle_shell_reply = function (e) {
824 824 reply = $.parseJSON(e.data);
825 825 var header = reply.header;
826 826 var content = reply.content;
827 827 var msg_type = header.msg_type;
828 828 // console.log(reply);
829 829 var cell = this.cell_for_msg(reply.parent_header.msg_id);
830 830 if (msg_type === "execute_reply") {
831 831 cell.set_input_prompt(content.execution_count);
832 832 cell.element.removeClass("running");
833 833 this.dirty = true;
834 834 } else if (msg_type === "complete_reply") {
835 835 cell.finish_completing(content.matched_text, content.matches);
836 836 } else if (msg_type === "object_info_reply"){
837 837 //console.log('back from object_info_request : ')
838 838 rep = reply.content;
839 839 if(rep.found)
840 840 {
841 841 cell.finish_tooltip(rep);
842 842 }
843 843 } else {
844 844 //console.log("unknown reply:"+msg_type);
845 845 }
846 846 // when having a rely from object_info_reply,
847 847 // no payload so no nned to handle it
848 848 if(typeof(content.payload)!='undefined') {
849 849 var payload = content.payload || [];
850 850 this.handle_payload(cell, payload);
851 851 }
852 852 };
853 853
854 854
855 855 Notebook.prototype.handle_payload = function (cell, payload) {
856 856 var l = payload.length;
857 857 for (var i=0; i<l; i++) {
858 858 if (payload[i].source === 'IPython.zmq.page.page') {
859 859 if (payload[i].text.trim() !== '') {
860 860 IPython.pager.clear();
861 861 IPython.pager.expand();
862 862 IPython.pager.append_text(payload[i].text);
863 863 }
864 864 } else if (payload[i].source === 'IPython.zmq.zmqshell.ZMQInteractiveShell.set_next_input') {
865 865 var index = this.find_cell_index(cell);
866 866 var new_cell = this.insert_cell_below('code',index);
867 867 new_cell.set_text(payload[i].text);
868 868 this.dirty = true;
869 869 }
870 870 };
871 871 };
872 872
873 873
874 874 Notebook.prototype.handle_iopub_reply = function (e) {
875 875 reply = $.parseJSON(e.data);
876 876 var content = reply.content;
877 877 // console.log(reply);
878 878 var msg_type = reply.header.msg_type;
879 879 var cell = this.cell_for_msg(reply.parent_header.msg_id);
880 880 if (msg_type !== 'status' && !cell){
881 881 // message not from this notebook, but should be attached to a cell
882 882 console.log("Received IOPub message not caused by one of my cells");
883 883 console.log(reply);
884 884 return;
885 885 }
886 886 var output_types = ['stream','display_data','pyout','pyerr'];
887 887 if (output_types.indexOf(msg_type) >= 0) {
888 888 this.handle_output(cell, msg_type, content);
889 889 } else if (msg_type === 'status') {
890 890 if (content.execution_state === 'busy') {
891 891 IPython.kernel_status_widget.status_busy();
892 892 } else if (content.execution_state === 'idle') {
893 893 IPython.kernel_status_widget.status_idle();
894 894 } else if (content.execution_state === 'dead') {
895 895 this.handle_status_dead();
896 896 };
897 897 } else if (msg_type === 'clear_output') {
898 898 cell.clear_output(content.stdout, content.stderr, content.other);
899 899 };
900 900 };
901 901
902 902
903 903 Notebook.prototype.handle_status_dead = function () {
904 904 var that = this;
905 905 this.kernel.stop_channels();
906 906 var dialog = $('<div/>');
907 907 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.');
908 908 $(document).append(dialog);
909 909 dialog.dialog({
910 910 resizable: false,
911 911 modal: true,
912 912 title: "Dead kernel",
913 913 buttons : {
914 914 "Restart": function () {
915 915 that.start_kernel();
916 916 $(this).dialog('close');
917 917 },
918 918 "Continue running": function () {
919 919 $(this).dialog('close');
920 920 }
921 921 }
922 922 });
923 923 };
924 924
925 925
926 926 Notebook.prototype.handle_output = function (cell, msg_type, content) {
927 927 var json = {};
928 928 json.output_type = msg_type;
929 929 if (msg_type === "stream") {
930 930 json.text = utils.fixConsole(content.data);
931 931 json.stream = content.name;
932 932 } else if (msg_type === "display_data") {
933 933 json = this.convert_mime_types(json, content.data);
934 934 } else if (msg_type === "pyout") {
935 935 json.prompt_number = content.execution_count;
936 936 json = this.convert_mime_types(json, content.data);
937 937 } else if (msg_type === "pyerr") {
938 938 json.ename = content.ename;
939 939 json.evalue = content.evalue;
940 940 var traceback = [];
941 941 for (var i=0; i<content.traceback.length; i++) {
942 942 traceback.push(utils.fixConsole(content.traceback[i]));
943 943 }
944 944 json.traceback = traceback;
945 945 };
946 946 cell.append_output(json);
947 947 this.dirty = true;
948 948 };
949 949
950 950
951 951 Notebook.prototype.convert_mime_types = function (json, data) {
952 952 if (data['text/plain'] !== undefined) {
953 953 json.text = utils.fixConsole(data['text/plain']);
954 954 };
955 955 if (data['text/html'] !== undefined) {
956 956 json.html = data['text/html'];
957 957 };
958 958 if (data['image/svg+xml'] !== undefined) {
959 959 json.svg = data['image/svg+xml'];
960 960 };
961 961 if (data['image/png'] !== undefined) {
962 962 json.png = data['image/png'];
963 963 };
964 964 if (data['image/jpeg'] !== undefined) {
965 965 json.jpeg = data['image/jpeg'];
966 966 };
967 967 if (data['text/latex'] !== undefined) {
968 968 json.latex = data['text/latex'];
969 969 };
970 970 if (data['application/json'] !== undefined) {
971 971 json.json = data['application/json'];
972 972 };
973 973 if (data['application/javascript'] !== undefined) {
974 974 json.javascript = data['application/javascript'];
975 975 }
976 976 return json;
977 977 };
978 978
979 979
980 980 Notebook.prototype.execute_selected_cell = function (options) {
981 981 // add_new: should a new cell be added if we are at the end of the nb
982 982 // terminal: execute in terminal mode, which stays in the current cell
983 983 default_options = {terminal: false, add_new: true};
984 984 $.extend(default_options, options);
985 985 var that = this;
986 986 var cell = that.get_selected_cell();
987 987 var cell_index = that.find_cell_index(cell);
988 988 if (cell instanceof IPython.CodeCell) {
989 989 cell.clear_output(true, true, true);
990 990 cell.set_input_prompt('*');
991 991 cell.element.addClass("running");
992 992 var code = cell.get_text();
993 993 var msg_id = that.kernel.execute(cell.get_text());
994 994 that.msg_cell_map[msg_id] = cell.cell_id;
995 995 } else if (cell instanceof IPython.HTMLCell) {
996 996 cell.render();
997 997 }
998 998 if (default_options.terminal) {
999 999 cell.select_all();
1000 1000 } else {
1001 1001 if ((cell_index === (that.ncells()-1)) && default_options.add_new) {
1002 1002 that.insert_cell_below('code');
1003 1003 // If we are adding a new cell at the end, scroll down to show it.
1004 1004 that.scroll_to_bottom();
1005 1005 } else {
1006 1006 that.select(cell_index+1);
1007 1007 };
1008 1008 };
1009 1009 this.dirty = true;
1010 1010 };
1011 1011
1012 1012
1013 1013 Notebook.prototype.execute_all_cells = function () {
1014 1014 var ncells = this.ncells();
1015 1015 for (var i=0; i<ncells; i++) {
1016 1016 this.select(i);
1017 this.execute_get_selected_cell({add_new:false});
1017 this.execute_selected_cell({add_new:false});
1018 1018 };
1019 1019 this.scroll_to_bottom();
1020 1020 };
1021 1021
1022 1022
1023 1023 Notebook.prototype.request_tool_tip = function (cell,func) {
1024 1024 // Feel free to shorten this logic if you are better
1025 1025 // than me in regEx
1026 1026 // basicaly you shoul be able to get xxx.xxx.xxx from
1027 1027 // something(range(10), kwarg=smth) ; xxx.xxx.xxx( firstarg, rand(234,23), kwarg1=2,
1028 1028 // remove everything between matchin bracket (need to iterate)
1029 1029 matchBracket = /\([^\(\)]+\)/g;
1030 1030 oldfunc = func;
1031 1031 func = func.replace(matchBracket,"");
1032 1032 while( oldfunc != func )
1033 1033 {
1034 1034 oldfunc = func;
1035 1035 func = func.replace(matchBracket,"");
1036 1036 }
1037 1037 // remove everythin after last open bracket
1038 1038 endBracket = /\([^\(]*$/g;
1039 1039 func = func.replace(endBracket,"");
1040 1040 var re = /[a-zA-Z._]+$/g;
1041 1041 var msg_id = this.kernel.object_info_request(re.exec(func));
1042 1042 if(typeof(msg_id)!='undefined'){
1043 1043 this.msg_cell_map[msg_id] = cell.cell_id;
1044 1044 }
1045 1045 };
1046 1046
1047 1047 Notebook.prototype.complete_cell = function (cell, line, cursor_pos) {
1048 1048 var msg_id = this.kernel.complete(line, cursor_pos);
1049 1049 this.msg_cell_map[msg_id] = cell.cell_id;
1050 1050 };
1051 1051
1052 1052 // Persistance and loading
1053 1053
1054 1054
1055 1055 Notebook.prototype.fromJSON = function (data) {
1056 1056 var ncells = this.ncells();
1057 1057 var i;
1058 1058 for (i=0; i<ncells; i++) {
1059 1059 // Always delete cell 0 as they get renumbered as they are deleted.
1060 1060 this.delete_cell(0);
1061 1061 };
1062 1062 // Save the metadata
1063 1063 this.metadata = data.metadata;
1064 1064 // Only handle 1 worksheet for now.
1065 1065 var worksheet = data.worksheets[0];
1066 1066 if (worksheet !== undefined) {
1067 1067 var new_cells = worksheet.cells;
1068 1068 ncells = new_cells.length;
1069 1069 var cell_data = null;
1070 1070 var new_cell = null;
1071 1071 for (i=0; i<ncells; i++) {
1072 1072 cell_data = new_cells[i];
1073 1073 new_cell = this.insert_cell_below(cell_data.cell_type);
1074 1074 new_cell.fromJSON(cell_data);
1075 1075 };
1076 1076 };
1077 1077 };
1078 1078
1079 1079
1080 1080 Notebook.prototype.toJSON = function () {
1081 1081 var cells = this.get_cells();
1082 1082 var ncells = cells.length;
1083 1083 cell_array = new Array(ncells);
1084 1084 for (var i=0; i<ncells; i++) {
1085 1085 cell_array[i] = cells[i].toJSON();
1086 1086 };
1087 1087 data = {
1088 1088 // Only handle 1 worksheet for now.
1089 1089 worksheets : [{cells:cell_array}],
1090 1090 metadata : this.metadata
1091 1091 };
1092 1092 return data;
1093 1093 };
1094 1094
1095 1095 Notebook.prototype.save_notebook = function () {
1096 1096 var notebook_id = IPython.save_widget.get_notebook_id();
1097 1097 var nbname = IPython.save_widget.get_notebook_name();
1098 1098 // We may want to move the name/id/nbformat logic inside toJSON?
1099 1099 var data = this.toJSON();
1100 1100 data.metadata.name = nbname;
1101 1101 data.nbformat = 2;
1102 1102 // We do the call with settings so we can set cache to false.
1103 1103 var settings = {
1104 1104 processData : false,
1105 1105 cache : false,
1106 1106 type : "PUT",
1107 1107 data : JSON.stringify(data),
1108 1108 headers : {'Content-Type': 'application/json'},
1109 1109 success : $.proxy(this.notebook_saved,this),
1110 1110 error : $.proxy(this.notebook_save_failed,this)
1111 1111 };
1112 1112 IPython.save_widget.status_saving();
1113 1113 var url = $('body').data('baseProjectUrl') + 'notebooks/' + notebook_id;
1114 1114 $.ajax(url, settings);
1115 1115 };
1116 1116
1117 1117
1118 1118 Notebook.prototype.notebook_saved = function (data, status, xhr) {
1119 1119 this.dirty = false;
1120 1120 IPython.save_widget.notebook_saved();
1121 1121 IPython.save_widget.status_last_saved();
1122 1122 };
1123 1123
1124 1124
1125 1125 Notebook.prototype.notebook_save_failed = function (xhr, status, error_msg) {
1126 1126 IPython.save_widget.status_save_failed();
1127 1127 };
1128 1128
1129 1129
1130 1130 Notebook.prototype.load_notebook = function () {
1131 1131 var that = this;
1132 1132 var notebook_id = IPython.save_widget.get_notebook_id();
1133 1133 // We do the call with settings so we can set cache to false.
1134 1134 var settings = {
1135 1135 processData : false,
1136 1136 cache : false,
1137 1137 type : "GET",
1138 1138 dataType : "json",
1139 1139 success : function (data, status, xhr) {
1140 1140 that.notebook_loaded(data, status, xhr);
1141 1141 }
1142 1142 };
1143 1143 IPython.save_widget.status_loading();
1144 1144 var url = $('body').data('baseProjectUrl') + 'notebooks/' + notebook_id;
1145 1145 $.ajax(url, settings);
1146 1146 };
1147 1147
1148 1148
1149 1149 Notebook.prototype.notebook_loaded = function (data, status, xhr) {
1150 1150 this.fromJSON(data);
1151 1151 if (this.ncells() === 0) {
1152 1152 this.insert_cell_below('code');
1153 1153 };
1154 1154 IPython.save_widget.status_last_saved();
1155 1155 IPython.save_widget.set_notebook_name(data.metadata.name);
1156 1156 this.dirty = false;
1157 1157 if (! this.read_only) {
1158 1158 this.start_kernel();
1159 1159 }
1160 1160 this.select(0);
1161 1161 this.scroll_to_top();
1162 1162 IPython.save_widget.update_url();
1163 1163 IPython.layout_manager.do_resize();
1164 1164 };
1165 1165
1166 1166 IPython.Notebook = Notebook;
1167 1167
1168 1168
1169 1169 return IPython;
1170 1170
1171 1171 }(IPython));
1172 1172
General Comments 0
You need to be logged in to leave comments. Login now