##// END OF EJS Templates
Getting a lot closer...
Jonathan Frederic -
Show More
@@ -1,578 +1,581
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 * An extendable module that provide base functionnality to create cell for notebook.
13 13 * @module IPython
14 14 * @namespace IPython
15 15 * @submodule CodeCell
16 16 */
17 17
18 18
19 19 /* local util for codemirror */
20 20 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;};
21 21
22 22 /**
23 23 *
24 24 * function to delete until previous non blanking space character
25 25 * or first multiple of 4 tabstop.
26 26 * @private
27 27 */
28 28 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
29 29 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
30 30 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
31 31 var cur = cm.getCursor(), line = cm.getLine(cur.line);
32 32 var tabsize = cm.getOption('tabSize');
33 33 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
34 34 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
35 35 var select = cm.getRange(from,cur);
36 36 if( select.match(/^\ +$/) !== null){
37 37 cm.replaceRange("",from,cur);
38 38 } else {
39 39 cm.deleteH(-1,"char");
40 40 }
41 41 };
42 42
43 43
44 44 var IPython = (function (IPython) {
45 45 "use strict";
46 46
47 47 var utils = IPython.utils;
48 48 var keycodes = IPython.keyboard.keycodes;
49 49
50 50 /**
51 51 * A Cell conceived to write code.
52 52 *
53 53 * The kernel doesn't have to be set at creation time, in that case
54 54 * it will be null and set_kernel has to be called later.
55 55 * @class CodeCell
56 56 * @extends IPython.Cell
57 57 *
58 58 * @constructor
59 59 * @param {Object|null} kernel
60 60 * @param {object|undefined} [options]
61 61 * @param [options.cm_config] {object} config to pass to CodeMirror
62 62 */
63 63 var CodeCell = function (kernel, options) {
64 64 this.kernel = kernel || null;
65 65 this.collapsed = false;
66 66
67 67 // create all attributed in constructor function
68 68 // even if null for V8 VM optimisation
69 69 this.input_prompt_number = null;
70 70 this.celltoolbar = null;
71 71 this.output_area = null;
72 72 this.last_msg_id = null;
73 73 this.completer = null;
74 74
75 75
76 76 var cm_overwrite_options = {
77 77 onKeyEvent: $.proxy(this.handle_keyevent,this)
78 78 };
79 79
80 80 options = this.mergeopt(CodeCell, options, {cm_config:cm_overwrite_options});
81 81
82 82 IPython.Cell.apply(this,[options]);
83 83
84 84 // Attributes we want to override in this subclass.
85 85 this.cell_type = "code";
86 86
87 87 var that = this;
88 88 this.element.focusout(
89 89 function() { that.auto_highlight(); }
90 90 );
91 91 };
92 92
93 93 CodeCell.options_default = {
94 94 cm_config : {
95 95 extraKeys: {
96 96 "Tab" : "indentMore",
97 97 "Shift-Tab" : "indentLess",
98 98 "Backspace" : "delSpaceToPrevTabStop",
99 99 "Cmd-/" : "toggleComment",
100 100 "Ctrl-/" : "toggleComment"
101 101 },
102 102 mode: 'ipython',
103 103 theme: 'ipython',
104 104 matchBrackets: true,
105 105 autoCloseBrackets: true
106 106 }
107 107 };
108 108
109 109 CodeCell.msg_cells = {};
110 110
111 111 CodeCell.prototype = new IPython.Cell();
112 112
113 113 /**
114 114 * @method auto_highlight
115 115 */
116 116 CodeCell.prototype.auto_highlight = function () {
117 117 this._auto_highlight(IPython.config.cell_magic_highlight);
118 118 };
119 119
120 120 /** @method create_element */
121 121 CodeCell.prototype.create_element = function () {
122 122 IPython.Cell.prototype.create_element.apply(this, arguments);
123 123
124 124 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell');
125 125 cell.attr('tabindex','2');
126 126
127 127 var input = $('<div></div>').addClass('input');
128 128 var prompt = $('<div/>').addClass('prompt input_prompt');
129 129 var inner_cell = $('<div/>').addClass('inner_cell');
130 130 this.celltoolbar = new IPython.CellToolbar(this);
131 131 inner_cell.append(this.celltoolbar.element);
132 132 var input_area = $('<div/>').addClass('input_area');
133 133 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
134 134 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
135 135 inner_cell.append(input_area);
136 136 input.append(prompt).append(inner_cell);
137 137
138 138 var widget_area = $('<div/>')
139 139 .addClass('widget-area')
140 140 .hide();
141 141 this.widget_area = widget_area;
142 142 var widget_prompt = $('<div/>')
143 143 .addClass('prompt')
144 144 .appendTo(widget_area);
145 145 var widget_subarea = $('<div/>')
146 146 .addClass('widget-subarea')
147 147 .appendTo(widget_area);
148 148 this.widget_subarea = widget_subarea;
149 149 var widget_clear_buton = $('<button />')
150 150 .addClass('close')
151 151 .html('&times;')
152 152 .click(function() {
153 153 widget_area.slideUp('', function(){ widget_subarea.html(''); });
154 154 })
155 155 .appendTo(widget_prompt);
156 156
157 157 var output = $('<div></div>');
158 158 cell.append(input).append(widget_area).append(output);
159 159 this.element = cell;
160 160 this.output_area = new IPython.OutputArea(output, true);
161 161 this.completer = new IPython.Completer(this);
162 162 };
163 163
164 164 /** @method bind_events */
165 165 CodeCell.prototype.bind_events = function () {
166 166 IPython.Cell.prototype.bind_events.apply(this);
167 167 var that = this;
168 168
169 169 this.element.focusout(
170 170 function() { that.auto_highlight(); }
171 171 );
172 172 };
173 173
174 174 CodeCell.prototype.handle_keyevent = function (editor, event) {
175 175
176 176 // console.log('CM', this.mode, event.which, event.type)
177 177
178 178 if (this.mode === 'command') {
179 179 return true;
180 180 } else if (this.mode === 'edit') {
181 181 return this.handle_codemirror_keyevent(editor, event);
182 182 }
183 183 };
184 184
185 185 /**
186 186 * This method gets called in CodeMirror's onKeyDown/onKeyPress
187 187 * handlers and is used to provide custom key handling. Its return
188 188 * value is used to determine if CodeMirror should ignore the event:
189 189 * true = ignore, false = don't ignore.
190 190 * @method handle_codemirror_keyevent
191 191 */
192 192 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
193 193
194 194 var that = this;
195 195 // whatever key is pressed, first, cancel the tooltip request before
196 196 // they are sent, and remove tooltip if any, except for tab again
197 197 var tooltip_closed = null;
198 198 if (event.type === 'keydown' && event.which != keycodes.tab ) {
199 199 tooltip_closed = IPython.tooltip.remove_and_cancel_tooltip();
200 200 }
201 201
202 202 var cur = editor.getCursor();
203 203 if (event.keyCode === keycodes.enter){
204 204 this.auto_highlight();
205 205 }
206 206
207 207 if (event.keyCode === keycodes.enter && (event.shiftKey || event.ctrlKey || event.altKey)) {
208 208 // Always ignore shift-enter in CodeMirror as we handle it.
209 209 return true;
210 210 } else if (event.which === 40 && event.type === 'keypress' && IPython.tooltip.time_before_tooltip >= 0) {
211 211 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
212 212 // browser and keyboard layout !
213 213 // Pressing '(' , request tooltip, don't forget to reappend it
214 214 // The second argument says to hide the tooltip if the docstring
215 215 // is actually empty
216 216 IPython.tooltip.pending(that, true);
217 217 } else if (event.which === keycodes.up && event.type === 'keydown') {
218 218 // If we are not at the top, let CM handle the up arrow and
219 219 // prevent the global keydown handler from handling it.
220 220 if (!that.at_top()) {
221 221 event.stop();
222 222 return false;
223 223 } else {
224 224 return true;
225 225 }
226 226 } else if (event.which === keycodes.esc && event.type === 'keydown') {
227 227 // First see if the tooltip is active and if so cancel it.
228 228 if (tooltip_closed) {
229 229 // The call to remove_and_cancel_tooltip above in L177 doesn't pass
230 230 // force=true. Because of this it won't actually close the tooltip
231 231 // if it is in sticky mode. Thus, we have to check again if it is open
232 232 // and close it with force=true.
233 233 if (!IPython.tooltip._hidden) {
234 234 IPython.tooltip.remove_and_cancel_tooltip(true);
235 235 }
236 236 // If we closed the tooltip, don't let CM or the global handlers
237 237 // handle this event.
238 238 event.stop();
239 239 return true;
240 240 }
241 241 if (that.code_mirror.options.keyMap === "vim-insert") {
242 242 // vim keyMap is active and in insert mode. In this case we leave vim
243 243 // insert mode, but remain in notebook edit mode.
244 244 // Let' CM handle this event and prevent global handling.
245 245 event.stop();
246 246 return false;
247 247 } else {
248 248 // vim keyMap is not active. Leave notebook edit mode.
249 249 // Don't let CM handle the event, defer to global handling.
250 250 return true;
251 251 }
252 252 } else if (event.which === keycodes.down && event.type === 'keydown') {
253 253 // If we are not at the bottom, let CM handle the down arrow and
254 254 // prevent the global keydown handler from handling it.
255 255 if (!that.at_bottom()) {
256 256 event.stop();
257 257 return false;
258 258 } else {
259 259 return true;
260 260 }
261 261 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
262 262 if (editor.somethingSelected()){
263 263 var anchor = editor.getCursor("anchor");
264 264 var head = editor.getCursor("head");
265 265 if( anchor.line != head.line){
266 266 return false;
267 267 }
268 268 }
269 269 IPython.tooltip.request(that);
270 270 event.stop();
271 271 return true;
272 272 } else if (event.keyCode === keycodes.tab && event.type == 'keydown') {
273 273 // Tab completion.
274 274 IPython.tooltip.remove_and_cancel_tooltip();
275 275 if (editor.somethingSelected()) {
276 276 return false;
277 277 }
278 278 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
279 279 if (pre_cursor.trim() === "") {
280 280 // Don't autocomplete if the part of the line before the cursor
281 281 // is empty. In this case, let CodeMirror handle indentation.
282 282 return false;
283 283 } else {
284 284 event.stop();
285 285 this.completer.startCompletion();
286 286 return true;
287 287 }
288 288 } else {
289 289 // keypress/keyup also trigger on TAB press, and we don't want to
290 290 // use those to disable tab completion.
291 291 return false;
292 292 }
293 293 return false;
294 294 };
295 295
296 296 // Kernel related calls.
297 297
298 298 CodeCell.prototype.set_kernel = function (kernel) {
299 299 this.kernel = kernel;
300 300 };
301 301
302 302 /**
303 303 * Execute current code cell to the kernel
304 304 * @method execute
305 305 */
306 306 CodeCell.prototype.execute = function () {
307 307 this.output_area.clear_output();
308 308
309 309 // Clear widget area
310 310 this.widget_subarea.html('');
311 311 this.widget_subarea.height('');
312 312 this.widget_area.height('');
313 313 this.widget_area.hide();
314 314
315 315 this.set_input_prompt('*');
316 316 this.element.addClass("running");
317 317 if (this.last_msg_id) {
318 318 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
319 319 }
320 320 var callbacks = this.get_callbacks();
321 321
322 322 var old_msg_id = this.last_msg_id;
323 323 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
324 324 if (old_msg_id) {
325 325 delete CodeCell.msg_cells[old_msg_id];
326 326 }
327 327 CodeCell.msg_cells[this.last_msg_id] = this;
328 328 };
329 329
330 330 /**
331 331 * Construct the default callbacks for
332 332 * @method get_callbacks
333 333 */
334 334 CodeCell.prototype.get_callbacks = function () {
335 335 return {
336 336 shell : {
337 337 reply : $.proxy(this._handle_execute_reply, this),
338 338 payload : {
339 339 set_next_input : $.proxy(this._handle_set_next_input, this),
340 340 page : $.proxy(this._open_with_pager, this)
341 341 }
342 342 },
343 343 iopub : {
344 344 output : $.proxy(this.output_area.handle_output, this.output_area),
345 345 clear_output : $.proxy(this.output_area.handle_clear_output, this.output_area),
346 346 },
347 347 input : $.proxy(this._handle_input_request, this)
348 348 };
349 349 };
350 350
351 351 CodeCell.prototype._open_with_pager = function (payload) {
352 352 $([IPython.events]).trigger('open_with_text.Pager', payload);
353 353 };
354 354
355 355 /**
356 356 * @method _handle_execute_reply
357 357 * @private
358 358 */
359 359 CodeCell.prototype._handle_execute_reply = function (msg) {
360 360 this.set_input_prompt(msg.content.execution_count);
361 361 this.element.removeClass("running");
362 362 $([IPython.events]).trigger('set_dirty.Notebook', {value: true});
363 363 };
364 364
365 365 /**
366 366 * @method _handle_set_next_input
367 367 * @private
368 368 */
369 369 CodeCell.prototype._handle_set_next_input = function (payload) {
370 370 var data = {'cell': this, 'text': payload.text};
371 371 $([IPython.events]).trigger('set_next_input.Notebook', data);
372 372 };
373 373
374 374 /**
375 375 * @method _handle_input_request
376 376 * @private
377 377 */
378 378 CodeCell.prototype._handle_input_request = function (msg) {
379 379 this.output_area.append_raw_input(msg);
380 380 };
381 381
382 382
383 383 // Basic cell manipulation.
384 384
385 385 CodeCell.prototype.select = function () {
386 386 var cont = IPython.Cell.prototype.select.apply(this);
387 387 if (cont) {
388 388 this.code_mirror.refresh();
389 389 this.auto_highlight();
390 390 }
391 391 return cont;
392 392 };
393 393
394 394 CodeCell.prototype.render = function () {
395 395 var cont = IPython.Cell.prototype.render.apply(this);
396 396 // Always execute, even if we are already in the rendered state
397 397 return cont;
398 398 };
399 399
400 400 CodeCell.prototype.unrender = function () {
401 401 // CodeCell is always rendered
402 402 return false;
403 403 };
404 404
405 405 CodeCell.prototype.select_all = function () {
406 406 var start = {line: 0, ch: 0};
407 407 var nlines = this.code_mirror.lineCount();
408 408 var last_line = this.code_mirror.getLine(nlines-1);
409 409 var end = {line: nlines-1, ch: last_line.length};
410 410 this.code_mirror.setSelection(start, end);
411 411 };
412 412
413 413
414 414 CodeCell.prototype.collapse_output = function () {
415 415 this.collapsed = true;
416 416 this.output_area.collapse();
417 417 };
418 418
419 419
420 420 CodeCell.prototype.expand_output = function () {
421 421 this.collapsed = false;
422 422 this.output_area.expand();
423 423 this.output_area.unscroll_area();
424 424 };
425 425
426 426 CodeCell.prototype.scroll_output = function () {
427 427 this.output_area.expand();
428 428 this.output_area.scroll_if_long();
429 429 };
430 430
431 431 CodeCell.prototype.toggle_output = function () {
432 432 this.collapsed = Boolean(1 - this.collapsed);
433 433 this.output_area.toggle_output();
434 434 };
435 435
436 436 CodeCell.prototype.toggle_output_scroll = function () {
437 437 this.output_area.toggle_scroll();
438 438 };
439 439
440 440
441 441 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
442 442 var ns;
443 443 if (prompt_value == undefined) {
444 444 ns = "&nbsp;";
445 445 } else {
446 446 ns = encodeURIComponent(prompt_value);
447 447 }
448 448 return 'In&nbsp;[' + ns + ']:';
449 449 };
450 450
451 451 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
452 452 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
453 453 for(var i=1; i < lines_number; i++) {
454 454 html.push(['...:']);
455 455 }
456 456 return html.join('<br/>');
457 457 };
458 458
459 459 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
460 460
461 461
462 462 CodeCell.prototype.set_input_prompt = function (number) {
463 463 var nline = 1;
464 464 if (this.code_mirror !== undefined) {
465 465 nline = this.code_mirror.lineCount();
466 466 }
467 467 this.input_prompt_number = number;
468 468 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
469 469 // This HTML call is okay because the user contents are escaped.
470 470 this.element.find('div.input_prompt').html(prompt_html);
471 471 };
472 472
473 473
474 474 CodeCell.prototype.clear_input = function () {
475 475 this.code_mirror.setValue('');
476 476 };
477 477
478 478
479 479 CodeCell.prototype.get_text = function () {
480 480 return this.code_mirror.getValue();
481 481 };
482 482
483 483
484 484 CodeCell.prototype.set_text = function (code) {
485 485 return this.code_mirror.setValue(code);
486 486 };
487 487
488 488
489 489 CodeCell.prototype.at_top = function () {
490 490 var cursor = this.code_mirror.getCursor();
491 491 if (cursor.line === 0 && cursor.ch === 0) {
492 492 return true;
493 493 } else {
494 494 return false;
495 495 }
496 496 };
497 497
498 498
499 499 CodeCell.prototype.at_bottom = function () {
500 500 var cursor = this.code_mirror.getCursor();
501 501 if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) {
502 502 return true;
503 503 } else {
504 504 return false;
505 505 }
506 506 };
507 507
508 508
509 509 CodeCell.prototype.clear_output = function (wait) {
510 510 this.output_area.clear_output(wait);
511 511 this.set_input_prompt();
512 512 };
513 513
514 514
515 515 // JSON serialization
516 516
517 517 CodeCell.prototype.fromJSON = function (data) {
518 518 IPython.Cell.prototype.fromJSON.apply(this, arguments);
519 519 if (data.cell_type === 'code') {
520 520 if (data.input !== undefined) {
521 521 this.set_text(data.input);
522 522 // make this value the starting point, so that we can only undo
523 523 // to this state, instead of a blank cell
524 524 this.code_mirror.clearHistory();
525 525 this.auto_highlight();
526 526 }
527 527 if (data.prompt_number !== undefined) {
528 528 this.set_input_prompt(data.prompt_number);
529 529 } else {
530 530 this.set_input_prompt();
531 531 }
532 532 this.output_area.trusted = data.trusted || false;
533 533 this.output_area.fromJSON(data.outputs);
534 534 if (data.collapsed !== undefined) {
535 535 if (data.collapsed) {
536 536 this.collapse_output();
537 537 } else {
538 538 this.expand_output();
539 539 }
540 540 }
541 541 }
542 542 };
543 543
544 544
545 545 CodeCell.prototype.toJSON = function () {
546 546 var data = IPython.Cell.prototype.toJSON.apply(this);
547 547 data.input = this.get_text();
548 548 // is finite protect against undefined and '*' value
549 549 if (isFinite(this.input_prompt_number)) {
550 550 data.prompt_number = this.input_prompt_number;
551 551 }
552 552 var outputs = this.output_area.toJSON();
553 553 data.outputs = outputs;
554 554 data.language = 'python';
555 555 data.trusted = this.output_area.trusted;
556 556 data.collapsed = this.collapsed;
557 557 return data;
558 558 };
559 559
560 560 /**
561 561 * handle cell level logic when a cell is unselected
562 562 * @method unselect
563 563 * @return is the action being taken
564 564 */
565 565 CodeCell.prototype.unselect = function () {
566 566 var cont = IPython.Cell.prototype.unselect.apply(this);
567 567 if (cont) {
568 568 // When a code cell is usnelected, make sure that the corresponding
569 // tooltip to that cell is closed.
569 // tooltip and completer to that cell is closed.
570 570 IPython.tooltip.remove_and_cancel_tooltip(true);
571 if (this.completer !== null) {
572 this.completer.close();
573 }
571 574 }
572 575 return cont;
573 576 };
574 577
575 578 IPython.CodeCell = CodeCell;
576 579
577 580 return IPython;
578 581 }(IPython));
@@ -1,373 +1,357
1 1 // function completer.
2 2 //
3 3 // completer should be a class that takes an cell instance
4 4 var IPython = (function (IPython) {
5 5 // that will prevent us from misspelling
6 6 "use strict";
7 7
8 8 // easier key mapping
9 9 var keycodes = IPython.keyboard.keycodes;
10 10
11 11 function prepend_n_prc(str, n) {
12 12 for( var i =0 ; i< n ; i++){
13 13 str = '%'+str ;
14 14 }
15 15 return str;
16 16 }
17 17
18 18 function _existing_completion(item, completion_array){
19 19 for( var c in completion_array ) {
20 20 if(completion_array[c].trim().substr(-item.length) == item)
21 21 { return true; }
22 22 }
23 23 return false;
24 24 }
25 25
26 26 // what is the common start of all completions
27 27 function shared_start(B, drop_prct) {
28 28 if (B.length == 1) {
29 29 return B[0];
30 30 }
31 31 var A = [];
32 32 var common;
33 33 var min_lead_prct = 10;
34 34 for (var i = 0; i < B.length; i++) {
35 35 var str = B[i].str;
36 36 var localmin = 0;
37 37 if(drop_prct === true){
38 38 while ( str.substr(0, 1) == '%') {
39 39 localmin = localmin+1;
40 40 str = str.substring(1);
41 41 }
42 42 }
43 43 min_lead_prct = Math.min(min_lead_prct, localmin);
44 44 A.push(str);
45 45 }
46 46
47 47 if (A.length > 1) {
48 48 var tem1, tem2, s;
49 49 A = A.slice(0).sort();
50 50 tem1 = A[0];
51 51 s = tem1.length;
52 52 tem2 = A.pop();
53 53 while (s && tem2.indexOf(tem1) == -1) {
54 54 tem1 = tem1.substring(0, --s);
55 55 }
56 56 if (tem1 === "" || tem2.indexOf(tem1) !== 0) {
57 57 return {
58 58 str:prepend_n_prc('', min_lead_prct),
59 59 type: "computed",
60 60 from: B[0].from,
61 61 to: B[0].to
62 62 };
63 63 }
64 64 return {
65 65 str: prepend_n_prc(tem1, min_lead_prct),
66 66 type: "computed",
67 67 from: B[0].from,
68 68 to: B[0].to
69 69 };
70 70 }
71 71 return null;
72 72 }
73 73
74 74
75 75 var Completer = function (cell) {
76 76 this.cell = cell;
77 77 this.editor = cell.code_mirror;
78 78 var that = this;
79 79 $([IPython.events]).on('status_busy.Kernel', function () {
80 80 that.skip_kernel_completion = true;
81 81 });
82 82 $([IPython.events]).on('status_idle.Kernel', function () {
83 83 that.skip_kernel_completion = false;
84 84 });
85 85 };
86 86
87 87 Completer.prototype.startCompletion = function () {
88 88 // call for a 'first' completion, that will set the editor and do some
89 // special behaviour like autopicking if only one completion availlable
90 //
89 // special behavior like autopicking if only one completion available.
91 90 if (this.editor.somethingSelected()) return;
92 91 this.done = false;
93 92 // use to get focus back on opera
94 93 this.carry_on_completion(true);
95 94 };
96 95
97 96
98 97 // easy access for julia to monkeypatch
99 98 //
100 99 Completer.reinvoke_re = /[%0-9a-z._/\\:~-]/i;
101 100
102 101 Completer.prototype.reinvoke= function(pre_cursor, block, cursor){
103 102 return Completer.reinvoke_re.test(pre_cursor);
104 103 };
105 104
106 105 /**
107 106 *
108 107 * pass true as parameter if this is the first invocation of the completer
109 108 * this will prevent the completer to dissmiss itself if it is not on a
110 109 * word boundary like pressing tab after a space, and make it autopick the
111 110 * only choice if there is only one which prevent from popping the UI. as
112 111 * well as fast-forwarding the typing if all completion have a common
113 112 * shared start
114 113 **/
115 114 Completer.prototype.carry_on_completion = function (first_invocation) {
116 115 // Pass true as parameter if you want the completer to autopick when
117 116 // only one completion. This function is automatically reinvoked at
118 117 // each keystroke with first_invocation = false
119 118 var cur = this.editor.getCursor();
120 119 var line = this.editor.getLine(cur.line);
121 120 var pre_cursor = this.editor.getRange({
122 121 line: cur.line,
123 122 ch: cur.ch - 1
124 123 }, cur);
125 124
126 125 // we need to check that we are still on a word boundary
127 126 // because while typing the completer is still reinvoking itself
128 127 // so dismiss if we are on a "bad" caracter
129 128 if (!this.reinvoke(pre_cursor) && !first_invocation) {
130 129 this.close();
131 130 return;
132 131 }
133 132
134 133 this.autopick = false;
135 134 if (first_invocation) {
136 135 this.autopick = true;
137 136 }
138 137
139 138 // We want a single cursor position.
140 139 if (this.editor.somethingSelected()) {
141 140 return;
142 141 }
143 142
144 143 // one kernel completion came back, finish_completing will be called with the results
145 144 // we fork here and directly call finish completing if kernel is busy
146 145 if (this.skip_kernel_completion) {
147 146 this.finish_completing({
148 147 'matches': [],
149 148 matched_text: ""
150 149 });
151 150 } else {
152 151 this.cell.kernel.complete(line, cur.ch, $.proxy(this.finish_completing, this));
153 152 }
154 153 };
155 154
156 155 Completer.prototype.finish_completing = function (msg) {
157 156 // let's build a function that wrap all that stuff into what is needed
158 157 // for the new completer:
159 158 var content = msg.content;
160 159 var matched_text = content.matched_text;
161 160 var matches = content.matches;
162 161
163 162 var cur = this.editor.getCursor();
164 163 var results = CodeMirror.contextHint(this.editor);
165 164 var filtered_results = [];
166 165 //remove results from context completion
167 166 //that are already in kernel completion
168 167 for (var elm in results) {
169 168 if (!_existing_completion(results[elm].str, matches)) {
170 169 filtered_results.push(results[elm]);
171 170 }
172 171 }
173 172
174 173 // append the introspection result, in order, at at the beginning of
175 174 // the table and compute the replacement range from current cursor
176 175 // positon and matched_text length.
177 176 for (var i = matches.length - 1; i >= 0; --i) {
178 177 filtered_results.unshift({
179 178 str: matches[i],
180 179 type: "introspection",
181 180 from: {
182 181 line: cur.line,
183 182 ch: cur.ch - matched_text.length
184 183 },
185 184 to: {
186 185 line: cur.line,
187 186 ch: cur.ch
188 187 }
189 188 });
190 189 }
191 190
192 191 // one the 2 sources results have been merge, deal with it
193 192 this.raw_result = filtered_results;
194 193
195 194 // if empty result return
196 195 if (!this.raw_result || !this.raw_result.length) return;
197 196
198 197 // When there is only one completion, use it directly.
199 198 if (this.autopick && this.raw_result.length == 1) {
200 199 this.insert(this.raw_result[0]);
201 200 return;
202 201 }
203 202
204 203 if (this.raw_result.length == 1) {
205 204 // test if first and only completion totally matches
206 205 // what is typed, in this case dismiss
207 206 var str = this.raw_result[0].str;
208 207 var pre_cursor = this.editor.getRange({
209 208 line: cur.line,
210 209 ch: cur.ch - str.length
211 210 }, cur);
212 211 if (pre_cursor == str) {
213 212 this.close();
214 213 return;
215 214 }
216 215 }
217 216
218 this.complete = $('<div/>').addClass('completions');
219 this.complete.attr('id', 'complete');
220
221 // Currently webkit doesn't use the size attr correctly. See:
222 // https://code.google.com/p/chromium/issues/detail?id=4579
223 this.sel = $('<select style="width: auto"/>')
224 .attr('tabindex', -1)
225 .attr('multiple', 'true')
226 .attr('size', Math.min(10, this.raw_result.length));
227 this.complete.append(this.sel);
228 $('body').append(this.complete);
217 if (!this.visible) {
218 console.log('add div');
219 this.complete = $('<div/>').addClass('completions');
220 this.complete.attr('id', 'complete');
221
222 // Currently webkit doesn't use the size attr correctly. See:
223 // https://code.google.com/p/chromium/issues/detail?id=4579
224 this.sel = $('<select style="width: auto"/>')
225 .attr('tabindex', -1)
226 .attr('multiple', 'true');
227 this.complete.append(this.sel);
228 this.visible = true;
229 $('body').append(this.complete);
230
231 //build the container
232 var that = this;
233 this.sel.dblclick(function () {
234 that.pick();
235 });
236 this.editor.on('keydown', function (event) {
237 that.keydown(event);
238 });
239 this.editor.on('keypress', function (event) {
240 that.keypress(event);
241 });
242 }
243 this.sel.attr('size', Math.min(10, this.raw_result.length));
229 244
230 245 // After everything is on the page, compute the postion.
231 246 // We put it above the code if it is too close to the bottom of the page.
232 247 cur.ch = cur.ch-matched_text.length;
233 248 var pos = this.editor.cursorCoords(cur);
234 249 var left = pos.left-3;
235 250 var top;
236 251 var cheight = this.complete.height();
237 252 var wheight = $(window).height();
238 253 if (pos.bottom+cheight+5 > wheight) {
239 254 top = pos.top-cheight-4;
240 255 } else {
241 256 top = pos.bottom+1;
242 257 }
243 258 this.complete.css('left', left + 'px');
244 259 this.complete.css('top', top + 'px');
245 260
246
247 //build the container
248 var that = this;
249 this.sel.dblclick(function () {
250 that.pick();
251 });
252 this.editor.on('keydown', function (event) {
253 that.keydown(event);
254 });
255 this.editor.on('keypress', function (event) {
256 that.keypress(event);
257 });
258
261 // Clear and fill the list.
262 this.sel.text('');
259 263 this.build_gui_list(this.raw_result);
260
261 this.sel.focus();
262 // Opera sometimes ignores focusing a freshly created node
263 if (window.opera) setTimeout(function () {
264 if (!this.done) this.sel.focus();
265 }, 100);
266 264 return true;
267 265 };
268 266
269 267 Completer.prototype.insert = function (completion) {
270 268 this.editor.replaceRange(completion.str, completion.from, completion.to);
271 269 };
272 270
273 271 Completer.prototype.build_gui_list = function (completions) {
274 272 for (var i = 0; i < completions.length; ++i) {
275 273 var opt = $('<option/>').text(completions[i].str).addClass(completions[i].type);
276 274 this.sel.append(opt);
277 275 }
278 276 this.sel.children().first().attr('selected', 'true');
279 277 this.sel.scrollTop(0);
280 278 };
281 279
282 280 Completer.prototype.close = function () {
283 281 this.done = true;
284 282 $('#complete').remove();
283 this.visible = false;
285 284 };
286 285
287 286 Completer.prototype.pick = function () {
288 287 this.insert(this.raw_result[this.sel[0].selectedIndex]);
289 288 this.close();
290 289 var that = this;
291 setTimeout(function () {
292 that.editor.focus();
293 }, 50);
294 290 };
295 291
296 292 Completer.prototype.keydown = function (event) {
297 293 var code = event.keyCode;
298 294 var that = this;
299 295
300 296 // Enter
301 297 if (code == keycodes.enter) {
302 298 CodeMirror.e_stop(event);
303 299 this.pick();
304 300 }
305 301 // Escape or backspace
306 302 else if (code == keycodes.esc) {
307 303 CodeMirror.e_stop(event);
308 304 this.close();
309 this.editor.focus();
310 305
311 306 } else if (code == keycodes.backspace) {
312 307 this.close();
313 this.editor.focus();
314 308 } else if (code == keycodes.tab) {
315 309 //all the fastforwarding operation,
316 310 //Check that shared start is not null which can append with prefixed completion
317 311 // like %pylab , pylab have no shred start, and ff will result in py<tab><tab>
318 312 // to erase py
319 313 var sh = shared_start(this.raw_result, true);
320 314 if (sh) {
321 315 this.insert(sh);
322 316 }
323 317 this.close();
324 318 CodeMirror.e_stop(event);
325 this.editor.focus();
326 319 //reinvoke self
327 320 setTimeout(function () {
328 321 that.carry_on_completion();
329 322 }, 50);
330 323 } else if (code == keycodes.up || code == keycodes.down) {
331 324 // need to do that to be able to move the arrow
332 325 // when on the first or last line ofo a code cell
333 326 event.stopPropagation();
334 327 }
335 328 };
336 329
337 330 Completer.prototype.keypress = function (event) {
338 331 // FIXME: This is a band-aid.
339 332 // on keypress, trigger insertion of a single character.
340 333 // This simulates the old behavior of completion as you type,
341 334 // before events were disconnected and CodeMirror stopped
342 335 // receiving events while the completer is focused.
343
336 if (!this.visible) return;
337
344 338 var that = this;
345 339 var code = event.keyCode;
346 340
347 341 // don't handle keypress if it's not a character (arrows on FF)
348 342 // or ENTER/TAB
349 343 if (event.charCode === 0 ||
350 344 code == keycodes.enter ||
351 345 code == keycodes.tab
352 346 ) return;
353 347
354 var cur = this.editor.getCursor();
355 var completion = {
356 str: String.fromCharCode(event.which),
357 type: "introspection",
358 from: cur,
359 to: cur,
360 };
361 this.insert(completion);
362
363 348 this.close();
364 this.editor.focus();
365 349 setTimeout(function () {
366 350 that.carry_on_completion();
367 351 }, 50);
368 352 };
369 353
370 354 IPython.Completer = Completer;
371 355
372 356 return IPython;
373 357 }(IPython));
General Comments 0
You need to be logged in to leave comments. Login now