Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit cd7ef69

Browse files
committed
Deduplicate completions between IPython and Jedi.
Completions coming from different sources may be in practice identical, but the object themselves differ. Provide a function to properly deduplicate them. Not perfect for #10282 in particular with trailing spaces, but can be dealt with later.
1 parent ee6d0e6 commit cd7ef69

3 files changed

Lines changed: 73 additions & 18 deletions

File tree

IPython/core/completer.py

Lines changed: 49 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -344,11 +344,52 @@ def __eq__(self, other)->Bool:
344344
def __hash__(self):
345345
return hash((self.start, self.end, self.text))
346346

347+
347348
_IC = Iterator[Completion]
348349

349-
def rectify_completions(text:str, completion:_IC, *, _debug=False)->_IC:
350+
351+
def _deduplicate_completions(text: str, completions: _IC)-> _IC:
350352
"""
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``
352393
353394
.. warning:: Unstable
354395
@@ -359,7 +400,7 @@ def rectify_completions(text:str, completion:_IC, *, _debug=False)->_IC:
359400
----------
360401
text: str
361402
text that should be completed.
362-
completion: Iterator[Completion]
403+
completions: Iterator[Completion]
363404
iterator over the completions to rectify
364405
365406
@@ -1510,22 +1551,17 @@ def completions(self, text: str, offset: int)->Iterator[Completion]:
15101551
fake Completion token to distinguish completion returned by Jedi
15111552
and usual IPython completion.
15121553
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.
15131559
"""
15141560
warnings.warn("_complete is a provisional API (as of IPython 6.0). "
15151561
"It may change without warnings. "
15161562
"Use in corresponding context manager.",
15171563
category=ProvisionalCompleterWarning, stacklevel=2)
15181564

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
15291565
seen = set()
15301566
for c in self._completions(text, offset, _timeout=self.jedi_compute_type_timeout/1000):
15311567
if c and (c in seen):

IPython/core/tests/test_completer.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import os
88
import sys
9+
import textwrap
910
import unittest
1011

1112
from contextlib import contextmanager
@@ -20,7 +21,8 @@
2021
from IPython.utils.generics import complete_object
2122
from IPython.testing import decorators as dec
2223

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)
2426
from nose.tools import assert_in, assert_not_in
2527

2628
#-----------------------------------------------------------------------------
@@ -294,14 +296,29 @@ def _test_not_complete(reason, s, comp):
294296

295297
import jedi
296298
jedi_version = tuple(int(i) for i in jedi.__version__.split('.')[:3])
297-
if jedi_version > (0,10):
299+
if jedi_version > (0, 10):
298300
yield _test_complete, 'jedi >0.9 should complete and not crash', 'a=1;a.', 'real'
299301
yield _test_complete, 'can infer first argument', 'a=(1,"foo");a[0].', 'real'
300302
yield _test_complete, 'can infer second argument', 'a=(1,"foo");a[1].', 'capitalize'
301303
yield _test_complete, 'cover duplicate completions', 'im', 'import', 0, 2
302304

303305
yield _test_not_complete, 'does not mix types', 'a=(1,"foo");a[0].', 'capitalize'
304306

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+
305322

306323
def test_greedy_completions():
307324
"""

IPython/terminal/ptutils.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
import unicodedata
1111
from wcwidth import wcwidth
1212

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)
1416
from prompt_toolkit.completion import Completer, Completion
1517
from prompt_toolkit.layout.lexers import Lexer
1618
from prompt_toolkit.layout.lexers import PygmentsLexer
@@ -61,8 +63,8 @@ def _get_completions(body, offset, cursor_position, ipyc):
6163
Private equivalent of get_completions() use only for unit_testing.
6264
"""
6365
debug = getattr(ipyc, 'debug', False)
64-
completions = rectify_completions(
65-
body, ipyc.completions(body, offset), _debug=debug)
66+
completions = _deduplicate_completions(
67+
body, ipyc.completions(body, offset))
6668
for c in completions:
6769
if not c.text:
6870
# Guard against completion machinery giving us an empty string.

0 commit comments

Comments
 (0)