Show More
@@ -344,11 +344,52 b' class Completion:' | |||||
344 | def __hash__(self): |
|
344 | def __hash__(self): | |
345 | return hash((self.start, self.end, self.text)) |
|
345 | return hash((self.start, self.end, self.text)) | |
346 |
|
346 | |||
|
347 | ||||
347 | _IC = Iterator[Completion] |
|
348 | _IC = Iterator[Completion] | |
348 |
|
349 | |||
349 | def rectify_completions(text:str, completion:_IC, *, _debug=False)->_IC: |
|
350 | ||
|
351 | def _deduplicate_completions(text: str, completions: _IC)-> _IC: | |||
350 | """ |
|
352 | """ | |
351 | Rectify a set of completion to all have the same ``start`` and ``end`` |
|
353 | Deduplicate a set of completions. | |
|
354 | ||||
|
355 | .. warning:: Unstable | |||
|
356 | ||||
|
357 | This function is unstable, API may change without warning. | |||
|
358 | ||||
|
359 | Parameters | |||
|
360 | ---------- | |||
|
361 | text: str | |||
|
362 | text that should be completed. | |||
|
363 | completions: Iterator[Completion] | |||
|
364 | iterator over the completions to deduplicate | |||
|
365 | ||||
|
366 | ||||
|
367 | Completions coming from multiple sources, may be different but end up having | |||
|
368 | the same effect when applied to ``text``. If this is the case, this will | |||
|
369 | consider completions as equal and only emit the first encountered. | |||
|
370 | ||||
|
371 | Not folded in `completions()` yet for debugging purpose, and to detect when | |||
|
372 | the IPython completer does return things that Jedi does not, but should be | |||
|
373 | at some point. | |||
|
374 | """ | |||
|
375 | completions = list(completions) | |||
|
376 | if not completions: | |||
|
377 | return | |||
|
378 | ||||
|
379 | new_start = min(c.start for c in completions) | |||
|
380 | new_end = max(c.end for c in completions) | |||
|
381 | ||||
|
382 | seen = set() | |||
|
383 | for c in completions: | |||
|
384 | new_text = text[new_start:c.start] + c.text + text[c.end:new_end] | |||
|
385 | if new_text not in seen: | |||
|
386 | yield c | |||
|
387 | seen.add(new_text) | |||
|
388 | ||||
|
389 | ||||
|
390 | def rectify_completions(text: str, completions: _IC, *, _debug=False)->_IC: | |||
|
391 | """ | |||
|
392 | Rectify a set of completions to all have the same ``start`` and ``end`` | |||
352 |
|
393 | |||
353 | .. warning:: Unstable |
|
394 | .. warning:: Unstable | |
354 |
|
395 | |||
@@ -359,7 +400,7 b' def rectify_completions(text:str, completion:_IC, *, _debug=False)->_IC:' | |||||
359 | ---------- |
|
400 | ---------- | |
360 | text: str |
|
401 | text: str | |
361 | text that should be completed. |
|
402 | text that should be completed. | |
362 | completion: Iterator[Completion] |
|
403 | completions: Iterator[Completion] | |
363 | iterator over the completions to rectify |
|
404 | iterator over the completions to rectify | |
364 |
|
405 | |||
365 |
|
406 | |||
@@ -1510,22 +1551,17 b' class IPCompleter(Completer):' | |||||
1510 | fake Completion token to distinguish completion returned by Jedi |
|
1551 | fake Completion token to distinguish completion returned by Jedi | |
1511 | and usual IPython completion. |
|
1552 | and usual IPython completion. | |
1512 |
|
1553 | |||
|
1554 | .. note:: | |||
|
1555 | ||||
|
1556 | Completions are not completely deduplicated yet. If identical | |||
|
1557 | completions are coming from different sources this function does not | |||
|
1558 | ensure that each completion object will only be present once. | |||
1513 | """ |
|
1559 | """ | |
1514 | warnings.warn("_complete is a provisional API (as of IPython 6.0). " |
|
1560 | warnings.warn("_complete is a provisional API (as of IPython 6.0). " | |
1515 | "It may change without warnings. " |
|
1561 | "It may change without warnings. " | |
1516 | "Use in corresponding context manager.", |
|
1562 | "Use in corresponding context manager.", | |
1517 | category=ProvisionalCompleterWarning, stacklevel=2) |
|
1563 | category=ProvisionalCompleterWarning, stacklevel=2) | |
1518 |
|
1564 | |||
1519 | # Possible Improvements / Known limitation |
|
|||
1520 | ########################################## |
|
|||
1521 | # Completions may be identical even if they have different ranges and |
|
|||
1522 | # text. For example: |
|
|||
1523 | # >>> a=1 |
|
|||
1524 | # >>> a.<tab> |
|
|||
1525 | # May returns: |
|
|||
1526 | # - `a.real` from 0 to 2 |
|
|||
1527 | # - `.real` from 1 to 2 |
|
|||
1528 | # the current code does not (yet) check for such equivalence |
|
|||
1529 | seen = set() |
|
1565 | seen = set() | |
1530 | for c in self._completions(text, offset, _timeout=self.jedi_compute_type_timeout/1000): |
|
1566 | for c in self._completions(text, offset, _timeout=self.jedi_compute_type_timeout/1000): | |
1531 | if c and (c in seen): |
|
1567 | if c and (c in seen): |
@@ -6,6 +6,7 b'' | |||||
6 |
|
6 | |||
7 | import os |
|
7 | import os | |
8 | import sys |
|
8 | import sys | |
|
9 | import textwrap | |||
9 | import unittest |
|
10 | import unittest | |
10 |
|
11 | |||
11 | from contextlib import contextmanager |
|
12 | from contextlib import contextmanager | |
@@ -20,7 +21,8 b' from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory' | |||||
20 | from IPython.utils.generics import complete_object |
|
21 | from IPython.utils.generics import complete_object | |
21 | from IPython.testing import decorators as dec |
|
22 | from IPython.testing import decorators as dec | |
22 |
|
23 | |||
23 | from IPython.core.completer import Completion, provisionalcompleter, match_dict_keys |
|
24 | from IPython.core.completer import ( | |
|
25 | Completion, provisionalcompleter, match_dict_keys, _deduplicate_completions) | |||
24 | from nose.tools import assert_in, assert_not_in |
|
26 | from nose.tools import assert_in, assert_not_in | |
25 |
|
27 | |||
26 | #----------------------------------------------------------------------------- |
|
28 | #----------------------------------------------------------------------------- | |
@@ -294,7 +296,7 b' def test_jedi():' | |||||
294 |
|
296 | |||
295 | import jedi |
|
297 | import jedi | |
296 | jedi_version = tuple(int(i) for i in jedi.__version__.split('.')[:3]) |
|
298 | jedi_version = tuple(int(i) for i in jedi.__version__.split('.')[:3]) | |
297 | if jedi_version > (0,10): |
|
299 | if jedi_version > (0, 10): | |
298 | yield _test_complete, 'jedi >0.9 should complete and not crash', 'a=1;a.', 'real' |
|
300 | yield _test_complete, 'jedi >0.9 should complete and not crash', 'a=1;a.', 'real' | |
299 | yield _test_complete, 'can infer first argument', 'a=(1,"foo");a[0].', 'real' |
|
301 | yield _test_complete, 'can infer first argument', 'a=(1,"foo");a[0].', 'real' | |
300 | yield _test_complete, 'can infer second argument', 'a=(1,"foo");a[1].', 'capitalize' |
|
302 | yield _test_complete, 'can infer second argument', 'a=(1,"foo");a[1].', 'capitalize' | |
@@ -302,6 +304,21 b' def test_jedi():' | |||||
302 |
|
304 | |||
303 | yield _test_not_complete, 'does not mix types', 'a=(1,"foo");a[0].', 'capitalize' |
|
305 | yield _test_not_complete, 'does not mix types', 'a=(1,"foo");a[0].', 'capitalize' | |
304 |
|
306 | |||
|
307 | def test_deduplicate_completions(): | |||
|
308 | """ | |||
|
309 | Test that completions are correctly deduplicated (even if ranges are not the same) | |||
|
310 | """ | |||
|
311 | ip = get_ipython() | |||
|
312 | ip.ex(textwrap.dedent(''' | |||
|
313 | class Z: | |||
|
314 | zoo = 1 | |||
|
315 | ''')) | |||
|
316 | with provisionalcompleter(): | |||
|
317 | l = list(_deduplicate_completions('Z.z', ip.Completer.completions('Z.z', 3))) | |||
|
318 | ||||
|
319 | assert len(l) == 1, 'Completions (Z.z<tab>) correctly deduplicate: %s ' % l | |||
|
320 | assert l[0].text == 'zoo' # and not `it.accumulate` | |||
|
321 | ||||
305 |
|
322 | |||
306 | def test_greedy_completions(): |
|
323 | def test_greedy_completions(): | |
307 | """ |
|
324 | """ |
@@ -10,7 +10,9 b' not to be used outside IPython.' | |||||
10 | import unicodedata |
|
10 | import unicodedata | |
11 | from wcwidth import wcwidth |
|
11 | from wcwidth import wcwidth | |
12 |
|
12 | |||
13 | from IPython.core.completer import IPCompleter, provisionalcompleter, rectify_completions, cursor_to_position |
|
13 | from IPython.core.completer import ( | |
|
14 | IPCompleter, provisionalcompleter, rectify_completions, cursor_to_position, | |||
|
15 | _deduplicate_completions) | |||
14 | from prompt_toolkit.completion import Completer, Completion |
|
16 | from prompt_toolkit.completion import Completer, Completion | |
15 | from prompt_toolkit.layout.lexers import Lexer |
|
17 | from prompt_toolkit.layout.lexers import Lexer | |
16 | from prompt_toolkit.layout.lexers import PygmentsLexer |
|
18 | from prompt_toolkit.layout.lexers import PygmentsLexer | |
@@ -61,8 +63,8 b' class IPythonPTCompleter(Completer):' | |||||
61 | Private equivalent of get_completions() use only for unit_testing. |
|
63 | Private equivalent of get_completions() use only for unit_testing. | |
62 | """ |
|
64 | """ | |
63 | debug = getattr(ipyc, 'debug', False) |
|
65 | debug = getattr(ipyc, 'debug', False) | |
64 |
completions = |
|
66 | completions = _deduplicate_completions( | |
65 |
body, ipyc.completions(body, offset) |
|
67 | body, ipyc.completions(body, offset)) | |
66 | for c in completions: |
|
68 | for c in completions: | |
67 | if not c.text: |
|
69 | if not c.text: | |
68 | # Guard against completion machinery giving us an empty string. |
|
70 | # Guard against completion machinery giving us an empty string. |
General Comments 0
You need to be logged in to leave comments.
Login now