##// END OF EJS Templates
more cleaning
Matthias BUSSONNIER -
Show More
@@ -1,644 +1,641
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 "use strict";
14 14
15 15 var utils = IPython.utils;
16 16 var key = IPython.utils.keycodes;
17 17
18 18 var CodeCell = function (notebook) {
19 19 this.code_mirror = null;
20 20 this.input_prompt_number = null;
21 this.completion_cursor = null;
22 21 this.outputs = [];
23 22 this.collapsed = false;
24 23 this.tooltip_timeout = null;
25 24 this.clear_out_timeout = null;
26 25 IPython.Cell.apply(this, arguments);
27 26 };
28 27
29 28
30 29 CodeCell.prototype = new IPython.Cell();
31 30
32 31
33 32 CodeCell.prototype.create_element = function () {
34 33 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell vbox');
35 34 cell.attr('tabindex','2');
36 35 var input = $('<div></div>').addClass('input hbox');
37 36 input.append($('<div/>').addClass('prompt input_prompt'));
38 37 var input_area = $('<div/>').addClass('input_area box-flex1');
39 38 this.code_mirror = CodeMirror(input_area.get(0), {
40 39 indentUnit : 4,
41 40 mode: 'python',
42 41 theme: 'ipython',
43 42 readOnly: this.read_only,
44 43 onKeyEvent: $.proxy(this.handle_codemirror_keyevent,this)
45 44 });
46 45 input.append(input_area);
47 46 var output = $('<div></div>').addClass('output vbox');
48 47 cell.append(input).append(output);
49 48 this.element = cell;
50 49 this.collapse();
51 50
52 51 // construct a completer
53 52 this.completer = new IPython.Completer(this);
54 53 };
55 54
56 55 //TODO, try to diminish the number of parameters.
57 56 CodeCell.prototype.request_tooltip_after_time = function (pre_cursor,time){
58 57 var that = this;
59 58 if (pre_cursor === "" || pre_cursor === "(" ) {
60 59 // don't do anything if line beggin with '(' or is empty
61 60 } else {
62 61 // Will set a timer to request tooltip in `time`
63 62 that.tooltip_timeout = setTimeout(function(){
64 63 IPython.notebook.request_tool_tip(that, pre_cursor)
65 64 },time);
66 65 }
67 66 };
68 67
69 68 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
70 69 // This method gets called in CodeMirror's onKeyDown/onKeyPress
71 70 // handlers and is used to provide custom key handling. Its return
72 71 // value is used to determine if CodeMirror should ignore the event:
73 72 // true = ignore, false = don't ignore.
74 73
75 74 if (this.read_only){
76 75 return false;
77 76 }
78 77
79 78 // note that we are comparing and setting the time to wait at each key press.
80 79 // a better wqy might be to generate a new function on each time change and
81 80 // assign it to CodeCell.prototype.request_tooltip_after_time
82 81 var tooltip_wait_time = this.notebook.time_before_tooltip;
83 82 var tooltip_on_tab = this.notebook.tooltip_on_tab;
84 83 var that = this;
85 84 // whatever key is pressed, first, cancel the tooltip request before
86 85 // they are sent, and remove tooltip if any
87 86 if(event.type === 'keydown' ) {
88 87 that.remove_and_cancel_tooltip();
89 88 };
90 89
91 90
92 91 if (event.keyCode === key.enter && (event.shiftKey || event.ctrlKey)) {
93 92 // Always ignore shift-enter in CodeMirror as we handle it.
94 93 return true;
95 94 } else if (event.which === 40 && event.type === 'keypress' && tooltip_wait_time >= 0) {
96 95 // triger aon keypress (!) otherwise inconsistent event.which depending on plateform
97 96 // browser and keyboard layout !
98 97 // Pressing '(' , request tooltip, don't forget to reappend it
99 98 var cursor = editor.getCursor();
100 99 var pre_cursor = editor.getRange({line:cursor.line,ch:0},cursor).trim()+'(';
101 100 that.request_tooltip_after_time(pre_cursor,tooltip_wait_time);
102 101 } else if (event.which === key.upArrow) {
103 102 // If we are not at the top, let CM handle the up arrow and
104 103 // prevent the global keydown handler from handling it.
105 104 if (!that.at_top()) {
106 105 event.stop();
107 106 return false;
108 107 } else {
109 108 return true;
110 109 };
111 110 } else if (event.which === key.downArrow) {
112 111 // If we are not at the bottom, let CM handle the down arrow and
113 112 // prevent the global keydown handler from handling it.
114 113 if (!that.at_bottom()) {
115 114 event.stop();
116 115 return false;
117 116 } else {
118 117 return true;
119 118 };
120 119 } else if (event.keyCode === key.tab && event.type == 'keydown') {
121 120 // Tab completion.
122 121 var cur = editor.getCursor();
123 122 //Do not trim here because of tooltip
124 123 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
125 124 if (pre_cursor.trim() === "") {
126 125 // Don't autocomplete if the part of the line before the cursor
127 126 // is empty. In this case, let CodeMirror handle indentation.
128 127 return false;
129 128 } else if ((pre_cursor.substr(-1) === "("|| pre_cursor.substr(-1) === " ") && tooltip_on_tab ) {
130 129 that.request_tooltip_after_time(pre_cursor,0);
131 130 // Prevent the event from bubbling up.
132 131 event.stop();
133 132 // Prevent CodeMirror from handling the tab.
134 133 return true;
135 134 } else {
136 135 event.stop();
137 136 this.completer.startCompletion();
138 137 return true;
139 138 };
140 139 } else if (event.keyCode === key.backspace && event.type == 'keydown') {
141 140 // If backspace and the line ends with 4 spaces, remove them.
142 141 var cur = editor.getCursor();
143 142 var line = editor.getLine(cur.line);
144 143 var ending = line.slice(-4);
145 144 if (ending === ' ') {
146 145 editor.replaceRange('',
147 146 {line: cur.line, ch: cur.ch-4},
148 147 {line: cur.line, ch: cur.ch}
149 148 );
150 149 event.stop();
151 150 return true;
152 151 } else {
153 152 return false;
154 153 };
155 154 } else {
156 155 // keypress/keyup also trigger on TAB press, and we don't want to
157 156 // use those to disable tab completion.
158 157 return false;
159 158 };
160 159 return false;
161 160 };
162 161
163 162 CodeCell.prototype.remove_and_cancel_tooltip = function() {
164 163 // note that we don't handle closing directly inside the calltip
165 164 // as in the completer, because it is not focusable, so won't
166 165 // get the event.
167 166 if (this.tooltip_timeout != null){
168 167 clearTimeout(this.tooltip_timeout);
169 168 $('#tooltip').remove();
170 169 this.tooltip_timeout = null;
171 170 }
172 171 }
173 172
174 173 CodeCell.prototype.finish_tooltip = function (reply) {
175 174 // Extract call tip data; the priority is call, init, main.
176 175 defstring = reply.call_def;
177 176 if (defstring == null) { defstring = reply.init_definition; }
178 177 if (defstring == null) { defstring = reply.definition; }
179 178
180 179 docstring = reply.call_docstring;
181 180 if (docstring == null) { docstring = reply.init_docstring; }
182 181 if (docstring == null) { docstring = reply.docstring; }
183 182 if (docstring == null) { docstring = "<empty docstring>"; }
184 183
185 184 name=reply.name;
186 185
187 186 var that = this;
188 187 var tooltip = $('<div/>').attr('id', 'tooltip').addClass('tooltip');
189 188 // remove to have the tooltip not Limited in X and Y
190 189 tooltip.addClass('smalltooltip');
191 190 var pre=$('<pre/>').html(utils.fixConsole(docstring));
192 191 var expandlink=$('<a/>').attr('href',"#");
193 192 expandlink.addClass("ui-corner-all"); //rounded corner
194 193 expandlink.attr('role',"button");
195 194 //expandlink.addClass('ui-button');
196 195 //expandlink.addClass('ui-state-default');
197 196 var expandspan=$('<span/>').text('Expand');
198 197 expandspan.addClass('ui-icon');
199 198 expandspan.addClass('ui-icon-plus');
200 199 expandlink.append(expandspan);
201 200 expandlink.attr('id','expanbutton');
202 201 expandlink.click(function(){
203 202 tooltip.removeClass('smalltooltip');
204 203 tooltip.addClass('bigtooltip');
205 204 $('#expanbutton').remove();
206 205 setTimeout(function(){that.code_mirror.focus();}, 50);
207 206 });
208 207 var morelink=$('<a/>').attr('href',"#");
209 208 morelink.attr('role',"button");
210 209 morelink.addClass('ui-button');
211 210 //morelink.addClass("ui-corner-all"); //rounded corner
212 211 //morelink.addClass('ui-state-default');
213 212 var morespan=$('<span/>').text('Open in Pager');
214 213 morespan.addClass('ui-icon');
215 214 morespan.addClass('ui-icon-arrowstop-l-n');
216 215 morelink.append(morespan);
217 216 morelink.click(function(){
218 217 var msg_id = IPython.notebook.kernel.execute(name+"?");
219 218 IPython.notebook.msg_cell_map[msg_id] = IPython.notebook.get_selected_cell().cell_id;
220 219 that.remove_and_cancel_tooltip();
221 220 setTimeout(function(){that.code_mirror.focus();}, 50);
222 221 });
223 222
224 223 var closelink=$('<a/>').attr('href',"#");
225 224 closelink.attr('role',"button");
226 225 closelink.addClass('ui-button');
227 226 //closelink.addClass("ui-corner-all"); //rounded corner
228 227 //closelink.adClass('ui-state-default'); // grey background and blue cross
229 228 var closespan=$('<span/>').text('Close');
230 229 closespan.addClass('ui-icon');
231 230 closespan.addClass('ui-icon-close');
232 231 closelink.append(closespan);
233 232 closelink.click(function(){
234 233 that.remove_and_cancel_tooltip();
235 234 setTimeout(function(){that.code_mirror.focus();}, 50);
236 235 });
237 236 //construct the tooltip
238 237 tooltip.append(closelink);
239 238 tooltip.append(expandlink);
240 239 tooltip.append(morelink);
241 240 if(defstring){
242 241 defstring_html = $('<pre/>').html(utils.fixConsole(defstring));
243 242 tooltip.append(defstring_html);
244 243 }
245 244 tooltip.append(pre);
246 245 var pos = this.code_mirror.cursorCoords();
247 246 tooltip.css('left',pos.x+'px');
248 247 tooltip.css('top',pos.yBot+'px');
249 248 $('body').append(tooltip);
250 249
251 250 // issues with cross-closing if multiple tooltip in less than 5sec
252 251 // keep it comented for now
253 252 // setTimeout(that.remove_and_cancel_tooltip, 5000);
254 253 };
255 254
256 255
257 // called when completion came back from the kernel. this will inspect the
258 // curent cell for (more) completion merge the resuults with the ones
259 // comming from the kernel and forward it to the completer
256 // called when completion came back from the kernel. just forward
260 257 CodeCell.prototype.finish_completing = function (matched_text, matches) {
261 258 this.completer.finish_completing(matched_text,matches);
262 259 }
263 260
264 261
265 262 CodeCell.prototype.select = function () {
266 263 IPython.Cell.prototype.select.apply(this);
267 264 this.code_mirror.refresh();
268 265 this.code_mirror.focus();
269 266 // We used to need an additional refresh() after the focus, but
270 267 // it appears that this has been fixed in CM. This bug would show
271 268 // up on FF when a newly loaded markdown cell was edited.
272 269 };
273 270
274 271
275 272 CodeCell.prototype.select_all = function () {
276 273 var start = {line: 0, ch: 0};
277 274 var nlines = this.code_mirror.lineCount();
278 275 var last_line = this.code_mirror.getLine(nlines-1);
279 276 var end = {line: nlines-1, ch: last_line.length};
280 277 this.code_mirror.setSelection(start, end);
281 278 };
282 279
283 280
284 281 CodeCell.prototype.append_output = function (json, dynamic) {
285 282 // If dynamic is true, javascript output will be eval'd.
286 283 this.expand();
287 284 this.flush_clear_timeout();
288 285 if (json.output_type === 'pyout') {
289 286 this.append_pyout(json, dynamic);
290 287 } else if (json.output_type === 'pyerr') {
291 288 this.append_pyerr(json);
292 289 } else if (json.output_type === 'display_data') {
293 290 this.append_display_data(json, dynamic);
294 291 } else if (json.output_type === 'stream') {
295 292 this.append_stream(json);
296 293 };
297 294 this.outputs.push(json);
298 295 };
299 296
300 297
301 298 CodeCell.prototype.create_output_area = function () {
302 299 var oa = $("<div/>").addClass("hbox output_area");
303 300 oa.append($('<div/>').addClass('prompt'));
304 301 return oa;
305 302 };
306 303
307 304
308 305 CodeCell.prototype.append_pyout = function (json, dynamic) {
309 306 var n = json.prompt_number || ' ';
310 307 var toinsert = this.create_output_area();
311 308 toinsert.find('div.prompt').addClass('output_prompt').html('Out[' + n + ']:');
312 309 this.append_mime_type(json, toinsert, dynamic);
313 310 this.element.find('div.output').append(toinsert);
314 311 // If we just output latex, typeset it.
315 312 if ((json.latex !== undefined) || (json.html !== undefined)) {
316 313 this.typeset();
317 314 };
318 315 };
319 316
320 317
321 318 CodeCell.prototype.append_pyerr = function (json) {
322 319 var tb = json.traceback;
323 320 if (tb !== undefined && tb.length > 0) {
324 321 var s = '';
325 322 var len = tb.length;
326 323 for (var i=0; i<len; i++) {
327 324 s = s + tb[i] + '\n';
328 325 }
329 326 s = s + '\n';
330 327 var toinsert = this.create_output_area();
331 328 this.append_text(s, toinsert);
332 329 this.element.find('div.output').append(toinsert);
333 330 };
334 331 };
335 332
336 333
337 334 CodeCell.prototype.append_stream = function (json) {
338 335 // temporary fix: if stream undefined (json file written prior to this patch),
339 336 // default to most likely stdout:
340 337 if (json.stream == undefined){
341 338 json.stream = 'stdout';
342 339 }
343 340 if (!utils.fixConsole(json.text)){
344 341 // fixConsole gives nothing (empty string, \r, etc.)
345 342 // so don't append any elements, which might add undesirable space
346 343 return;
347 344 }
348 345 var subclass = "output_"+json.stream;
349 346 if (this.outputs.length > 0){
350 347 // have at least one output to consider
351 348 var last = this.outputs[this.outputs.length-1];
352 349 if (last.output_type == 'stream' && json.stream == last.stream){
353 350 // latest output was in the same stream,
354 351 // so append directly into its pre tag
355 352 // escape ANSI & HTML specials:
356 353 var text = utils.fixConsole(json.text);
357 354 this.element.find('div.'+subclass).last().find('pre').append(text);
358 355 return;
359 356 }
360 357 }
361 358
362 359 // If we got here, attach a new div
363 360 var toinsert = this.create_output_area();
364 361 this.append_text(json.text, toinsert, "output_stream "+subclass);
365 362 this.element.find('div.output').append(toinsert);
366 363 };
367 364
368 365
369 366 CodeCell.prototype.append_display_data = function (json, dynamic) {
370 367 var toinsert = this.create_output_area();
371 368 this.append_mime_type(json, toinsert, dynamic);
372 369 this.element.find('div.output').append(toinsert);
373 370 // If we just output latex, typeset it.
374 371 if ( (json.latex !== undefined) || (json.html !== undefined) ) {
375 372 this.typeset();
376 373 };
377 374 };
378 375
379 376
380 377 CodeCell.prototype.append_mime_type = function (json, element, dynamic) {
381 378 if (json.javascript !== undefined && dynamic) {
382 379 this.append_javascript(json.javascript, element, dynamic);
383 380 } else if (json.html !== undefined) {
384 381 this.append_html(json.html, element);
385 382 } else if (json.latex !== undefined) {
386 383 this.append_latex(json.latex, element);
387 384 } else if (json.svg !== undefined) {
388 385 this.append_svg(json.svg, element);
389 386 } else if (json.png !== undefined) {
390 387 this.append_png(json.png, element);
391 388 } else if (json.jpeg !== undefined) {
392 389 this.append_jpeg(json.jpeg, element);
393 390 } else if (json.text !== undefined) {
394 391 this.append_text(json.text, element);
395 392 };
396 393 };
397 394
398 395
399 396 CodeCell.prototype.append_html = function (html, element) {
400 397 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_html rendered_html");
401 398 toinsert.append(html);
402 399 element.append(toinsert);
403 400 };
404 401
405 402
406 403 CodeCell.prototype.append_javascript = function (js, container) {
407 404 // We just eval the JS code, element appears in the local scope.
408 405 var element = $("<div/>").addClass("box_flex1 output_subarea");
409 406 container.append(element);
410 407 // Div for js shouldn't be drawn, as it will add empty height to the area.
411 408 container.hide();
412 409 // If the Javascript appends content to `element` that should be drawn, then
413 410 // it must also call `container.show()`.
414 411 eval(js);
415 412 }
416 413
417 414
418 415 CodeCell.prototype.append_text = function (data, element, extra_class) {
419 416 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_text");
420 417 // escape ANSI & HTML specials in plaintext:
421 418 data = utils.fixConsole(data);
422 419 if (extra_class){
423 420 toinsert.addClass(extra_class);
424 421 }
425 422 toinsert.append($("<pre/>").html(data));
426 423 element.append(toinsert);
427 424 };
428 425
429 426
430 427 CodeCell.prototype.append_svg = function (svg, element) {
431 428 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_svg");
432 429 toinsert.append(svg);
433 430 element.append(toinsert);
434 431 };
435 432
436 433
437 434 CodeCell.prototype.append_png = function (png, element) {
438 435 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_png");
439 436 toinsert.append($("<img/>").attr('src','data:image/png;base64,'+png));
440 437 element.append(toinsert);
441 438 };
442 439
443 440
444 441 CodeCell.prototype.append_jpeg = function (jpeg, element) {
445 442 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_jpeg");
446 443 toinsert.append($("<img/>").attr('src','data:image/jpeg;base64,'+jpeg));
447 444 element.append(toinsert);
448 445 };
449 446
450 447
451 448 CodeCell.prototype.append_latex = function (latex, element) {
452 449 // This method cannot do the typesetting because the latex first has to
453 450 // be on the page.
454 451 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_latex");
455 452 toinsert.append(latex);
456 453 element.append(toinsert);
457 454 };
458 455
459 456
460 457 CodeCell.prototype.clear_output = function (stdout, stderr, other) {
461 458 var that = this;
462 459 if (this.clear_out_timeout != null){
463 460 // fire previous pending clear *immediately*
464 461 clearTimeout(this.clear_out_timeout);
465 462 this.clear_out_timeout = null;
466 463 this.clear_output_callback(this._clear_stdout, this._clear_stderr, this._clear_other);
467 464 }
468 465 // store flags for flushing the timeout
469 466 this._clear_stdout = stdout;
470 467 this._clear_stderr = stderr;
471 468 this._clear_other = other;
472 469 this.clear_out_timeout = setTimeout(function(){
473 470 // really clear timeout only after a short delay
474 471 // this reduces flicker in 'clear_output; print' cases
475 472 that.clear_out_timeout = null;
476 473 that._clear_stdout = that._clear_stderr = that._clear_other = null;
477 474 that.clear_output_callback(stdout, stderr, other);
478 475 }, 500
479 476 );
480 477 };
481 478
482 479 CodeCell.prototype.clear_output_callback = function (stdout, stderr, other) {
483 480 var output_div = this.element.find("div.output");
484 481
485 482 if (stdout && stderr && other){
486 483 // clear all, no need for logic
487 484 output_div.html("");
488 485 this.outputs = [];
489 486 return;
490 487 }
491 488 // remove html output
492 489 // each output_subarea that has an identifying class is in an output_area
493 490 // which is the element to be removed.
494 491 if (stdout){
495 492 output_div.find("div.output_stdout").parent().remove();
496 493 }
497 494 if (stderr){
498 495 output_div.find("div.output_stderr").parent().remove();
499 496 }
500 497 if (other){
501 498 output_div.find("div.output_subarea").not("div.output_stderr").not("div.output_stdout").parent().remove();
502 499 }
503 500
504 501 // remove cleared outputs from JSON list:
505 502 for (var i = this.outputs.length - 1; i >= 0; i--){
506 503 var out = this.outputs[i];
507 504 var output_type = out.output_type;
508 505 if (output_type == "display_data" && other){
509 506 this.outputs.splice(i,1);
510 507 }else if (output_type == "stream"){
511 508 if (stdout && out.stream == "stdout"){
512 509 this.outputs.splice(i,1);
513 510 }else if (stderr && out.stream == "stderr"){
514 511 this.outputs.splice(i,1);
515 512 }
516 513 }
517 514 }
518 515 };
519 516
520 517
521 518 CodeCell.prototype.clear_input = function () {
522 519 this.code_mirror.setValue('');
523 520 };
524 521
525 522 CodeCell.prototype.flush_clear_timeout = function() {
526 523 var output_div = this.element.find('div.output');
527 524 if (this.clear_out_timeout){
528 525 clearTimeout(this.clear_out_timeout);
529 526 this.clear_out_timeout = null;
530 527 this.clear_output_callback(this._clear_stdout, this._clear_stderr, this._clear_other);
531 528 };
532 529 }
533 530
534 531
535 532 CodeCell.prototype.collapse = function () {
536 533 if (!this.collapsed) {
537 534 this.element.find('div.output').hide();
538 535 this.collapsed = true;
539 536 };
540 537 };
541 538
542 539
543 540 CodeCell.prototype.expand = function () {
544 541 if (this.collapsed) {
545 542 this.element.find('div.output').show();
546 543 this.collapsed = false;
547 544 };
548 545 };
549 546
550 547
551 548 CodeCell.prototype.toggle_output = function () {
552 549 if (this.collapsed) {
553 550 this.expand();
554 551 } else {
555 552 this.collapse();
556 553 };
557 554 };
558 555
559 556 CodeCell.prototype.set_input_prompt = function (number) {
560 557 this.input_prompt_number = number;
561 558 var ns = number || "&nbsp;";
562 559 this.element.find('div.input_prompt').html('In&nbsp;[' + ns + ']:');
563 560 };
564 561
565 562
566 563 CodeCell.prototype.get_text = function () {
567 564 return this.code_mirror.getValue();
568 565 };
569 566
570 567
571 568 CodeCell.prototype.set_text = function (code) {
572 569 return this.code_mirror.setValue(code);
573 570 };
574 571
575 572
576 573 CodeCell.prototype.at_top = function () {
577 574 var cursor = this.code_mirror.getCursor();
578 575 if (cursor.line === 0) {
579 576 return true;
580 577 } else {
581 578 return false;
582 579 }
583 580 };
584 581
585 582
586 583 CodeCell.prototype.at_bottom = function () {
587 584 var cursor = this.code_mirror.getCursor();
588 585 if (cursor.line === (this.code_mirror.lineCount()-1)) {
589 586 return true;
590 587 } else {
591 588 return false;
592 589 }
593 590 };
594 591
595 592
596 593 CodeCell.prototype.fromJSON = function (data) {
597 594 if (data.cell_type === 'code') {
598 595 if (data.input !== undefined) {
599 596 this.set_text(data.input);
600 597 }
601 598 if (data.prompt_number !== undefined) {
602 599 this.set_input_prompt(data.prompt_number);
603 600 } else {
604 601 this.set_input_prompt();
605 602 };
606 603 var len = data.outputs.length;
607 604 for (var i=0; i<len; i++) {
608 605 // append with dynamic=false.
609 606 this.append_output(data.outputs[i], false);
610 607 };
611 608 if (data.collapsed !== undefined) {
612 609 if (data.collapsed) {
613 610 this.collapse();
614 611 } else {
615 612 this.expand();
616 613 };
617 614 };
618 615 };
619 616 };
620 617
621 618
622 619 CodeCell.prototype.toJSON = function () {
623 620 var data = {};
624 621 data.input = this.get_text();
625 622 data.cell_type = 'code';
626 623 if (this.input_prompt_number) {
627 624 data.prompt_number = this.input_prompt_number;
628 625 };
629 626 var outputs = [];
630 627 var len = this.outputs.length;
631 628 for (var i=0; i<len; i++) {
632 629 outputs[i] = this.outputs[i];
633 630 };
634 631 data.outputs = outputs;
635 632 data.language = 'python';
636 633 data.collapsed = this.collapsed;
637 634 return data;
638 635 };
639 636
640 637
641 638 IPython.CodeCell = CodeCell;
642 639
643 640 return IPython;
644 641 }(IPython));
@@ -1,246 +1,230
1 1 // function completer.
2 2 //
3 3 // completer should be a class that take an cell instance
4 //
5 4
6 5 var IPython = (function(IPython ) {
7 6 // that will prevent us from misspelling
8 7 "use strict";
9 8
10 9 // easyier key mapping
11 10 var key = IPython.utils.keycodes;
12 11
13 12 // what is the common start of all completions
14 13 function sharedStart(B){
15 14 if(B.length == 1){return B[0]}
16 15 var A = new Array()
17 16 for(var i=0; i< B.length; i++)
18 17 {
19 18 A.push(B[i].str);
20 19 }
21 20 if(A.length > 1 ){
22 21 var tem1, tem2, s, A = A.slice(0).sort();
23 22 tem1 = A[0];
24 23 s = tem1.length;
25 24 tem2 = A.pop();
26 25 while(s && tem2.indexOf(tem1) == -1){
27 26 tem1 = tem1.substring(0, --s);
28 27 }
29 28 if (tem1 == "" || tem2.indexOf(tem1) != 0){return null;}
30 29 return { str : tem1,
31 30 type : "computed",
32 31 from : B[0].from,
33 32 to : B[0].to
34 33 };
35 34 }
36 35 return null;
37 36 }
38 37
39 38
40 39 var Completer = function(cell) {
41 40 this.cell = cell;
42 41 this.editor = cell.code_mirror;
43 42 // if last caractere before cursor is not in this, we stop completing
44 43 this.reg = /[A-Za-z.]/;
45 44 }
46 45
47 46 Completer.prototype.kernelCompletionRequest = function(){
48 47 var cur = this.editor.getCursor();
49 // Autocomplete the current line.
50 48 var line = this.editor.getLine(cur.line);
51 // one could fork here and directly call finish completing
52 // if kernel is busy
49 // one could fork here and directly call finish completing if kernel is busy
53 50 IPython.notebook.complete_cell(this.cell, line, cur.ch);
54 51 }
55 52
56 53
57 54 Completer.prototype.startCompletion = function()
58 55 {
59 56 // call for a 'first' completion, that will set the editor and do some
60 57 // special behaviour like autopicking if only one completion availlable
61 58 //
62 59 if (this.editor.somethingSelected()) return;
63 60 this.done = false;
64 61 // use to get focus back on opera
65 62 this.carryOnCompletion(true);
66 63 }
67 64
68 Completer.prototype.carryOnCompletion = function(ff)
69 {
70 // pass true as parameter if you want the commpleter to autopick
71 // when only one completion
72 // as this function is automatically reinvoked at each keystroke with
73 // ff = false
65 Completer.prototype.carryOnCompletion = function(ff) {
66 // Pass true as parameter if you want the commpleter to autopick when
67 // only one completion. This function is automatically reinvoked at
68 // each keystroke with ff = false
69
74 70 var cur = this.editor.getCursor();
75 71 var pre_cursor = this.editor.getRange({line:cur.line,ch:cur.ch-1},cur);
76 72
77 73 // we need to check that we are still on a word boundary
78 74 // because while typing the completer is still reinvoking itself
79 75 if(!this.reg.test(pre_cursor)){ this.close(); return;}
80 76
81 77 this.autopick = false;
82 78 if( ff != 'undefined' && ff==true)
83 {
84 this.autopick=true;
85 }
79 { this.autopick=true; }
80
86 81 // We want a single cursor position.
87 82 if (this.editor.somethingSelected()) return;
88 83
89 84 //one kernel completion came back, finish_completing will be called with the results
90 85 this.kernelCompletionRequest();
91 86 }
92 87
93 88 Completer.prototype.finish_completing =function (matched_text, matches) {
94 // let's build a function that wrap all that stuff into what is needed for the
95 // new completer:
96 //
89 // let's build a function that wrap all that stuff into what is needed
90 // for the new completer:
91
97 92 var cur = this.editor.getCursor();
98 93 var results = CodeMirror.contextHint(this.editor);
99 94
100 // append the introspection result, in order, at
101 // at the beginning of the table and compute the replacement rance
102 // from current cursor positon and matched_text length.
95 // append the introspection result, in order, at at the beginning of
96 // the table and compute the replacement range from current cursor
97 // positon and matched_text length.
103 98 for(var i= matches.length-1; i>=0 ;--i)
104 99 {
105 100 results.unshift(
106 {
107 str : matches[i],
101 { str : matches[i],
108 102 type : "introspection",
109 103 from : {line: cur.line, ch: cur.ch-matched_text.length},
110 104 to : {line: cur.line, ch: cur.ch}
111 }
112 )
105 })
113 106 }
114 107
115 108 // one the 2 sources results have been merge, deal with it
116 109 this.raw_result = results;
117 110
118 111 // if empty result return
119 112 if (!this.raw_result || !this.raw_result.length) return;
120 113
121 114 // When there is only one completion, use it directly.
122 115 if (this.autopick == true && this.raw_result.length == 1)
123 116 {
124 117 this.insert(this.raw_result[0]);
125 118 return true;
126 119 }
127 120
128 121 if (this.raw_result.length == 1)
129 122 {
130 123 // test if first and only completion totally matches
131 124 // what is typed, in this case dismiss
132 125 var str = this.raw_result[0].str
133 126 var cur = this.editor.getCursor();
134 127 var pre_cursor = this.editor.getRange({line:cur.line,ch:cur.ch-str.length},cur);
135 if(pre_cursor == str){
136 this.close();
137 return ;
138 }
128 if(pre_cursor == str)
129 { this.close(); return ; }
139 130 }
140 131
141 132 this.complete = $('<div/>').addClass('completions');
142 133 this.complete.attr('id','complete');
143 134
144 135 this.sel = $('<select/>')
145 136 .attr('multiple','true')
146 137 .attr('size',Math.min(10,this.raw_result.length));
147 138 var pos = this.editor.cursorCoords();
148 139
149 140 // TODO: I propose to remove enough horizontal pixel
150 141 // to align the text later
151 142 this.complete.css('left',pos.x+'px');
152 143 this.complete.css('top',pos.yBot+'px');
153 144 this.complete.append(this.sel);
154 145
155 146 $('body').append(this.complete);
156 147 //build the container
157 148 var that = this;
158 149 this.sel.dblclick(function(){that.pick()});
159 150 this.sel.blur(this.close);
160 151 this.sel.keydown(function(event){that.keydown(event)});
161 152
162 153 this.build_gui_list(this.raw_result);
163 //CodeMirror.connect(that.sel, "dblclick", function(){that.pick()});
164 154
165 155 this.sel.focus();
166 156 // Opera sometimes ignores focusing a freshly created node
167 157 if (window.opera) setTimeout(function(){if (!this.done) this.sel.focus();}, 100);
168 // why do we return true ?
169 158 return true;
170 159 }
171 160
172 161 Completer.prototype.insert = function(completion) {
173 162 this.editor.replaceRange(completion.str, completion.from, completion.to);
174 163 }
175 164
176 165 Completer.prototype.build_gui_list = function(completions){
177 166 // Need to clear the all list
178 167 for (var i = 0; i < completions.length; ++i) {
179 168 var opt = $('<option/>')
180 169 .text(completions[i].str)
181 170 .addClass(completions[i].type);
182 171 this.sel.append(opt);
183 172 }
184 173 this.sel.children().first().attr('selected','true');
185
186 //sel.size = Math.min(10, completions.length);
187 // Hack to hide the scrollbar.
188 //if (completions.length <= 10)
189 //this.complete.style.width = (this.sel.clientWidth - 1) + "px";
190 174 }
191 175
192 176 Completer.prototype.close = function() {
193 177 if (this.done) return;
194 178 this.done = true;
195 179 $('.completions').remove();
196 180 }
197 181
198 182 Completer.prototype.pick = function(){
199 183 this.insert(this.raw_result[this.sel[0].selectedIndex]);
200 184 this.close();
201 185 var that = this;
202 186 setTimeout(function(){that.editor.focus();}, 50);
203 187 }
204 188
205 189
206 190 Completer.prototype.keydown = function(event) {
207 191 var code = event.keyCode;
208 192 // Enter
209 193 if (code == key.enter) {CodeMirror.e_stop(event); this.pick();}
210 194 // Escape or backspace
211 195 else if (code == key.esc ) {CodeMirror.e_stop(event); this.close(); this.editor.focus();}
212 196 else if (code == key.space || code == key.backspace) {this.close(); this.editor.focus();}
213 197 else if (code == key.tab){
214 198 //all the fastforwarding operation,
215 199 //Check that shared start is not null which can append with prefixed completion
216 200 // like %pylab , pylab have no shred start, and ff will result in py<tab><tab>
217 201 // to erase py
218 202 var sh = sharedStart(this.raw_result);
219 203 if(sh){
220 204 this.insert(sh);
221 205 }
222 206 this.close();
223 207 CodeMirror.e_stop(event);
224 208 this.editor.focus();
225 209 //reinvoke self
226 210 var that = this;
227 211 setTimeout(function(){that.carryOnCompletion();}, 50);
228 212 }
229 213 else if (code == key.upArrow || code == key.downArrow) {
230 214 // need to do that to be able to move the arrow
231 215 // when on the first or last line ofo a code cell
232 216 event.stopPropagation();
233 217 }
234 218 else if (code != key.upArrow && code != key.downArrow) {
235 219 this.close(); this.editor.focus();
236 220 //we give focus to the editor immediately and call sell in 50 ms
237 221 var that = this;
238 222 setTimeout(function(){that.carryOnCompletion();}, 50);
239 223 }
240 224 }
241 225
242 226
243 227 IPython.Completer = Completer;
244 228
245 229 return IPython;
246 230 }(IPython));
General Comments 0
You need to be logged in to leave comments. Login now