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