completer.js
246 lines
| 8.5 KiB
| application/javascript
|
JavascriptLexer
|
r7131 | // function completer. | ||
// | ||||
|
r7142 | // completer should be a class that take an cell instance | ||
|
r7131 | // | ||
var IPython = (function(IPython ) { | ||||
|
r7142 | // that will prevent us from misspelling | ||
|
r7131 | "use strict"; | ||
// easyier key mapping | ||||
|
r7137 | var key = IPython.utils.keycodes; | ||
|
r7142 | |||
|
r7131 | // what is the common start of all completions | ||
function sharedStart(B){ | ||||
if(B.length == 1){return B[0]} | ||||
var A = new Array() | ||||
|
r7132 | for(var i=0; i< B.length; i++) | ||
|
r7131 | { | ||
|
r7142 | A.push(B[i].str); | ||
|
r7131 | } | ||
if(A.length > 1 ){ | ||||
var tem1, tem2, s, A = A.slice(0).sort(); | ||||
tem1 = A[0]; | ||||
s = tem1.length; | ||||
tem2 = A.pop(); | ||||
while(s && tem2.indexOf(tem1) == -1){ | ||||
tem1 = tem1.substring(0, --s); | ||||
} | ||||
|
r7135 | if (tem1 == "" || tem2.indexOf(tem1) != 0){return null;} | ||
|
r7131 | return { str : tem1, | ||
type : "computed", | ||||
from : B[0].from, | ||||
to : B[0].to | ||||
}; | ||||
} | ||||
return null; | ||||
} | ||||
|
r7141 | |||
var Completer = function(cell) { | ||||
this.cell = cell; | ||||
this.editor = cell.code_mirror; | ||||
|
r7131 | // if last caractere before cursor is not in this, we stop completing | ||
this.reg = /[A-Za-z.]/; | ||||
} | ||||
|
r7141 | Completer.prototype.kernelCompletionRequest = function(){ | ||
var cur = this.editor.getCursor(); | ||||
// Autocomplete the current line. | ||||
var line = this.editor.getLine(cur.line); | ||||
// one could fork here and directly call finish completing | ||||
// if kernel is busy | ||||
IPython.notebook.complete_cell(this.cell, line, cur.ch); | ||||
} | ||||
|
r7132 | Completer.prototype.startCompletion = function() | ||
|
r7131 | { | ||
|
r7142 | // call for a 'first' completion, that will set the editor and do some | ||
|
r7131 | // special behaviour like autopicking if only one completion availlable | ||
// | ||||
if (this.editor.somethingSelected()) return; | ||||
this.done = false; | ||||
// use to get focus back on opera | ||||
this.carryOnCompletion(true); | ||||
} | ||||
Completer.prototype.carryOnCompletion = function(ff) | ||||
{ | ||||
|
r7142 | // pass true as parameter if you want the commpleter to autopick | ||
|
r7131 | // when only one completion | ||
|
r7142 | // as this function is automatically reinvoked at each keystroke with | ||
|
r7131 | // ff = false | ||
var cur = this.editor.getCursor(); | ||||
var pre_cursor = this.editor.getRange({line:cur.line,ch:cur.ch-1},cur); | ||||
|
r7142 | // we need to check that we are still on a word boundary | ||
|
r7131 | // because while typing the completer is still reinvoking itself | ||
if(!this.reg.test(pre_cursor)){ this.close(); return;} | ||||
|
r7142 | |||
|
r7131 | this.autopick = false; | ||
if( ff != 'undefined' && ff==true) | ||||
{ | ||||
this.autopick=true; | ||||
} | ||||
// We want a single cursor position. | ||||
if (this.editor.somethingSelected()) return; | ||||
|
r7142 | //one kernel completion came back, finish_completing will be called with the results | ||
|
r7141 | this.kernelCompletionRequest(); | ||
|
r7131 | } | ||
|
r7142 | |||
Completer.prototype.finish_completing =function (matched_text, matches) { | ||||
// let's build a function that wrap all that stuff into what is needed for the | ||||
// new completer: | ||||
// | ||||
var cur = this.editor.getCursor(); | ||||
var results = CodeMirror.contextHint(this.editor); | ||||
// append the introspection result, in order, at | ||||
// at the beginning of the table and compute the replacement rance | ||||
// from current cursor positon and matched_text length. | ||||
for(var i= matches.length-1; i>=0 ;--i) | ||||
{ | ||||
results.unshift( | ||||
{ | ||||
str : matches[i], | ||||
type : "introspection", | ||||
from : {line: cur.line, ch: cur.ch-matched_text.length}, | ||||
to : {line: cur.line, ch: cur.ch} | ||||
} | ||||
) | ||||
} | ||||
// one the 2 sources results have been merge, deal with it | ||||
|
r7131 | this.raw_result = results; | ||
// if empty result return | ||||
if (!this.raw_result || !this.raw_result.length) return; | ||||
// When there is only one completion, use it directly. | ||||
if (this.autopick == true && this.raw_result.length == 1) | ||||
{ | ||||
this.insert(this.raw_result[0]); | ||||
return true; | ||||
} | ||||
if (this.raw_result.length == 1) | ||||
{ | ||||
|
r7142 | // test if first and only completion totally matches | ||
|
r7131 | // what is typed, in this case dismiss | ||
var str = this.raw_result[0].str | ||||
var cur = this.editor.getCursor(); | ||||
var pre_cursor = this.editor.getRange({line:cur.line,ch:cur.ch-str.length},cur); | ||||
if(pre_cursor == str){ | ||||
this.close(); | ||||
return ; | ||||
} | ||||
} | ||||
this.complete = $('<div/>').addClass('completions'); | ||||
this.complete.attr('id','complete'); | ||||
|
r7132 | this.sel = $('<select/>') | ||
.attr('multiple','true') | ||||
.attr('size',Math.min(10,this.raw_result.length)); | ||||
|
r7131 | var pos = this.editor.cursorCoords(); | ||
// TODO: I propose to remove enough horizontal pixel | ||||
// to align the text later | ||||
this.complete.css('left',pos.x+'px'); | ||||
this.complete.css('top',pos.yBot+'px'); | ||||
this.complete.append(this.sel); | ||||
$('body').append(this.complete); | ||||
//build the container | ||||
var that = this; | ||||
this.sel.dblclick(function(){that.pick()}); | ||||
this.sel.blur(this.close); | ||||
this.sel.keydown(function(event){that.keydown(event)}); | ||||
this.build_gui_list(this.raw_result); | ||||
//CodeMirror.connect(that.sel, "dblclick", function(){that.pick()}); | ||||
this.sel.focus(); | ||||
// Opera sometimes ignores focusing a freshly created node | ||||
if (window.opera) setTimeout(function(){if (!this.done) this.sel.focus();}, 100); | ||||
// why do we return true ? | ||||
return true; | ||||
} | ||||
Completer.prototype.insert = function(completion) { | ||||
this.editor.replaceRange(completion.str, completion.from, completion.to); | ||||
} | ||||
Completer.prototype.build_gui_list = function(completions){ | ||||
// Need to clear the all list | ||||
for (var i = 0; i < completions.length; ++i) { | ||||
var opt = $('<option/>') | ||||
.text(completions[i].str) | ||||
.addClass(completions[i].type); | ||||
this.sel.append(opt); | ||||
} | ||||
this.sel.children().first().attr('selected','true'); | ||||
|
r7142 | |||
|
r7131 | //sel.size = Math.min(10, completions.length); | ||
// Hack to hide the scrollbar. | ||||
//if (completions.length <= 10) | ||||
//this.complete.style.width = (this.sel.clientWidth - 1) + "px"; | ||||
} | ||||
Completer.prototype.close = function() { | ||||
if (this.done) return; | ||||
this.done = true; | ||||
$('.completions').remove(); | ||||
} | ||||
Completer.prototype.pick = function(){ | ||||
this.insert(this.raw_result[this.sel[0].selectedIndex]); | ||||
this.close(); | ||||
var that = this; | ||||
setTimeout(function(){that.editor.focus();}, 50); | ||||
} | ||||
|
r7142 | |||
|
r7131 | Completer.prototype.keydown = function(event) { | ||
var code = event.keyCode; | ||||
// Enter | ||||
if (code == key.enter) {CodeMirror.e_stop(event); this.pick();} | ||||
// Escape or backspace | ||||
else if (code == key.esc ) {CodeMirror.e_stop(event); this.close(); this.editor.focus();} | ||||
else if (code == key.space || code == key.backspace) {this.close(); this.editor.focus();} | ||||
else if (code == key.tab){ | ||||
|
r7142 | //all the fastforwarding operation, | ||
|
r7132 | //Check that shared start is not null which can append with prefixed completion | ||
// like %pylab , pylab have no shred start, and ff will result in py<tab><tab> | ||||
// to erase py | ||||
|
r7135 | var sh = sharedStart(this.raw_result); | ||
|
r7132 | if(sh){ | ||
this.insert(sh); | ||||
} | ||||
|
r7142 | this.close(); | ||
|
r7131 | CodeMirror.e_stop(event); | ||
this.editor.focus(); | ||||
//reinvoke self | ||||
var that = this; | ||||
setTimeout(function(){that.carryOnCompletion();}, 50); | ||||
} | ||||
else if (code == key.upArrow || code == key.downArrow) { | ||||
|
r7142 | // need to do that to be able to move the arrow | ||
|
r7131 | // when on the first or last line ofo a code cell | ||
event.stopPropagation(); | ||||
} | ||||
else if (code != key.upArrow && code != key.downArrow) { | ||||
this.close(); this.editor.focus(); | ||||
//we give focus to the editor immediately and call sell in 50 ms | ||||
var that = this; | ||||
setTimeout(function(){that.carryOnCompletion();}, 50); | ||||
} | ||||
} | ||||
IPython.Completer = Completer; | ||||
return IPython; | ||||
}(IPython)); | ||||