##// END OF EJS Templates
usability and cross browser compat for completer...
Matthias BUSSONNIER -
Show More
@@ -1,797 +1,819 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 // note that we are comparing and setting the time to wait at each key press.
71 71 // a better wqy might be to generate a new function on each time change and
72 72 // assign it to CodeCell.prototype.request_tooltip_after_time
73 73 tooltip_wait_time = this.notebook.time_before_tooltip;
74 74 tooltip_on_tab = this.notebook.tooltip_on_tab;
75 75 var that = this;
76 76 // whatever key is pressed, first, cancel the tooltip request before
77 77 // they are sent, and remove tooltip if any
78 78 if(event.type === 'keydown' ){
79 79 that.remove_and_cancel_tooltip();
80 80 }
81 81
82 82 if (event.keyCode === 13 && (event.shiftKey || event.ctrlKey)) {
83 83 // Always ignore shift-enter in CodeMirror as we handle it.
84 84 return true;
85 85 }else if (event.which === 40 && event.type === 'keypress' && tooltip_wait_time >= 0) {
86 86 // triger aon keypress (!) otherwise inconsistent event.which depending on plateform
87 87 // browser and keyboard layout !
88 88 // Pressing '(' , request tooltip, don't forget to reappend it
89 89 var cursor = editor.getCursor();
90 90 var pre_cursor = editor.getRange({line:cursor.line,ch:0},cursor).trim()+'(';
91 91 that.request_tooltip_after_time(pre_cursor,tooltip_wait_time);
92 92 } else if (event.keyCode === 9 && event.type == 'keydown') {
93 93 // Tab completion.
94 94 var cur = editor.getCursor();
95 95 //Do not trim here because of tooltip
96 96 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
97 97 if (pre_cursor.trim() === "") {
98 98 // Don't autocomplete if the part of the line before the cursor
99 99 // is empty. In this case, let CodeMirror handle indentation.
100 100 return false;
101 101 } else if ((pre_cursor.substr(-1) === "("|| pre_cursor.substr(-1) === " ") && tooltip_on_tab ) {
102 102 that.request_tooltip_after_time(pre_cursor,0);
103 103 } else {
104 104 pre_cursor.trim();
105 105 // Autocomplete the current line.
106 106 event.stop();
107 107 var line = editor.getLine(cur.line);
108 108 this.is_completing = true;
109 109 this.completion_cursor = cur;
110 110 IPython.notebook.complete_cell(this, line, cur.ch);
111 111 return true;
112 112 }
113 113 } else if (event.keyCode === 8 && event.type == 'keydown') {
114 114 // If backspace and the line ends with 4 spaces, remove them.
115 115 var cur = editor.getCursor();
116 116 var line = editor.getLine(cur.line);
117 117 var ending = line.slice(-4);
118 118 if (ending === ' ') {
119 119 editor.replaceRange('',
120 120 {line: cur.line, ch: cur.ch-4},
121 121 {line: cur.line, ch: cur.ch}
122 122 );
123 123 event.stop();
124 124 return true;
125 125 } else {
126 126 return false;
127 127 }
128 128 } else if (event.keyCode === 76 && event.ctrlKey && event.shiftKey
129 129 && event.type == 'keydown') {
130 130 // toggle line numbers with Ctrl-Shift-L
131 131 this.toggle_line_numbers();
132 132 }
133 133 else {
134 134 // keypress/keyup also trigger on TAB press, and we don't want to
135 135 // use those to disable tab completion.
136 136 if (this.is_completing && event.keyCode !== 9) {
137 137 var ed_cur = editor.getCursor();
138 138 var cc_cur = this.completion_cursor;
139 139 if (ed_cur.line !== cc_cur.line || ed_cur.ch !== cc_cur.ch) {
140 140 this.is_completing = false;
141 141 this.completion_cursor = null;
142 142 }
143 143 }
144 144 return false;
145 145 };
146 146 return false;
147 147 };
148 148
149 149 CodeCell.prototype.remove_and_cancel_tooltip = function() {
150 150 // note that we don't handle closing directly inside the calltip
151 151 // as in the completer, because it is not focusable, so won't
152 152 // get the event.
153 153 if (this.tooltip_timeout != null){
154 154 clearTimeout(this.tooltip_timeout);
155 155 $('#tooltip').remove();
156 156 this.tooltip_timeout = null;
157 157 }
158 158 }
159 159
160 160 CodeCell.prototype.finish_tooltip = function (reply) {
161 161 defstring=reply.definition;
162 162 docstring=reply.docstring;
163 163 if(docstring == null){docstring="<empty docstring>"};
164 164 name=reply.name;
165 165
166 166 var that = this;
167 167 var tooltip = $('<div/>').attr('id', 'tooltip').addClass('tooltip');
168 168 // remove to have the tooltip not Limited in X and Y
169 169 tooltip.addClass('smalltooltip');
170 170 var pre=$('<pre/>').html(utils.fixConsole(docstring));
171 171 var expandlink=$('<a/>').attr('href',"#");
172 172 expandlink.addClass("ui-corner-all"); //rounded corner
173 173 expandlink.attr('role',"button");
174 174 //expandlink.addClass('ui-button');
175 175 //expandlink.addClass('ui-state-default');
176 176 var expandspan=$('<span/>').text('Expand');
177 177 expandspan.addClass('ui-icon');
178 178 expandspan.addClass('ui-icon-plus');
179 179 expandlink.append(expandspan);
180 180 expandlink.attr('id','expanbutton');
181 181 expandlink.click(function(){
182 182 tooltip.removeClass('smalltooltip');
183 183 tooltip.addClass('bigtooltip');
184 184 $('#expanbutton').remove();
185 185 setTimeout(function(){that.code_mirror.focus();}, 50);
186 186 });
187 187 var morelink=$('<a/>').attr('href',"#");
188 188 morelink.attr('role',"button");
189 189 morelink.addClass('ui-button');
190 190 //morelink.addClass("ui-corner-all"); //rounded corner
191 191 //morelink.addClass('ui-state-default');
192 192 var morespan=$('<span/>').text('Open in Pager');
193 193 morespan.addClass('ui-icon');
194 194 morespan.addClass('ui-icon-arrowstop-l-n');
195 195 morelink.append(morespan);
196 196 morelink.click(function(){
197 197 var msg_id = IPython.notebook.kernel.execute(name+"?");
198 198 IPython.notebook.msg_cell_map[msg_id] = IPython.notebook.selected_cell().cell_id;
199 199 that.remove_and_cancel_tooltip();
200 200 setTimeout(function(){that.code_mirror.focus();}, 50);
201 201 });
202 202
203 203 var closelink=$('<a/>').attr('href',"#");
204 204 closelink.attr('role',"button");
205 205 closelink.addClass('ui-button');
206 206 //closelink.addClass("ui-corner-all"); //rounded corner
207 207 //closelink.adClass('ui-state-default'); // grey background and blue cross
208 208 var closespan=$('<span/>').text('Close');
209 209 closespan.addClass('ui-icon');
210 210 closespan.addClass('ui-icon-close');
211 211 closelink.append(closespan);
212 212 closelink.click(function(){
213 213 that.remove_and_cancel_tooltip();
214 214 setTimeout(function(){that.code_mirror.focus();}, 50);
215 215 });
216 216 //construct the tooltip
217 217 tooltip.append(closelink);
218 218 tooltip.append(expandlink);
219 219 tooltip.append(morelink);
220 220 if(defstring){
221 221 defstring_html= $('<pre/>').html(utils.fixConsole(defstring));
222 222 tooltip.append(defstring_html);
223 223 }
224 224 tooltip.append(pre);
225 225 var pos = this.code_mirror.cursorCoords();
226 226 tooltip.css('left',pos.x+'px');
227 227 tooltip.css('top',pos.yBot+'px');
228 228 $('body').append(tooltip);
229 229
230 230 // issues with cross-closing if multiple tooltip in less than 5sec
231 231 // keep it comented for now
232 232 // setTimeout(that.remove_and_cancel_tooltip, 5000);
233 233 };
234 234
235 235 // As you type completer
236 236 CodeCell.prototype.finish_completing = function (matched_text, matches) {
237 237 //return if not completing or nothing to complete
238 238 if (!this.is_completing || matches.length === 0) {return;}
239 239
240 240 // for later readability
241 241 var key = { tab:9,
242 242 esc:27,
243 243 backspace:8,
244 244 space:13,
245 245 shift:16,
246 246 enter:32,
247 // _ is 189
247 // _ is 95
248 248 isCompSymbol : function (code)
249 {return ((code>64 && code <=122)|| code == 189)}
249 {
250 return (code > 64 && code <= 90)
251 || (code >= 97 && code <= 122)
252 || (code == 95)
253 },
254 dismissAndAppend : function (code)
255 {
256 chararr=['(',')','[',']','+','-','/','\\','.'];
257 codearr=chararr.map(function(x){return x.charCodeAt(0)});
258 return jQuery.inArray(code, codearr)!=-1;
259 }
260
250 261 }
251 262
252 263 // smart completion, sort kwarg ending with '='
253 264 var newm = new Array();
254 265 if(this.notebook.smart_completer)
255 266 {
256 267 kwargs = new Array();
257 268 other = new Array();
258 269 for(var i=0;i<matches.length; ++i){
259 270 if(matches[i].substr(-1) === '='){
260 271 kwargs.push(matches[i]);
261 272 }else{other.push(matches[i]);}
262 273 }
263 274 newm = kwargs.concat(other);
264 275 matches=newm;
265 276 }
266 277 // end sort kwargs
267 278
268 279 // give common prefix of a array of string
269 280 function sharedStart(A){
270 281 if(A.length > 1 ){
271 282 var tem1, tem2, s, A= A.slice(0).sort();
272 283 tem1= A[0];
273 284 s= tem1.length;
274 285 tem2= A.pop();
275 286 while(s && tem2.indexOf(tem1)== -1){
276 287 tem1= tem1.substring(0, --s);
277 288 }
278 289 return tem1;
279 290 }
280 291 return "";
281 292 }
282 293
283 294
284 295 //try to check if the user is typing tab at least twice after a word
285 296 // and completion is "done"
286 297 fallback_on_tooltip_after=2
287 298 if(matches.length==1 && matched_text === matches[0])
288 299 {
289 300 if(this.npressed >fallback_on_tooltip_after && this.prevmatch==matched_text)
290 301 {
291 302 console.log('Ok, you really want to complete after pressing tab '+this.npressed+' times !');
292 303 console.log('You should understand that there is no (more) completion for that !');
293 304 console.log("I'll show you the tooltip, will you stop bothering me ?");
294 305 this.request_tooltip_after_time(matched_text+'(',0);
295 306 return;
296 307 }
297 308 this.prevmatch=matched_text
298 309 this.npressed=this.npressed+1;
299 310 }
300 311 else
301 312 {
302 313 this.prevmatch="";
303 314 this.npressed=0;
304 315 }
305 316 // end fallback on tooltip
306 317 //==================================
307 318 // Real completion logic start here
308 319 var that = this;
309 320 var cur = this.completion_cursor;
310 321 var done = false;
311 322
312 323 // call to dismmiss the completer
313 324 var close = function () {
314 325 if (done) return;
315 326 done = true;
316 327 if (complete!=undefined)
317 328 {complete.remove();}
318 329 that.is_completing = false;
319 330 that.completion_cursor = null;
320 331 };
321 332
322 333 // insert the given text and exit the completer
323 334 var insert = function (selected_text, event) {
324 335 that.code_mirror.replaceRange(
325 336 selected_text,
326 337 {line: cur.line, ch: (cur.ch-matched_text.length)},
327 338 {line: cur.line, ch: cur.ch}
328 339 );
329 340 if(event != null){
330 341 event.stopPropagation();
331 342 event.preventDefault();
332 343 }
333 344 close();
334 345 setTimeout(function(){that.code_mirror.focus();}, 50);
335 346 };
336 347
337 348 // insert the curent highlited selection and exit
338 349 var pick = function () {
339 350 insert(select.val()[0],null);
340 351 };
341 352
342 353
343 354 // Define function to clear the completer, refill it with the new
344 355 // matches, update the pseuso typing field. autopick insert match if
345 356 // only one left, in no matches (anymore) dismiss itself by pasting
346 357 // what the user have typed until then
347 358 var complete_with = function(matches,typed_text,autopick,event)
348 359 {
349 360 // If autopick an only one match, past.
350 361 // Used to 'pick' when pressing tab
351 362 if (matches.length < 1) {
352 363 insert(typed_text,event);
353 364 if(event !=null){
354 365 event.stopPropagation();
355 366 event.preventDefault();
356 367 }
357 368 } else if (autopick && matches.length==1) {
358 369 insert(matches[0],event);
359 370 if(event !=null){
360 371 event.stopPropagation();
361 372 event.preventDefault();
362 373 }
363 374 }
364 375 //clear the previous completion if any
365 376 complete.children().children().remove();
366 377 $('#asyoutype').text(typed_text);
367 378 select=$('#asyoutypeselect');
368 379 for (var i=0; i<matches.length; ++i) {
369 380 select.append($('<option/>').html(matches[i]));
370 381 }
371 382 select.children().first().attr('selected','true');
372 383 }
373 384
374 385 // create html for completer
375 386 var complete = $('<div/>').addClass('completions');
376 387 complete.attr('id','complete');
377 388 complete.append($('<p/>').attr('id', 'asyoutype').html(matched_text));//pseudo input field
378 389
379 390 var select = $('<select/>').attr('multiple','true');
380 391 select.attr('id', 'asyoutypeselect')
381 392 select.attr('size',Math.min(10,matches.length));
382 393 var pos = this.code_mirror.cursorCoords();
383 394
384 395 // TODO: I propose to remove enough horizontal pixel
385 396 // to align the text later
386 397 complete.css('left',pos.x+'px');
387 398 complete.css('top',pos.yBot+'px');
388 399 complete.append(select);
389 400
390 401 $('body').append(complete);
391 402
392 403 // So a first actual completion. see if all the completion start wit
393 404 // the same letter and complete if necessary
394 405 fastForward = sharedStart(matches)
395 406 typed_characters= fastForward.substr(matched_text.length);
396 407 complete_with(matches,matched_text+typed_characters,true,null);
397 408 filterd=matches;
398 409 // Give focus to select, and make it filter the match as the user type
399 410 // by filtering the previous matches. Called by .keypress and .keydown
400 411 var downandpress = function (event,press_or_down) {
401 412 var code = event.which;
402 413 var autopick = false; // auto 'pick' if only one match
403 414 if (press_or_down === 0){
404 415 press=true; down=false; //Are we called from keypress or keydown
405 416 } else if (press_or_down == 1){
406 417 press=false; down=true;
407 418 }
408 419 if (code === key.shift) {
409 420 // nothing on Shift
410 421 return;
411 422 }
423 if (key.dismissAndAppend(code) && press) {
424 var newchar = String.fromCharCode(code);
425 typed_characters=typed_characters+newchar;
426 insert(matched_text+typed_characters,event);
427 return
428 }
412 429 if (code === key.space || code === key.enter) {
413 430 // Pressing SPACE or ENTER will cause a pick
414 431 event.stopPropagation();
415 432 event.preventDefault();
416 433 pick();
417 434 } else if (code === 38 || code === 40) {
418 435 // We don't want the document keydown handler to handle UP/DOWN,
419 436 // but we want the default action.
420 437 event.stopPropagation();
421 //} else if ( key.isCompSymbol(code)|| (code==key.backspace)||(code==key.tab && down)){
422 } else if ( (code==key.backspace)||(code==key.tab && down) || press || key.isCompSymbol(code)){
438 } else if ( (code==key.backspace)||(code==key.tab && down) || press || key.isCompSymbol(code)){
423 439 if( key.isCompSymbol(code) && press)
424 440 {
425 441 var newchar = String.fromCharCode(code);
426 442 typed_characters=typed_characters+newchar;
427 443 } else if (code == key.tab) {
428 444 fastForward = sharedStart(filterd)
429 445 ffsub = fastForward.substr(matched_text.length+typed_characters.length);
430 446 typed_characters=typed_characters+ffsub;
431 447 autopick=true;
432 448 event.stopPropagation();
433 449 event.preventDefault();
434 450 } else if (code == key.backspace && down) {
435 451 // cancel if user have erase everything, otherwise decrease
436 452 // what we filter with
437 453 if (typed_characters.length <= 0)
438 454 {
439 455 insert(matched_text,event)
456 return
440 457 }
441 458 typed_characters=typed_characters.substr(0,typed_characters.length-1);
442 }else{return}
459 } else if (press && code != key.backspace && code != key.tab && code != 0){
460 insert(matched_text+typed_characters,event);
461 return
462 } else {
463 return
464 }
443 465 re = new RegExp("^"+"\%?"+matched_text+typed_characters,"");
444 466 filterd = matches.filter(function(x){return re.test(x)});
445 467 complete_with(filterd,matched_text+typed_characters,autopick,event);
446 } else if(down){ // abort only on .keydown
468 } else if( press || code==key.esc){ // abort only on .keypress or esc
447 469 // abort with what the user have pressed until now
448 470 console.log('aborting with keycode : '+code+' is down :'+down);
449 471 insert(matched_text+typed_characters,event);
450 472 }
451 473 }
452 474 select.keydown(function (event) {
453 475 downandpress(event,1)
454 476 });
455 477 select.keypress(function (event) {
456 478 downandpress(event,0)
457 479 });
458 480 // Double click also causes a pick.
459 481 // and bind the last actions.
460 482 select.dblclick(pick);
461 483 select.blur(close);
462 484 select.focus();
463 485 };
464 486
465 487 CodeCell.prototype.toggle_line_numbers = function () {
466 488 if (this.code_mirror.getOption('lineNumbers') == false) {
467 489 this.code_mirror.setOption('lineNumbers', true);
468 490 } else {
469 491 this.code_mirror.setOption('lineNumbers', false);
470 492 }
471 493 this.code_mirror.refresh();
472 494 };
473 495
474 496 CodeCell.prototype.select = function () {
475 497 IPython.Cell.prototype.select.apply(this);
476 498 // Todo: this dance is needed because as of CodeMirror 2.12, focus is
477 499 // not causing the cursor to blink if the editor is empty initially.
478 500 // While this seems to fix the issue, this should be fixed
479 501 // in CodeMirror proper.
480 502 var s = this.code_mirror.getValue();
481 503 this.code_mirror.focus();
482 504 if (s === '') this.code_mirror.setValue('');
483 505 };
484 506
485 507
486 508 CodeCell.prototype.select_all = function () {
487 509 var start = {line: 0, ch: 0};
488 510 var nlines = this.code_mirror.lineCount();
489 511 var last_line = this.code_mirror.getLine(nlines-1);
490 512 var end = {line: nlines-1, ch: last_line.length};
491 513 this.code_mirror.setSelection(start, end);
492 514 };
493 515
494 516
495 517 CodeCell.prototype.append_output = function (json) {
496 518 this.expand();
497 519 if (json.output_type === 'pyout') {
498 520 this.append_pyout(json);
499 521 } else if (json.output_type === 'pyerr') {
500 522 this.append_pyerr(json);
501 523 } else if (json.output_type === 'display_data') {
502 524 this.append_display_data(json);
503 525 } else if (json.output_type === 'stream') {
504 526 this.append_stream(json);
505 527 };
506 528 this.outputs.push(json);
507 529 };
508 530
509 531
510 532 CodeCell.prototype.create_output_area = function () {
511 533 var oa = $("<div/>").addClass("hbox output_area");
512 534 oa.append($('<div/>').addClass('prompt'));
513 535 return oa;
514 536 };
515 537
516 538
517 539 CodeCell.prototype.append_pyout = function (json) {
518 540 n = json.prompt_number || ' ';
519 541 var toinsert = this.create_output_area();
520 542 toinsert.find('div.prompt').addClass('output_prompt').html('Out[' + n + ']:');
521 543 this.append_mime_type(json, toinsert);
522 544 this.element.find('div.output').append(toinsert);
523 545 // If we just output latex, typeset it.
524 546 if ((json.latex !== undefined) || (json.html !== undefined)) {
525 547 this.typeset();
526 548 };
527 549 };
528 550
529 551
530 552 CodeCell.prototype.append_pyerr = function (json) {
531 553 var tb = json.traceback;
532 554 if (tb !== undefined && tb.length > 0) {
533 555 var s = '';
534 556 var len = tb.length;
535 557 for (var i=0; i<len; i++) {
536 558 s = s + tb[i] + '\n';
537 559 }
538 560 s = s + '\n';
539 561 var toinsert = this.create_output_area();
540 562 this.append_text(s, toinsert);
541 563 this.element.find('div.output').append(toinsert);
542 564 };
543 565 };
544 566
545 567
546 568 CodeCell.prototype.append_stream = function (json) {
547 569 // temporary fix: if stream undefined (json file written prior to this patch),
548 570 // default to most likely stdout:
549 571 if (json.stream == undefined){
550 572 json.stream = 'stdout';
551 573 }
552 574 var subclass = "output_"+json.stream;
553 575 if (this.outputs.length > 0){
554 576 // have at least one output to consider
555 577 var last = this.outputs[this.outputs.length-1];
556 578 if (last.output_type == 'stream' && json.stream == last.stream){
557 579 // latest output was in the same stream,
558 580 // so append directly into its pre tag
559 581 this.element.find('div.'+subclass).last().find('pre').append(json.text);
560 582 return;
561 583 }
562 584 }
563 585
564 586 // If we got here, attach a new div
565 587 var toinsert = this.create_output_area();
566 588 this.append_text(json.text, toinsert, "output_stream "+subclass);
567 589 this.element.find('div.output').append(toinsert);
568 590 };
569 591
570 592
571 593 CodeCell.prototype.append_display_data = function (json) {
572 594 var toinsert = this.create_output_area();
573 595 this.append_mime_type(json, toinsert);
574 596 this.element.find('div.output').append(toinsert);
575 597 // If we just output latex, typeset it.
576 598 if ( (json.latex !== undefined) || (json.html !== undefined) ) {
577 599 this.typeset();
578 600 };
579 601 };
580 602
581 603
582 604 CodeCell.prototype.append_mime_type = function (json, element) {
583 605 if (json.html !== undefined) {
584 606 this.append_html(json.html, element);
585 607 } else if (json.latex !== undefined) {
586 608 this.append_latex(json.latex, element);
587 609 } else if (json.svg !== undefined) {
588 610 this.append_svg(json.svg, element);
589 611 } else if (json.png !== undefined) {
590 612 this.append_png(json.png, element);
591 613 } else if (json.jpeg !== undefined) {
592 614 this.append_jpeg(json.jpeg, element);
593 615 } else if (json.text !== undefined) {
594 616 this.append_text(json.text, element);
595 617 };
596 618 };
597 619
598 620
599 621 CodeCell.prototype.append_html = function (html, element) {
600 622 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_html rendered_html");
601 623 toinsert.append(html);
602 624 element.append(toinsert);
603 625 };
604 626
605 627
606 628 CodeCell.prototype.append_text = function (data, element, extra_class) {
607 629 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_text");
608 630 if (extra_class){
609 631 toinsert.addClass(extra_class);
610 632 }
611 633 toinsert.append($("<pre/>").html(data));
612 634 element.append(toinsert);
613 635 };
614 636
615 637
616 638 CodeCell.prototype.append_svg = function (svg, element) {
617 639 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_svg");
618 640 toinsert.append(svg);
619 641 element.append(toinsert);
620 642 };
621 643
622 644
623 645 CodeCell.prototype.append_png = function (png, element) {
624 646 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_png");
625 647 toinsert.append($("<img/>").attr('src','data:image/png;base64,'+png));
626 648 element.append(toinsert);
627 649 };
628 650
629 651
630 652 CodeCell.prototype.append_jpeg = function (jpeg, element) {
631 653 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_jpeg");
632 654 toinsert.append($("<img/>").attr('src','data:image/jpeg;base64,'+jpeg));
633 655 element.append(toinsert);
634 656 };
635 657
636 658
637 659 CodeCell.prototype.append_latex = function (latex, element) {
638 660 // This method cannot do the typesetting because the latex first has to
639 661 // be on the page.
640 662 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_latex");
641 663 toinsert.append(latex);
642 664 element.append(toinsert);
643 665 };
644 666
645 667
646 668 CodeCell.prototype.clear_output = function (stdout, stderr, other) {
647 669 var output_div = this.element.find("div.output");
648 670 if (stdout && stderr && other){
649 671 // clear all, no need for logic
650 672 output_div.html("");
651 673 this.outputs = [];
652 674 return;
653 675 }
654 676 // remove html output
655 677 // each output_subarea that has an identifying class is in an output_area
656 678 // which is the element to be removed.
657 679 if (stdout){
658 680 output_div.find("div.output_stdout").parent().remove();
659 681 }
660 682 if (stderr){
661 683 output_div.find("div.output_stderr").parent().remove();
662 684 }
663 685 if (other){
664 686 output_div.find("div.output_subarea").not("div.output_stderr").not("div.output_stdout").parent().remove();
665 687 }
666 688
667 689 // remove cleared outputs from JSON list:
668 690 for (var i = this.outputs.length - 1; i >= 0; i--){
669 691 var out = this.outputs[i];
670 692 var output_type = out.output_type;
671 693 if (output_type == "display_data" && other){
672 694 this.outputs.splice(i,1);
673 695 }else if (output_type == "stream"){
674 696 if (stdout && out.stream == "stdout"){
675 697 this.outputs.splice(i,1);
676 698 }else if (stderr && out.stream == "stderr"){
677 699 this.outputs.splice(i,1);
678 700 }
679 701 }
680 702 }
681 703 };
682 704
683 705
684 706 CodeCell.prototype.clear_input = function () {
685 707 this.code_mirror.setValue('');
686 708 };
687 709
688 710
689 711 CodeCell.prototype.collapse = function () {
690 712 if (!this.collapsed) {
691 713 this.element.find('div.output').hide();
692 714 this.collapsed = true;
693 715 };
694 716 };
695 717
696 718
697 719 CodeCell.prototype.expand = function () {
698 720 if (this.collapsed) {
699 721 this.element.find('div.output').show();
700 722 this.collapsed = false;
701 723 };
702 724 };
703 725
704 726
705 727 CodeCell.prototype.toggle_output = function () {
706 728 if (this.collapsed) {
707 729 this.expand();
708 730 } else {
709 731 this.collapse();
710 732 };
711 733 };
712 734
713 735 CodeCell.prototype.set_input_prompt = function (number) {
714 736 var n = number || '&nbsp;';
715 737 this.input_prompt_number = n;
716 738 this.element.find('div.input_prompt').html('In&nbsp;[' + n + ']:');
717 739 };
718 740
719 741
720 742 CodeCell.prototype.get_code = function () {
721 743 return this.code_mirror.getValue();
722 744 };
723 745
724 746
725 747 CodeCell.prototype.set_code = function (code) {
726 748 return this.code_mirror.setValue(code);
727 749 };
728 750
729 751
730 752 CodeCell.prototype.at_top = function () {
731 753 var cursor = this.code_mirror.getCursor();
732 754 if (cursor.line === 0) {
733 755 return true;
734 756 } else {
735 757 return false;
736 758 }
737 759 };
738 760
739 761
740 762 CodeCell.prototype.at_bottom = function () {
741 763 var cursor = this.code_mirror.getCursor();
742 764 if (cursor.line === (this.code_mirror.lineCount()-1)) {
743 765 return true;
744 766 } else {
745 767 return false;
746 768 }
747 769 };
748 770
749 771
750 772 CodeCell.prototype.fromJSON = function (data) {
751 773 console.log('Import from JSON:', data);
752 774 if (data.cell_type === 'code') {
753 775 if (data.input !== undefined) {
754 776 this.set_code(data.input);
755 777 }
756 778 if (data.prompt_number !== undefined) {
757 779 this.set_input_prompt(data.prompt_number);
758 780 } else {
759 781 this.set_input_prompt();
760 782 };
761 783 var len = data.outputs.length;
762 784 for (var i=0; i<len; i++) {
763 785 this.append_output(data.outputs[i]);
764 786 };
765 787 if (data.collapsed !== undefined) {
766 788 if (data.collapsed) {
767 789 this.collapse();
768 790 };
769 791 };
770 792 };
771 793 };
772 794
773 795
774 796 CodeCell.prototype.toJSON = function () {
775 797 var data = {};
776 798 data.input = this.get_code();
777 799 data.cell_type = 'code';
778 800 if (this.input_prompt_number !== ' ') {
779 801 data.prompt_number = this.input_prompt_number;
780 802 };
781 803 var outputs = [];
782 804 var len = this.outputs.length;
783 805 for (var i=0; i<len; i++) {
784 806 outputs[i] = this.outputs[i];
785 807 };
786 808 data.outputs = outputs;
787 809 data.language = 'python';
788 810 data.collapsed = this.collapsed;
789 811 // console.log('Export to JSON:',data);
790 812 return data;
791 813 };
792 814
793 815
794 816 IPython.CodeCell = CodeCell;
795 817
796 818 return IPython;
797 819 }(IPython));
General Comments 0
You need to be logged in to leave comments. Login now