##// END OF EJS Templates
Remove commented out lines (for tab evt)
Jonathan Frederic -
Show More
@@ -1,380 +1,378 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.cell = cell;
76 this.cell = cell;
77 this.editor = cell.code_mirror;
77 this.editor = cell.code_mirror;
78 var that = this;
78 var that = this;
79 $([IPython.events]).on('status_busy.Kernel', function () {
79 $([IPython.events]).on('status_busy.Kernel', function () {
80 that.skip_kernel_completion = true;
80 that.skip_kernel_completion = true;
81 });
81 });
82 $([IPython.events]).on('status_idle.Kernel', function () {
82 $([IPython.events]).on('status_idle.Kernel', function () {
83 that.skip_kernel_completion = false;
83 that.skip_kernel_completion = false;
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 behavior like autopicking if only one completion available.
89 // special behavior like autopicking if only one completion available.
90 if (this.editor.somethingSelected()) return;
90 if (this.editor.somethingSelected()) return;
91 this.done = false;
91 this.done = false;
92 // use to get focus back on opera
92 // use to get focus back on opera
93 this.carry_on_completion(true);
93 this.carry_on_completion(true);
94 };
94 };
95
95
96
96
97 // easy access for julia to monkeypatch
97 // easy access for julia to monkeypatch
98 //
98 //
99 Completer.reinvoke_re = /[%0-9a-z._/\\:~-]/i;
99 Completer.reinvoke_re = /[%0-9a-z._/\\:~-]/i;
100
100
101 Completer.prototype.reinvoke= function(pre_cursor, block, cursor){
101 Completer.prototype.reinvoke= function(pre_cursor, block, cursor){
102 return Completer.reinvoke_re.test(pre_cursor);
102 return Completer.reinvoke_re.test(pre_cursor);
103 };
103 };
104
104
105 /**
105 /**
106 *
106 *
107 * pass true as parameter if this is the first invocation of the completer
107 * pass true as parameter if this is the first invocation of the completer
108 * this will prevent the completer to dissmiss itself if it is not on a
108 * this will prevent the completer to dissmiss itself if it is not on a
109 * word boundary like pressing tab after a space, and make it autopick the
109 * word boundary like pressing tab after a space, and make it autopick the
110 * only choice if there is only one which prevent from popping the UI. as
110 * only choice if there is only one which prevent from popping the UI. as
111 * well as fast-forwarding the typing if all completion have a common
111 * well as fast-forwarding the typing if all completion have a common
112 * shared start
112 * shared start
113 **/
113 **/
114 Completer.prototype.carry_on_completion = function (first_invocation) {
114 Completer.prototype.carry_on_completion = function (first_invocation) {
115 // Pass true as parameter if you want the completer to autopick when
115 // Pass true as parameter if you want the completer to autopick when
116 // only one completion. This function is automatically reinvoked at
116 // only one completion. This function is automatically reinvoked at
117 // each keystroke with first_invocation = false
117 // each keystroke with first_invocation = false
118 var cur = this.editor.getCursor();
118 var cur = this.editor.getCursor();
119 var line = this.editor.getLine(cur.line);
119 var line = this.editor.getLine(cur.line);
120 var pre_cursor = this.editor.getRange({
120 var pre_cursor = this.editor.getRange({
121 line: cur.line,
121 line: cur.line,
122 ch: cur.ch - 1
122 ch: cur.ch - 1
123 }, cur);
123 }, cur);
124
124
125 // we need to check that we are still on a word boundary
125 // we need to check that we are still on a word boundary
126 // because while typing the completer is still reinvoking itself
126 // because while typing the completer is still reinvoking itself
127 // so dismiss if we are on a "bad" caracter
127 // so dismiss if we are on a "bad" caracter
128 if (!this.reinvoke(pre_cursor) && !first_invocation) {
128 if (!this.reinvoke(pre_cursor) && !first_invocation) {
129 this.close();
129 this.close();
130 return;
130 return;
131 }
131 }
132
132
133 this.autopick = false;
133 this.autopick = false;
134 if (first_invocation) {
134 if (first_invocation) {
135 this.autopick = true;
135 this.autopick = true;
136 }
136 }
137
137
138 // We want a single cursor position.
138 // We want a single cursor position.
139 if (this.editor.somethingSelected()) {
139 if (this.editor.somethingSelected()) {
140 return;
140 return;
141 }
141 }
142
142
143 // one kernel completion came back, finish_completing will be called with the results
143 // one kernel completion came back, finish_completing will be called with the results
144 // we fork here and directly call finish completing if kernel is busy
144 // we fork here and directly call finish completing if kernel is busy
145 if (this.skip_kernel_completion) {
145 if (this.skip_kernel_completion) {
146 this.finish_completing({
146 this.finish_completing({
147 'matches': [],
147 'matches': [],
148 matched_text: ""
148 matched_text: ""
149 });
149 });
150 } else {
150 } else {
151 this.cell.kernel.complete(line, cur.ch, $.proxy(this.finish_completing, this));
151 this.cell.kernel.complete(line, cur.ch, $.proxy(this.finish_completing, this));
152 }
152 }
153 };
153 };
154
154
155 Completer.prototype.finish_completing = function (msg) {
155 Completer.prototype.finish_completing = function (msg) {
156 // let's build a function that wrap all that stuff into what is needed
156 // let's build a function that wrap all that stuff into what is needed
157 // for the new completer:
157 // for the new completer:
158 var content = msg.content;
158 var content = msg.content;
159 var matched_text = content.matched_text;
159 var matched_text = content.matched_text;
160 var matches = content.matches;
160 var matches = content.matches;
161
161
162 var cur = this.editor.getCursor();
162 var cur = this.editor.getCursor();
163 var results = CodeMirror.contextHint(this.editor);
163 var results = CodeMirror.contextHint(this.editor);
164 var filtered_results = [];
164 var filtered_results = [];
165 //remove results from context completion
165 //remove results from context completion
166 //that are already in kernel completion
166 //that are already in kernel completion
167 for (var elm in results) {
167 for (var elm in results) {
168 if (!_existing_completion(results[elm].str, matches)) {
168 if (!_existing_completion(results[elm].str, matches)) {
169 filtered_results.push(results[elm]);
169 filtered_results.push(results[elm]);
170 }
170 }
171 }
171 }
172
172
173 // append the introspection result, in order, at at the beginning of
173 // append the introspection result, in order, at at the beginning of
174 // the table and compute the replacement range from current cursor
174 // the table and compute the replacement range from current cursor
175 // positon and matched_text length.
175 // positon and matched_text length.
176 for (var i = matches.length - 1; i >= 0; --i) {
176 for (var i = matches.length - 1; i >= 0; --i) {
177 filtered_results.unshift({
177 filtered_results.unshift({
178 str: matches[i],
178 str: matches[i],
179 type: "introspection",
179 type: "introspection",
180 from: {
180 from: {
181 line: cur.line,
181 line: cur.line,
182 ch: cur.ch - matched_text.length
182 ch: cur.ch - matched_text.length
183 },
183 },
184 to: {
184 to: {
185 line: cur.line,
185 line: cur.line,
186 ch: cur.ch
186 ch: cur.ch
187 }
187 }
188 });
188 });
189 }
189 }
190
190
191 // one the 2 sources results have been merge, deal with it
191 // one the 2 sources results have been merge, deal with it
192 this.raw_result = filtered_results;
192 this.raw_result = filtered_results;
193
193
194 // if empty result return
194 // if empty result return
195 if (!this.raw_result || !this.raw_result.length) return;
195 if (!this.raw_result || !this.raw_result.length) return;
196
196
197 // When there is only one completion, use it directly.
197 // When there is only one completion, use it directly.
198 if (this.autopick && this.raw_result.length == 1) {
198 if (this.autopick && this.raw_result.length == 1) {
199 this.insert(this.raw_result[0]);
199 this.insert(this.raw_result[0]);
200 return;
200 return;
201 }
201 }
202
202
203 if (this.raw_result.length == 1) {
203 if (this.raw_result.length == 1) {
204 // test if first and only completion totally matches
204 // test if first and only completion totally matches
205 // what is typed, in this case dismiss
205 // what is typed, in this case dismiss
206 var str = this.raw_result[0].str;
206 var str = this.raw_result[0].str;
207 var pre_cursor = this.editor.getRange({
207 var pre_cursor = this.editor.getRange({
208 line: cur.line,
208 line: cur.line,
209 ch: cur.ch - str.length
209 ch: cur.ch - str.length
210 }, cur);
210 }, cur);
211 if (pre_cursor == str) {
211 if (pre_cursor == str) {
212 this.close();
212 this.close();
213 return;
213 return;
214 }
214 }
215 }
215 }
216
216
217 if (!this.visible) {
217 if (!this.visible) {
218 console.log('add div');
218 console.log('add div');
219 this.complete = $('<div/>').addClass('completions');
219 this.complete = $('<div/>').addClass('completions');
220 this.complete.attr('id', 'complete');
220 this.complete.attr('id', 'complete');
221
221
222 // Currently webkit doesn't use the size attr correctly. See:
222 // Currently webkit doesn't use the size attr correctly. See:
223 // https://code.google.com/p/chromium/issues/detail?id=4579
223 // https://code.google.com/p/chromium/issues/detail?id=4579
224 this.sel = $('<select/>')
224 this.sel = $('<select/>')
225 .attr('tabindex', -1)
225 .attr('tabindex', -1)
226 .attr('multiple', 'true');
226 .attr('multiple', 'true');
227 this.complete.append(this.sel);
227 this.complete.append(this.sel);
228 this.visible = true;
228 this.visible = true;
229 $('body').append(this.complete);
229 $('body').append(this.complete);
230
230
231 //build the container
231 //build the container
232 var that = this;
232 var that = this;
233 this.sel.dblclick(function () {
233 this.sel.dblclick(function () {
234 that.pick();
234 that.pick();
235 });
235 });
236 this.sel.focus(function () {
236 this.sel.focus(function () {
237 that.editor.focus();
237 that.editor.focus();
238 });
238 });
239 this._handle_keydown = function (cm, event) {
239 this._handle_keydown = function (cm, event) {
240 that.keydown(event);
240 that.keydown(event);
241 };
241 };
242 this.editor.on('keydown', this._handle_keydown);
242 this.editor.on('keydown', this._handle_keydown);
243 this._handle_keypress = function (cm, event) {
243 this._handle_keypress = function (cm, event) {
244 that.keypress(event);
244 that.keypress(event);
245 };
245 };
246 this.editor.on('keypress', this._handle_keypress);
246 this.editor.on('keypress', this._handle_keypress);
247 }
247 }
248 this.sel.attr('size', Math.min(10, this.raw_result.length));
248 this.sel.attr('size', Math.min(10, this.raw_result.length));
249
249
250 // After everything is on the page, compute the postion.
250 // After everything is on the page, compute the postion.
251 // We put it above the code if it is too close to the bottom of the page.
251 // We put it above the code if it is too close to the bottom of the page.
252 cur.ch = cur.ch-matched_text.length;
252 cur.ch = cur.ch-matched_text.length;
253 var pos = this.editor.cursorCoords(cur);
253 var pos = this.editor.cursorCoords(cur);
254 var left = pos.left-3;
254 var left = pos.left-3;
255 var top;
255 var top;
256 var cheight = this.complete.height();
256 var cheight = this.complete.height();
257 var wheight = $(window).height();
257 var wheight = $(window).height();
258 if (pos.bottom+cheight+5 > wheight) {
258 if (pos.bottom+cheight+5 > wheight) {
259 top = pos.top-cheight-4;
259 top = pos.top-cheight-4;
260 } else {
260 } else {
261 top = pos.bottom+1;
261 top = pos.bottom+1;
262 }
262 }
263 this.complete.css('left', left + 'px');
263 this.complete.css('left', left + 'px');
264 this.complete.css('top', top + 'px');
264 this.complete.css('top', top + 'px');
265
265
266 // Clear and fill the list.
266 // Clear and fill the list.
267 this.sel.text('');
267 this.sel.text('');
268 this.build_gui_list(this.raw_result);
268 this.build_gui_list(this.raw_result);
269 return true;
269 return true;
270 };
270 };
271
271
272 Completer.prototype.insert = function (completion) {
272 Completer.prototype.insert = function (completion) {
273 this.editor.replaceRange(completion.str, completion.from, completion.to);
273 this.editor.replaceRange(completion.str, completion.from, completion.to);
274 };
274 };
275
275
276 Completer.prototype.build_gui_list = function (completions) {
276 Completer.prototype.build_gui_list = function (completions) {
277 for (var i = 0; i < completions.length; ++i) {
277 for (var i = 0; i < completions.length; ++i) {
278 var opt = $('<option/>').text(completions[i].str).addClass(completions[i].type);
278 var opt = $('<option/>').text(completions[i].str).addClass(completions[i].type);
279 this.sel.append(opt);
279 this.sel.append(opt);
280 }
280 }
281 this.sel.children().first().attr('selected', 'true');
281 this.sel.children().first().attr('selected', 'true');
282 this.sel.scrollTop(0);
282 this.sel.scrollTop(0);
283 };
283 };
284
284
285 Completer.prototype.close = function () {
285 Completer.prototype.close = function () {
286 this.done = true;
286 this.done = true;
287 $('#complete').remove();
287 $('#complete').remove();
288 this.editor.off('keydown', this._handle_keydown);
288 this.editor.off('keydown', this._handle_keydown);
289 this.editor.off('keypress', this._handle_keypress);
289 this.editor.off('keypress', this._handle_keypress);
290 this.visible = false;
290 this.visible = false;
291 };
291 };
292
292
293 Completer.prototype.pick = function () {
293 Completer.prototype.pick = function () {
294 this.insert(this.raw_result[this.sel[0].selectedIndex]);
294 this.insert(this.raw_result[this.sel[0].selectedIndex]);
295 this.close();
295 this.close();
296 };
296 };
297
297
298 Completer.prototype.keydown = function (event) {
298 Completer.prototype.keydown = function (event) {
299 var code = event.keyCode;
299 var code = event.keyCode;
300 var that = this;
300 var that = this;
301
301
302 // Enter
302 // Enter
303 if (code == keycodes.enter) {
303 if (code == keycodes.enter) {
304 CodeMirror.e_stop(event);
304 CodeMirror.e_stop(event);
305 this.pick();
305 this.pick();
306 // Escape or backspace
306 // Escape or backspace
307 } else if (code == keycodes.esc || code == keycodes.backspace) {
307 } else if (code == keycodes.esc || code == keycodes.backspace) {
308 CodeMirror.e_stop(event);
308 CodeMirror.e_stop(event);
309 this.close();
309 this.close();
310 } else if (code == keycodes.tab) {
310 } else if (code == keycodes.tab) {
311 //all the fastforwarding operation,
311 //all the fastforwarding operation,
312 //Check that shared start is not null which can append with prefixed completion
312 //Check that shared start is not null which can append with prefixed completion
313 // like %pylab , pylab have no shred start, and ff will result in py<tab><tab>
313 // like %pylab , pylab have no shred start, and ff will result in py<tab><tab>
314 // to erase py
314 // to erase py
315 var sh = shared_start(this.raw_result, true);
315 var sh = shared_start(this.raw_result, true);
316 if (sh) {
316 if (sh) {
317 this.insert(sh);
317 this.insert(sh);
318 }
318 }
319 this.close();
319 this.close();
320 // event.codemirrorIgnore = true;
321 // event.stopPropagation();
322 //reinvoke self
320 //reinvoke self
323 setTimeout(function () {
321 setTimeout(function () {
324 that.carry_on_completion();
322 that.carry_on_completion();
325 }, 50);
323 }, 50);
326 } else if (code == keycodes.up || code == keycodes.down) {
324 } else if (code == keycodes.up || code == keycodes.down) {
327 // need to do that to be able to move the arrow
325 // need to do that to be able to move the arrow
328 // when on the first or last line ofo a code cell
326 // when on the first or last line ofo a code cell
329 CodeMirror.e_stop(event);
327 CodeMirror.e_stop(event);
330
328
331 var options = this.sel.find('option');
329 var options = this.sel.find('option');
332 var index = this.sel[0].selectedIndex;
330 var index = this.sel[0].selectedIndex;
333 if (code == keycodes.up) {
331 if (code == keycodes.up) {
334 index--;
332 index--;
335 }
333 }
336 if (code == keycodes.down) {
334 if (code == keycodes.down) {
337 index++;
335 index++;
338 }
336 }
339 index = Math.min(Math.max(index, 0), options.length-1);
337 index = Math.min(Math.max(index, 0), options.length-1);
340 this.sel[0].selectedIndex = index;
338 this.sel[0].selectedIndex = index;
341 } else if (code == keycodes.left || code == keycodes.right) {
339 } else if (code == keycodes.left || code == keycodes.right) {
342 this.close();
340 this.close();
343 }
341 }
344 };
342 };
345
343
346 Completer.prototype.keypress = function (event) {
344 Completer.prototype.keypress = function (event) {
347 // FIXME: This is a band-aid.
345 // FIXME: This is a band-aid.
348 // on keypress, trigger insertion of a single character.
346 // on keypress, trigger insertion of a single character.
349 // This simulates the old behavior of completion as you type,
347 // This simulates the old behavior of completion as you type,
350 // before events were disconnected and CodeMirror stopped
348 // before events were disconnected and CodeMirror stopped
351 // receiving events while the completer is focused.
349 // receiving events while the completer is focused.
352
350
353 var that = this;
351 var that = this;
354 var code = event.keyCode;
352 var code = event.keyCode;
355
353
356 // don't handle keypress if it's not a character (arrows on FF)
354 // don't handle keypress if it's not a character (arrows on FF)
357 // or ENTER/TAB
355 // or ENTER/TAB
358 if (event.charCode === 0 ||
356 if (event.charCode === 0 ||
359 code == keycodes.tab ||
357 code == keycodes.tab ||
360 code == keycodes.enter
358 code == keycodes.enter
361 ) return;
359 ) return;
362
360
363 var cur = this.editor.getCursor();
361 var cur = this.editor.getCursor();
364 var completion = {
362 var completion = {
365 str: String.fromCharCode(event.which),
363 str: String.fromCharCode(event.which),
366 type: "introspection",
364 type: "introspection",
367 from: cur,
365 from: cur,
368 to: cur,
366 to: cur,
369 };
367 };
370
368
371 this.close();
369 this.close();
372 this.editor.focus();
370 this.editor.focus();
373 setTimeout(function () {
371 setTimeout(function () {
374 that.carry_on_completion();
372 that.carry_on_completion();
375 }, 50);
373 }, 50);
376 };
374 };
377 IPython.Completer = Completer;
375 IPython.Completer = Completer;
378
376
379 return IPython;
377 return IPython;
380 }(IPython));
378 }(IPython));
General Comments 0
You need to be logged in to leave comments. Login now