##// END OF EJS Templates
Addressing things in completer.js.
Brian E. Granger -
Show More
@@ -1,392 +1,384 b''
1 // function completer.
1 // function completer.
2 //
2 //
3 // completer should be a class that takes an cell instance
3 // completer should be a class that takes 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 // easier key mapping
8 // easier key mapping
9 var keycodes = IPython.keyboard.keycodes;
9 var keycodes = IPython.keyboard.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 }
14 }
15 return str;
15 return str;
16 }
16 }
17
17
18 function _existing_completion(item, completion_array){
18 function _existing_completion(item, completion_array){
19 for( var c in completion_array ) {
19 for( var c in completion_array ) {
20 if(completion_array[c].trim().substr(-item.length) == item)
20 if(completion_array[c].trim().substr(-item.length) == item)
21 { return true; }
21 { return true; }
22 }
22 }
23 return false;
23 return false;
24 }
24 }
25
25
26 // what is the common start of all completions
26 // what is the common start of all completions
27 function shared_start(B, drop_prct) {
27 function shared_start(B, drop_prct) {
28 if (B.length == 1) {
28 if (B.length == 1) {
29 return B[0];
29 return B[0];
30 }
30 }
31 var A = [];
31 var A = [];
32 var common;
32 var common;
33 var min_lead_prct = 10;
33 var min_lead_prct = 10;
34 for (var i = 0; i < B.length; i++) {
34 for (var i = 0; i < B.length; i++) {
35 var str = B[i].str;
35 var str = B[i].str;
36 var localmin = 0;
36 var localmin = 0;
37 if(drop_prct === true){
37 if(drop_prct === true){
38 while ( str.substr(0, 1) == '%') {
38 while ( str.substr(0, 1) == '%') {
39 localmin = localmin+1;
39 localmin = localmin+1;
40 str = str.substring(1);
40 str = str.substring(1);
41 }
41 }
42 }
42 }
43 min_lead_prct = Math.min(min_lead_prct, localmin);
43 min_lead_prct = Math.min(min_lead_prct, localmin);
44 A.push(str);
44 A.push(str);
45 }
45 }
46
46
47 if (A.length > 1) {
47 if (A.length > 1) {
48 var tem1, tem2, s;
48 var tem1, tem2, s;
49 A = A.slice(0).sort();
49 A = A.slice(0).sort();
50 tem1 = A[0];
50 tem1 = A[0];
51 s = tem1.length;
51 s = tem1.length;
52 tem2 = A.pop();
52 tem2 = A.pop();
53 while (s && tem2.indexOf(tem1) == -1) {
53 while (s && tem2.indexOf(tem1) == -1) {
54 tem1 = tem1.substring(0, --s);
54 tem1 = tem1.substring(0, --s);
55 }
55 }
56 if (tem1 === "" || tem2.indexOf(tem1) !== 0) {
56 if (tem1 === "" || tem2.indexOf(tem1) !== 0) {
57 return {
57 return {
58 str:prepend_n_prc('', min_lead_prct),
58 str:prepend_n_prc('', min_lead_prct),
59 type: "computed",
59 type: "computed",
60 from: B[0].from,
60 from: B[0].from,
61 to: B[0].to
61 to: B[0].to
62 };
62 };
63 }
63 }
64 return {
64 return {
65 str: prepend_n_prc(tem1, min_lead_prct),
65 str: prepend_n_prc(tem1, min_lead_prct),
66 type: "computed",
66 type: "computed",
67 from: B[0].from,
67 from: B[0].from,
68 to: B[0].to
68 to: B[0].to
69 };
69 };
70 }
70 }
71 return null;
71 return null;
72 }
72 }
73
73
74
74
75 var Completer = function (cell) {
75 var Completer = function (cell) {
76 this._visible = false;
76 this._visible = false;
77 this.cell = cell;
77 this.cell = cell;
78 this.editor = cell.code_mirror;
78 this.editor = cell.code_mirror;
79 var that = this;
79 var that = this;
80 $([IPython.events]).on('status_busy.Kernel', function () {
80 $([IPython.events]).on('status_busy.Kernel', function () {
81 that.skip_kernel_completion = true;
81 that.skip_kernel_completion = true;
82 });
82 });
83 $([IPython.events]).on('status_idle.Kernel', function () {
83 $([IPython.events]).on('status_idle.Kernel', function () {
84 that.skip_kernel_completion = false;
84 that.skip_kernel_completion = false;
85 });
85 });
86 };
86 };
87
87
88 Completer.prototype.is_visible = function () {
88 Completer.prototype.is_visible = function () {
89 // Return whether or not the completer is visible.
89 // Return whether or not the completer is visible.
90 return this._visible;
90 return this._visible;
91 };
91 };
92
92
93 Completer.prototype.startCompletion = function () {
93 Completer.prototype.startCompletion = function () {
94 // call for a 'first' completion, that will set the editor and do some
94 // call for a 'first' completion, that will set the editor and do some
95 // special behaviour like autopicking if only one completion availlable
95 // special behaviour like autopicking if only one completion availlable
96 //
96 //
97 if (this.editor.somethingSelected()) return;
97 if (this.editor.somethingSelected()) return;
98 this.done = false;
98 this.done = false;
99 // use to get focus back on opera
99 // use to get focus back on opera
100 this.carry_on_completion(true);
100 this.carry_on_completion(true);
101 };
101 };
102
102
103
103
104 // easy access for julia to monkeypatch
104 // easy access for julia to monkeypatch
105 //
105 //
106 Completer.reinvoke_re = /[%0-9a-z._/\\:~-]/i;
106 Completer.reinvoke_re = /[%0-9a-z._/\\:~-]/i;
107
107
108 Completer.prototype.reinvoke= function(pre_cursor, block, cursor){
108 Completer.prototype.reinvoke= function(pre_cursor, block, cursor){
109 return Completer.reinvoke_re.test(pre_cursor);
109 return Completer.reinvoke_re.test(pre_cursor);
110 };
110 };
111
111
112 /**
112 /**
113 *
113 *
114 * pass true as parameter if this is the first invocation of the completer
114 * pass true as parameter if this is the first invocation of the completer
115 * this will prevent the completer to dissmiss itself if it is not on a
115 * this will prevent the completer to dissmiss itself if it is not on a
116 * word boundary like pressing tab after a space, and make it autopick the
116 * word boundary like pressing tab after a space, and make it autopick the
117 * only choice if there is only one which prevent from popping the UI. as
117 * only choice if there is only one which prevent from popping the UI. as
118 * well as fast-forwarding the typing if all completion have a common
118 * well as fast-forwarding the typing if all completion have a common
119 * shared start
119 * shared start
120 **/
120 **/
121 Completer.prototype.carry_on_completion = function (first_invocation) {
121 Completer.prototype.carry_on_completion = function (first_invocation) {
122 // Pass true as parameter if you want the completer to autopick when
122 // Pass true as parameter if you want the completer to autopick when
123 // only one completion. This function is automatically reinvoked at
123 // only one completion. This function is automatically reinvoked at
124 // each keystroke with first_invocation = false
124 // each keystroke with first_invocation = false
125 var cur = this.editor.getCursor();
125 var cur = this.editor.getCursor();
126 var line = this.editor.getLine(cur.line);
126 var line = this.editor.getLine(cur.line);
127 var pre_cursor = this.editor.getRange({
127 var pre_cursor = this.editor.getRange({
128 line: cur.line,
128 line: cur.line,
129 ch: cur.ch - 1
129 ch: cur.ch - 1
130 }, cur);
130 }, cur);
131
131
132 // we need to check that we are still on a word boundary
132 // we need to check that we are still on a word boundary
133 // because while typing the completer is still reinvoking itself
133 // because while typing the completer is still reinvoking itself
134 // so dismiss if we are on a "bad" caracter
134 // so dismiss if we are on a "bad" caracter
135 if (!this.reinvoke(pre_cursor) && !first_invocation) {
135 if (!this.reinvoke(pre_cursor) && !first_invocation) {
136 this.close();
136 this.close();
137 return;
137 return;
138 }
138 }
139
139
140 this.autopick = false;
140 this.autopick = false;
141 if (first_invocation) {
141 if (first_invocation) {
142 this.autopick = true;
142 this.autopick = true;
143 }
143 }
144
144
145 // We want a single cursor position.
145 // We want a single cursor position.
146 if (this.editor.somethingSelected()) {
146 if (this.editor.somethingSelected()) {
147 return;
147 return;
148 }
148 }
149
149
150 // one kernel completion came back, finish_completing will be called with the results
150 // one kernel completion came back, finish_completing will be called with the results
151 // we fork here and directly call finish completing if kernel is busy
151 // we fork here and directly call finish completing if kernel is busy
152 if (this.skip_kernel_completion) {
152 if (this.skip_kernel_completion) {
153 this.finish_completing({
153 this.finish_completing({
154 'matches': [],
154 'matches': [],
155 matched_text: ""
155 matched_text: ""
156 });
156 });
157 } else {
157 } else {
158 this.cell.kernel.complete(line, cur.ch, $.proxy(this.finish_completing, this));
158 this.cell.kernel.complete(line, cur.ch, $.proxy(this.finish_completing, this));
159 }
159 }
160 };
160 };
161
161
162 Completer.prototype.finish_completing = function (msg) {
162 Completer.prototype.finish_completing = function (msg) {
163 // let's build a function that wrap all that stuff into what is needed
163 // let's build a function that wrap all that stuff into what is needed
164 // for the new completer:
164 // for the new completer:
165 var content = msg.content;
165 var content = msg.content;
166 var matched_text = content.matched_text;
166 var matched_text = content.matched_text;
167 var matches = content.matches;
167 var matches = content.matches;
168
168
169 var cur = this.editor.getCursor();
169 var cur = this.editor.getCursor();
170 var results = CodeMirror.contextHint(this.editor);
170 var results = CodeMirror.contextHint(this.editor);
171 var filtered_results = [];
171 var filtered_results = [];
172 //remove results from context completion
172 //remove results from context completion
173 //that are already in kernel completion
173 //that are already in kernel completion
174 for (var elm in results) {
174 for (var elm in results) {
175 if (!_existing_completion(results[elm].str, matches)) {
175 if (!_existing_completion(results[elm].str, matches)) {
176 filtered_results.push(results[elm]);
176 filtered_results.push(results[elm]);
177 }
177 }
178 }
178 }
179
179
180 // append the introspection result, in order, at at the beginning of
180 // append the introspection result, in order, at at the beginning of
181 // the table and compute the replacement range from current cursor
181 // the table and compute the replacement range from current cursor
182 // positon and matched_text length.
182 // positon and matched_text length.
183 for (var i = matches.length - 1; i >= 0; --i) {
183 for (var i = matches.length - 1; i >= 0; --i) {
184 filtered_results.unshift({
184 filtered_results.unshift({
185 str: matches[i],
185 str: matches[i],
186 type: "introspection",
186 type: "introspection",
187 from: {
187 from: {
188 line: cur.line,
188 line: cur.line,
189 ch: cur.ch - matched_text.length
189 ch: cur.ch - matched_text.length
190 },
190 },
191 to: {
191 to: {
192 line: cur.line,
192 line: cur.line,
193 ch: cur.ch
193 ch: cur.ch
194 }
194 }
195 });
195 });
196 }
196 }
197
197
198 // one the 2 sources results have been merge, deal with it
198 // one the 2 sources results have been merge, deal with it
199 this.raw_result = filtered_results;
199 this.raw_result = filtered_results;
200
200
201 // if empty result return
201 // if empty result return
202 if (!this.raw_result || !this.raw_result.length) return;
202 if (!this.raw_result || !this.raw_result.length) return;
203
203
204 // When there is only one completion, use it directly.
204 // When there is only one completion, use it directly.
205 if (this.autopick && this.raw_result.length == 1) {
205 if (this.autopick && this.raw_result.length == 1) {
206 this.insert(this.raw_result[0]);
206 this.insert(this.raw_result[0]);
207 return;
207 return;
208 }
208 }
209
209
210 if (this.raw_result.length == 1) {
210 if (this.raw_result.length == 1) {
211 // test if first and only completion totally matches
211 // test if first and only completion totally matches
212 // what is typed, in this case dismiss
212 // what is typed, in this case dismiss
213 var str = this.raw_result[0].str;
213 var str = this.raw_result[0].str;
214 var pre_cursor = this.editor.getRange({
214 var pre_cursor = this.editor.getRange({
215 line: cur.line,
215 line: cur.line,
216 ch: cur.ch - str.length
216 ch: cur.ch - str.length
217 }, cur);
217 }, cur);
218 if (pre_cursor == str) {
218 if (pre_cursor == str) {
219 this.close();
219 this.close();
220 return;
220 return;
221 }
221 }
222 }
222 }
223
223
224 this.complete = $('<div/>').addClass('completions');
224 this.complete = $('<div/>').addClass('completions');
225 this.complete.attr('id', 'complete');
225 this.complete.attr('id', 'complete');
226
226
227 // Currently webkit doesn't use the size attr correctly. See:
227 // Currently webkit doesn't use the size attr correctly. See:
228 // https://code.google.com/p/chromium/issues/detail?id=4579
228 // https://code.google.com/p/chromium/issues/detail?id=4579
229 this.sel = $('<select style="width: auto"/>')
229 this.sel = $('<select style="width: auto"/>')
230 .attr('multiple', 'true')
230 .attr('multiple', 'true')
231 .attr('size', Math.min(10, this.raw_result.length));
231 .attr('size', Math.min(10, this.raw_result.length));
232 this.complete.append(this.sel);
232 this.complete.append(this.sel);
233 this._visible = true;
233 this._visible = true;
234 $('body').append(this.complete);
234 $('body').append(this.complete);
235
235
236 // After everything is on the page, compute the postion.
236 // After everything is on the page, compute the postion.
237 // We put it above the code if it is too close to the bottom of the page.
237 // We put it above the code if it is too close to the bottom of the page.
238 cur.ch = cur.ch-matched_text.length;
238 cur.ch = cur.ch-matched_text.length;
239 var pos = this.editor.cursorCoords(cur);
239 var pos = this.editor.cursorCoords(cur);
240 var left = pos.left-3;
240 var left = pos.left-3;
241 var top;
241 var top;
242 var cheight = this.complete.height();
242 var cheight = this.complete.height();
243 var wheight = $(window).height();
243 var wheight = $(window).height();
244 if (pos.bottom+cheight+5 > wheight) {
244 if (pos.bottom+cheight+5 > wheight) {
245 top = pos.top-cheight-4;
245 top = pos.top-cheight-4;
246 } else {
246 } else {
247 top = pos.bottom+1;
247 top = pos.bottom+1;
248 }
248 }
249 this.complete.css('left', left + 'px');
249 this.complete.css('left', left + 'px');
250 this.complete.css('top', top + 'px');
250 this.complete.css('top', top + 'px');
251
251
252
252
253 //build the container
253 //build the container
254 var that = this;
254 var that = this;
255 this.sel.dblclick(function () {
255 this.sel.dblclick(function () {
256 that.pick();
256 that.pick();
257 });
257 });
258 this.sel.blur(this.close);
258 this.sel.blur(this.close);
259 this.sel.keydown(function (event) {
259 this.sel.keydown(function (event) {
260 that.keydown(event);
260 that.keydown(event);
261 });
261 });
262 this.sel.keypress(function (event) {
262 this.sel.keypress(function (event) {
263 that.keypress(event);
263 that.keypress(event);
264 });
264 });
265
265
266 this.build_gui_list(this.raw_result);
266 this.build_gui_list(this.raw_result);
267
267
268 this.sel.focus();
268 this.sel.focus();
269 IPython.keyboard_manager.disable();
269 IPython.keyboard_manager.disable();
270 // Opera sometimes ignores focusing a freshly created node
270 // Opera sometimes ignores focusing a freshly created node
271 if (window.opera) setTimeout(function () {
271 if (window.opera) setTimeout(function () {
272 if (!this.done) this.sel.focus();
272 if (!this.done) this.sel.focus();
273 }, 100);
273 }, 100);
274 return true;
274 return true;
275 };
275 };
276
276
277 Completer.prototype.insert = function (completion) {
277 Completer.prototype.insert = function (completion) {
278 this.editor.replaceRange(completion.str, completion.from, completion.to);
278 this.editor.replaceRange(completion.str, completion.from, completion.to);
279 };
279 };
280
280
281 Completer.prototype.build_gui_list = function (completions) {
281 Completer.prototype.build_gui_list = function (completions) {
282 for (var i = 0; i < completions.length; ++i) {
282 for (var i = 0; i < completions.length; ++i) {
283 var opt = $('<option/>').text(completions[i].str).addClass(completions[i].type);
283 var opt = $('<option/>').text(completions[i].str).addClass(completions[i].type);
284 this.sel.append(opt);
284 this.sel.append(opt);
285 }
285 }
286 this.sel.children().first().attr('selected', 'true');
286 this.sel.children().first().attr('selected', 'true');
287 this.sel.scrollTop(0);
287 this.sel.scrollTop(0);
288 };
288 };
289
289
290 Completer.prototype.close = function () {
290 Completer.prototype.close = function () {
291 this._visible = false;
291 this._visible = false;
292 if (this.done) return;
292 if (this.done) return;
293 this.done = true;
293 this.done = true;
294 $('.completions').remove();
294 $('.completions').remove();
295 IPython.keyboard_manager.enable();
295 IPython.keyboard_manager.enable();
296 };
296 };
297
297
298 Completer.prototype.pick = function () {
298 Completer.prototype.pick = function () {
299 this.insert(this.raw_result[this.sel[0].selectedIndex]);
299 this.insert(this.raw_result[this.sel[0].selectedIndex]);
300 this.close();
300 this.close();
301 var that = this;
301 var that = this;
302 setTimeout(function () {
302 setTimeout(function () {
303 that.editor.focus();
303 that.editor.focus();
304 }, 50);
304 }, 50);
305 };
305 };
306
306
307 Completer.prototype.keydown = function (event) {
307 Completer.prototype.keydown = function (event) {
308 var code = event.keyCode;
308 var code = event.keyCode;
309 var that = this;
309 var that = this;
310 var special_key = false;
311
312 // detect special keys like SHIFT,PGUP,...
313 for( var _key in key ) {
314 if (code == key[_key] ) {
315 special_key = true;
316 }
317 }
318
310
319 // Enter
311 // Enter
320 if (code == keycodes.enter) {
312 if (code == keycodes.enter) {
321 CodeMirror.e_stop(event);
313 CodeMirror.e_stop(event);
322 this.pick();
314 this.pick();
323 }
315 }
324 // Escape or backspace
316 // Escape or backspace
325 else if (code == keycodes.esc) {
317 else if (code == keycodes.esc) {
326 CodeMirror.e_stop(event);
318 CodeMirror.e_stop(event);
327 this.close();
319 this.close();
328 this.editor.focus();
320 this.editor.focus();
329
321
330 } else if (code == keycodes.backspace) {
322 } else if (code == keycodes.backspace) {
331 this.close();
323 this.close();
332 this.editor.focus();
324 this.editor.focus();
333 } else if (code == keycodes.tab) {
325 } else if (code == keycodes.tab) {
334 //all the fastforwarding operation,
326 //all the fastforwarding operation,
335 //Check that shared start is not null which can append with prefixed completion
327 //Check that shared start is not null which can append with prefixed completion
336 // like %pylab , pylab have no shred start, and ff will result in py<tab><tab>
328 // like %pylab , pylab have no shred start, and ff will result in py<tab><tab>
337 // to erase py
329 // to erase py
338 var sh = shared_start(this.raw_result, true);
330 var sh = shared_start(this.raw_result, true);
339 if (sh) {
331 if (sh) {
340 this.insert(sh);
332 this.insert(sh);
341 }
333 }
342 this.close();
334 this.close();
343 CodeMirror.e_stop(event);
335 CodeMirror.e_stop(event);
344 this.editor.focus();
336 this.editor.focus();
345 //reinvoke self
337 //reinvoke self
346 setTimeout(function () {
338 setTimeout(function () {
347 that.carry_on_completion();
339 that.carry_on_completion();
348 }, 50);
340 }, 50);
349 } else if (code == keycodes.up || code == keycodes.down) {
341 } else if (code == keycodes.up || code == keycodes.down) {
350 // need to do that to be able to move the arrow
342 // need to do that to be able to move the arrow
351 // when on the first or last line ofo a code cell
343 // when on the first or last line ofo a code cell
352 event.stopPropagation();
344 event.stopPropagation();
353 }
345 }
354 };
346 };
355
347
356 Completer.prototype.keypress = function (event) {
348 Completer.prototype.keypress = function (event) {
357 // FIXME: This is a band-aid.
349 // FIXME: This is a band-aid.
358 // on keypress, trigger insertion of a single character.
350 // on keypress, trigger insertion of a single character.
359 // This simulates the old behavior of completion as you type,
351 // This simulates the old behavior of completion as you type,
360 // before events were disconnected and CodeMirror stopped
352 // before events were disconnected and CodeMirror stopped
361 // receiving events while the completer is focused.
353 // receiving events while the completer is focused.
362
354
363 var that = this;
355 var that = this;
364 var code = event.keyCode;
356 var code = event.keyCode;
365
357
366 // don't handle keypress if it's not a character (arrows on FF)
358 // don't handle keypress if it's not a character (arrows on FF)
367 // or ENTER/TAB
359 // or ENTER/TAB
368 if (event.charCode === 0 ||
360 if (event.charCode === 0 ||
369 code == key.ENTER ||
361 code == keycodes.enter ||
370 code == key.TAB
362 code == keycodes.tab
371 ) return;
363 ) return;
372
364
373 var cur = this.editor.getCursor();
365 var cur = this.editor.getCursor();
374 var completion = {
366 var completion = {
375 str: String.fromCharCode(event.which),
367 str: String.fromCharCode(event.which),
376 type: "introspection",
368 type: "introspection",
377 from: cur,
369 from: cur,
378 to: cur,
370 to: cur,
379 };
371 };
380 this.insert(completion);
372 this.insert(completion);
381
373
382 this.close();
374 this.close();
383 this.editor.focus();
375 this.editor.focus();
384 setTimeout(function () {
376 setTimeout(function () {
385 that.carry_on_completion();
377 that.carry_on_completion();
386 }, 50);
378 }, 50);
387 };
379 };
388
380
389 IPython.Completer = Completer;
381 IPython.Completer = Completer;
390
382
391 return IPython;
383 return IPython;
392 }(IPython));
384 }(IPython));
General Comments 0
You need to be logged in to leave comments. Login now