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