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