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