##// END OF EJS Templates
Merge pull request #4281 from ellisonbg/complete_above...
Matthias Bussonnier -
r12907:3ad718f0 merge
parent child Browse files
Show More
@@ -1,319 +1,331 b''
1 1 // function completer.
2 2 //
3 3 // completer should be a class that take 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 // easyier key mapping
9 9 var key = IPython.utils.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 return str;
15 15 }
16 16
17 17 function _existing_completion(item, completion_array){
18 18 for( var c in completion_array ) {
19 19 if(completion_array[c].trim().substr(-item.length) == item)
20 20 { return true; }
21 21 }
22 22 return false;
23 23 }
24 24
25 25 // what is the common start of all completions
26 26 function shared_start(B, drop_prct) {
27 27 if (B.length == 1) {
28 28 return B[0];
29 29 }
30 30 var A = new Array();
31 31 var common;
32 32 var min_lead_prct = 10;
33 33 for (var i = 0; i < B.length; i++) {
34 34 var str = B[i].str;
35 35 var localmin = 0;
36 36 if(drop_prct == true){
37 37 while ( str.substr(0, 1) == '%') {
38 38 localmin = localmin+1;
39 39 str = str.substring(1);
40 40 }
41 41 }
42 42 min_lead_prct = Math.min(min_lead_prct, localmin);
43 43 A.push(str);
44 44 }
45 45
46 46 if (A.length > 1) {
47 47 var tem1, tem2, s;
48 48 A = A.slice(0).sort();
49 49 tem1 = A[0];
50 50 s = tem1.length;
51 51 tem2 = A.pop();
52 52 while (s && tem2.indexOf(tem1) == -1) {
53 53 tem1 = tem1.substring(0, --s);
54 54 }
55 55 if (tem1 == "" || tem2.indexOf(tem1) != 0) {
56 56 return {
57 57 str:prepend_n_prc('', min_lead_prct),
58 58 type: "computed",
59 59 from: B[0].from,
60 60 to: B[0].to
61 61 }
62 62 }
63 63 return {
64 64 str: prepend_n_prc(tem1, min_lead_prct),
65 65 type: "computed",
66 66 from: B[0].from,
67 67 to: B[0].to
68 68 };
69 69 }
70 70 return null;
71 71 }
72 72
73 73
74 74 var Completer = function (cell) {
75 75 this.cell = cell;
76 76 this.editor = cell.code_mirror;
77 77 var that = this;
78 78 $([IPython.events]).on('status_busy.Kernel', function () {
79 79 that.skip_kernel_completion = true;
80 80 });
81 81 $([IPython.events]).on('status_idle.Kernel', function () {
82 82 that.skip_kernel_completion = false;
83 83 });
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 89 // special behaviour like autopicking if only one completion availlable
90 90 //
91 91 if (this.editor.somethingSelected()) return;
92 92 this.done = false;
93 93 // use to get focus back on opera
94 94 this.carry_on_completion(true);
95 95 };
96 96
97 97 Completer.prototype.carry_on_completion = function (ff) {
98 98 // Pass true as parameter if you want the commpleter to autopick when
99 99 // only one completion. This function is automatically reinvoked at
100 100 // each keystroke with ff = false
101 101 var cur = this.editor.getCursor();
102 102 var line = this.editor.getLine(cur.line);
103 103 var pre_cursor = this.editor.getRange({
104 104 line: cur.line,
105 105 ch: cur.ch - 1
106 106 }, cur);
107 107
108 108 // we need to check that we are still on a word boundary
109 109 // because while typing the completer is still reinvoking itself
110 110 if (!/[%0-9a-z._/\\:~-]/i.test(pre_cursor)) {
111 111 this.close();
112 112 return;
113 113 }
114 114
115 115 this.autopick = false;
116 116 if (ff != 'undefined' && ff == true) {
117 117 this.autopick = true;
118 118 }
119 119
120 120 // We want a single cursor position.
121 121 if (this.editor.somethingSelected()) return;
122 122
123 123 // one kernel completion came back, finish_completing will be called with the results
124 124 // we fork here and directly call finish completing if kernel is busy
125 125 if (this.skip_kernel_completion == true) {
126 126 this.finish_completing({
127 127 'matches': [],
128 128 matched_text: ""
129 129 })
130 130 } else {
131 131 var callbacks = {
132 132 'complete_reply': $.proxy(this.finish_completing, this)
133 133 };
134 134 this.cell.kernel.complete(line, cur.ch, callbacks);
135 135 }
136 136 };
137 137
138 138 Completer.prototype.finish_completing = function (content) {
139 139 // let's build a function that wrap all that stuff into what is needed
140 140 // for the new completer:
141 141 var matched_text = content.matched_text;
142 142 var matches = content.matches;
143 143
144 144 var cur = this.editor.getCursor();
145 145 var results = CodeMirror.contextHint(this.editor);
146 146 var filterd_results = Array();
147 147 //remove results from context completion
148 148 //that are already in kernel completion
149 149 for(var elm in results) {
150 150 if(_existing_completion(results[elm]['str'], matches) == false)
151 151 { filterd_results.push(results[elm]); }
152 152 }
153 153
154 154 // append the introspection result, in order, at at the beginning of
155 155 // the table and compute the replacement range from current cursor
156 156 // positon and matched_text length.
157 157 for (var i = matches.length - 1; i >= 0; --i) {
158 158 filterd_results.unshift({
159 159 str: matches[i],
160 160 type: "introspection",
161 161 from: {
162 162 line: cur.line,
163 163 ch: cur.ch - matched_text.length
164 164 },
165 165 to: {
166 166 line: cur.line,
167 167 ch: cur.ch
168 168 }
169 169 });
170 170 }
171 171
172 172 // one the 2 sources results have been merge, deal with it
173 173 this.raw_result = filterd_results;
174 174
175 175 // if empty result return
176 176 if (!this.raw_result || !this.raw_result.length) return;
177 177
178 178 // When there is only one completion, use it directly.
179 179 if (this.autopick == true && this.raw_result.length == 1) {
180 180 this.insert(this.raw_result[0]);
181 181 return;
182 182 }
183 183
184 184 if (this.raw_result.length == 1) {
185 185 // test if first and only completion totally matches
186 186 // what is typed, in this case dismiss
187 187 var str = this.raw_result[0].str;
188 188 var pre_cursor = this.editor.getRange({
189 189 line: cur.line,
190 190 ch: cur.ch - str.length
191 191 }, cur);
192 192 if (pre_cursor == str) {
193 193 this.close();
194 194 return;
195 195 }
196 196 }
197 197
198 198 this.complete = $('<div/>').addClass('completions');
199 199 this.complete.attr('id', 'complete');
200 200
201 201 this.sel = $('<select style="width: auto"/>').attr('multiple', 'true').attr('size', Math.min(10, this.raw_result.length));
202 //var pos = this.editor.cursorCoords();
202 this.complete.append(this.sel);
203 $('body').append(this.complete);
204
205 // After everything is on the page, compute the postion.
206 // We put it above the code if it is too close to the bottom of the page.
203 207 var cur = this.editor.getCursor();
204 208 cur.ch = cur.ch-matched_text.length;
205 209 var pos = this.editor.cursorCoords(cur);
206 this.complete.css('left', pos.left-3 + 'px');
207 this.complete.css('top', pos.bottom+1 + 'px');
208 this.complete.append(this.sel);
210 var left = pos.left-3;
211 var top;
212 var cheight = this.complete.height();
213 var wheight = $(window).height();
214 if (pos.bottom+cheight+5 > wheight) {
215 top = pos.top-cheight-4;
216 } else {
217 top = pos.bottom+1;
218 }
219 this.complete.css('left', left + 'px');
220 this.complete.css('top', top + 'px');
221
209 222
210 $('body').append(this.complete);
211 223 //build the container
212 224 var that = this;
213 225 this.sel.dblclick(function () {
214 226 that.pick();
215 227 });
216 228 this.sel.blur(this.close);
217 229 this.sel.keydown(function (event) {
218 230 that.keydown(event);
219 231 });
220 232
221 233 this.build_gui_list(this.raw_result);
222 234
223 235 this.sel.focus();
224 236 // Opera sometimes ignores focusing a freshly created node
225 237 if (window.opera) setTimeout(function () {
226 238 if (!this.done) this.sel.focus();
227 239 }, 100);
228 240 return true;
229 241 }
230 242
231 243 Completer.prototype.insert = function (completion) {
232 244 this.editor.replaceRange(completion.str, completion.from, completion.to);
233 245 }
234 246
235 247 Completer.prototype.build_gui_list = function (completions) {
236 // Need to clear the all list
237 248 for (var i = 0; i < completions.length; ++i) {
238 249 var opt = $('<option/>').text(completions[i].str).addClass(completions[i].type);
239 250 this.sel.append(opt);
240 251 }
241 252 this.sel.children().first().attr('selected', 'true');
253 this.sel.scrollTop(0);
242 254 }
243 255
244 256 Completer.prototype.close = function () {
245 257 if (this.done) return;
246 258 this.done = true;
247 259 $('.completions').remove();
248 260 }
249 261
250 262 Completer.prototype.pick = function () {
251 263 this.insert(this.raw_result[this.sel[0].selectedIndex]);
252 264 this.close();
253 265 var that = this;
254 266 setTimeout(function () {
255 267 that.editor.focus();
256 268 }, 50);
257 269 }
258 270
259 271
260 272 Completer.prototype.keydown = function (event) {
261 273 var code = event.keyCode;
262 274 var that = this;
263 275 var special_key = false;
264 276
265 277 // detect special keys like SHIFT,PGUP,...
266 278 for( var _key in key ) {
267 279 if (code == key[_key] ) {
268 280 special_key = true;
269 281 }
270 282 };
271 283
272 284 // Enter
273 285 if (code == key.ENTER) {
274 286 CodeMirror.e_stop(event);
275 287 this.pick();
276 288 }
277 289 // Escape or backspace
278 290 else if (code == key.ESC) {
279 291 CodeMirror.e_stop(event);
280 292 this.close();
281 293 this.editor.focus();
282 294 } else if (code == key.SPACE || code == key.BACKSPACE) {
283 295 this.close();
284 296 this.editor.focus();
285 297 } else if (code == key.TAB) {
286 298 //all the fastforwarding operation,
287 299 //Check that shared start is not null which can append with prefixed completion
288 300 // like %pylab , pylab have no shred start, and ff will result in py<tab><tab>
289 301 // to erase py
290 302 var sh = shared_start(this.raw_result, true);
291 303 if (sh) {
292 304 this.insert(sh);
293 305 }
294 306 this.close();
295 307 CodeMirror.e_stop(event);
296 308 this.editor.focus();
297 309 //reinvoke self
298 310 setTimeout(function () {
299 311 that.carry_on_completion();
300 312 }, 50);
301 313 } else if (code == key.UPARROW || code == key.DOWNARROW) {
302 314 // need to do that to be able to move the arrow
303 315 // when on the first or last line ofo a code cell
304 316 event.stopPropagation();
305 317 } else if (special_key != true) {
306 318 this.close();
307 319 this.editor.focus();
308 320 //we give focus to the editor immediately and call sell in 50 ms
309 321 setTimeout(function () {
310 322 that.carry_on_completion();
311 323 }, 50);
312 324 }
313 325 }
314 326
315 327
316 328 IPython.Completer = Completer;
317 329
318 330 return IPython;
319 331 }(IPython));
General Comments 0
You need to be logged in to leave comments. Login now