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