##// END OF EJS Templates
allow number in tokens
Matthias BUSSONNIER -
Show More
@@ -1,230 +1,230 b''
1 1 // function completer.
2 2 //
3 3 // completer should be a class that take an cell instance
4 4
5 5 var IPython = (function(IPython ) {
6 6 // that will prevent us from misspelling
7 7 "use strict";
8 8
9 9 // easyier key mapping
10 10 var key = IPython.utils.keycodes;
11 11
12 12 // what is the common start of all completions
13 13 function sharedStart(B){
14 14 if(B.length == 1){return B[0]}
15 15 var A = new Array()
16 16 for(var i=0; i< B.length; i++)
17 17 {
18 18 A.push(B[i].str);
19 19 }
20 20 if(A.length > 1 ){
21 21 var tem1, tem2, s, A = A.slice(0).sort();
22 22 tem1 = A[0];
23 23 s = tem1.length;
24 24 tem2 = A.pop();
25 25 while(s && tem2.indexOf(tem1) == -1){
26 26 tem1 = tem1.substring(0, --s);
27 27 }
28 28 if (tem1 == "" || tem2.indexOf(tem1) != 0){return null;}
29 29 return { str : tem1,
30 30 type : "computed",
31 31 from : B[0].from,
32 32 to : B[0].to
33 33 };
34 34 }
35 35 return null;
36 36 }
37 37
38 38
39 39 var Completer = function(cell) {
40 40 this.cell = cell;
41 41 this.editor = cell.code_mirror;
42 42 // if last caractere before cursor is not in this, we stop completing
43 this.reg = /[A-Za-z.]/;
43 this.reg = /[0-9a-z.]/i; // casse insensitive
44 44 }
45 45
46 46 Completer.prototype.kernelCompletionRequest = function(){
47 47 var cur = this.editor.getCursor();
48 48 var line = this.editor.getLine(cur.line);
49 49 // one could fork here and directly call finish completing if kernel is busy
50 50 IPython.notebook.complete_cell(this.cell, line, cur.ch);
51 51 }
52 52
53 53
54 54 Completer.prototype.startCompletion = function()
55 55 {
56 56 // call for a 'first' completion, that will set the editor and do some
57 57 // special behaviour like autopicking if only one completion availlable
58 58 //
59 59 if (this.editor.somethingSelected()) return;
60 60 this.done = false;
61 61 // use to get focus back on opera
62 62 this.carryOnCompletion(true);
63 63 }
64 64
65 65 Completer.prototype.carryOnCompletion = function(ff) {
66 66 // Pass true as parameter if you want the commpleter to autopick when
67 67 // only one completion. This function is automatically reinvoked at
68 68 // each keystroke with ff = false
69 69
70 70 var cur = this.editor.getCursor();
71 71 var pre_cursor = this.editor.getRange({line:cur.line,ch:cur.ch-1},cur);
72 72
73 73 // we need to check that we are still on a word boundary
74 74 // because while typing the completer is still reinvoking itself
75 75 if(!this.reg.test(pre_cursor)){ this.close(); return;}
76 76
77 77 this.autopick = false;
78 78 if( ff != 'undefined' && ff==true)
79 79 { this.autopick=true; }
80 80
81 81 // We want a single cursor position.
82 82 if (this.editor.somethingSelected()) return;
83 83
84 84 //one kernel completion came back, finish_completing will be called with the results
85 85 this.kernelCompletionRequest();
86 86 }
87 87
88 88 Completer.prototype.finish_completing =function (matched_text, matches) {
89 89 // let's build a function that wrap all that stuff into what is needed
90 90 // for the new completer:
91 91
92 92 var cur = this.editor.getCursor();
93 93 var results = CodeMirror.contextHint(this.editor);
94 94
95 95 // append the introspection result, in order, at at the beginning of
96 96 // the table and compute the replacement range from current cursor
97 97 // positon and matched_text length.
98 98 for(var i= matches.length-1; i>=0 ;--i)
99 99 {
100 100 results.unshift(
101 101 { str : matches[i],
102 102 type : "introspection",
103 103 from : {line: cur.line, ch: cur.ch-matched_text.length},
104 104 to : {line: cur.line, ch: cur.ch}
105 105 })
106 106 }
107 107
108 108 // one the 2 sources results have been merge, deal with it
109 109 this.raw_result = results;
110 110
111 111 // if empty result return
112 112 if (!this.raw_result || !this.raw_result.length) return;
113 113
114 114 // When there is only one completion, use it directly.
115 115 if (this.autopick == true && this.raw_result.length == 1)
116 116 {
117 117 this.insert(this.raw_result[0]);
118 118 return true;
119 119 }
120 120
121 121 if (this.raw_result.length == 1)
122 122 {
123 123 // test if first and only completion totally matches
124 124 // what is typed, in this case dismiss
125 125 var str = this.raw_result[0].str
126 126 var cur = this.editor.getCursor();
127 127 var pre_cursor = this.editor.getRange({line:cur.line,ch:cur.ch-str.length},cur);
128 128 if(pre_cursor == str)
129 129 { this.close(); return ; }
130 130 }
131 131
132 132 this.complete = $('<div/>').addClass('completions');
133 133 this.complete.attr('id','complete');
134 134
135 135 this.sel = $('<select/>')
136 136 .attr('multiple','true')
137 137 .attr('size',Math.min(10,this.raw_result.length));
138 138 var pos = this.editor.cursorCoords();
139 139
140 140 // TODO: I propose to remove enough horizontal pixel
141 141 // to align the text later
142 142 this.complete.css('left',pos.x+'px');
143 143 this.complete.css('top',pos.yBot+'px');
144 144 this.complete.append(this.sel);
145 145
146 146 $('body').append(this.complete);
147 147 //build the container
148 148 var that = this;
149 149 this.sel.dblclick(function(){that.pick()});
150 150 this.sel.blur(this.close);
151 151 this.sel.keydown(function(event){that.keydown(event)});
152 152
153 153 this.build_gui_list(this.raw_result);
154 154
155 155 this.sel.focus();
156 156 // Opera sometimes ignores focusing a freshly created node
157 157 if (window.opera) setTimeout(function(){if (!this.done) this.sel.focus();}, 100);
158 158 return true;
159 159 }
160 160
161 161 Completer.prototype.insert = function(completion) {
162 162 this.editor.replaceRange(completion.str, completion.from, completion.to);
163 163 }
164 164
165 165 Completer.prototype.build_gui_list = function(completions){
166 166 // Need to clear the all list
167 167 for (var i = 0; i < completions.length; ++i) {
168 168 var opt = $('<option/>')
169 169 .text(completions[i].str)
170 170 .addClass(completions[i].type);
171 171 this.sel.append(opt);
172 172 }
173 173 this.sel.children().first().attr('selected','true');
174 174 }
175 175
176 176 Completer.prototype.close = function() {
177 177 if (this.done) return;
178 178 this.done = true;
179 179 $('.completions').remove();
180 180 }
181 181
182 182 Completer.prototype.pick = function(){
183 183 this.insert(this.raw_result[this.sel[0].selectedIndex]);
184 184 this.close();
185 185 var that = this;
186 186 setTimeout(function(){that.editor.focus();}, 50);
187 187 }
188 188
189 189
190 190 Completer.prototype.keydown = function(event) {
191 191 var code = event.keyCode;
192 192 // Enter
193 193 if (code == key.enter) {CodeMirror.e_stop(event); this.pick();}
194 194 // Escape or backspace
195 195 else if (code == key.esc ) {CodeMirror.e_stop(event); this.close(); this.editor.focus();}
196 196 else if (code == key.space || code == key.backspace) {this.close(); this.editor.focus();}
197 197 else if (code == key.tab){
198 198 //all the fastforwarding operation,
199 199 //Check that shared start is not null which can append with prefixed completion
200 200 // like %pylab , pylab have no shred start, and ff will result in py<tab><tab>
201 201 // to erase py
202 202 var sh = sharedStart(this.raw_result);
203 203 if(sh){
204 204 this.insert(sh);
205 205 }
206 206 this.close();
207 207 CodeMirror.e_stop(event);
208 208 this.editor.focus();
209 209 //reinvoke self
210 210 var that = this;
211 211 setTimeout(function(){that.carryOnCompletion();}, 50);
212 212 }
213 213 else if (code == key.upArrow || code == key.downArrow) {
214 214 // need to do that to be able to move the arrow
215 215 // when on the first or last line ofo a code cell
216 216 event.stopPropagation();
217 217 }
218 218 else if (code != key.upArrow && code != key.downArrow) {
219 219 this.close(); this.editor.focus();
220 220 //we give focus to the editor immediately and call sell in 50 ms
221 221 var that = this;
222 222 setTimeout(function(){that.carryOnCompletion();}, 50);
223 223 }
224 224 }
225 225
226 226
227 227 IPython.Completer = Completer;
228 228
229 229 return IPython;
230 230 }(IPython));
General Comments 0
You need to be logged in to leave comments. Login now