Show More
@@ -0,0 +1,4 b'' | |||||
|
1 | Terminal IPython will now show the signature of the function while completing. | |||
|
2 | Only the currently highlighted function will show its signature on the line | |||
|
3 | below the completer by default. The functionality is recent so might be | |||
|
4 | limited, we welcome bug report and enhancement request on it. |
@@ -1,4 +1,3 b'' | |||||
1 | # encoding: utf-8 |
|
|||
2 |
|
|
1 | """Completion for IPython. | |
3 |
|
2 | |||
4 | This module started as fork of the rlcompleter module in the Python standard |
|
3 | This module started as fork of the rlcompleter module in the Python standard | |
@@ -87,7 +86,7 b' We welcome any feedback on these new API, and we also encourage you to try this' | |||||
87 | module in debug mode (start IPython with ``--Completer.debug=True``) in order |
|
86 | module in debug mode (start IPython with ``--Completer.debug=True``) in order | |
88 | to have extra logging information is :any:`jedi` is crashing, or if current |
|
87 | to have extra logging information is :any:`jedi` is crashing, or if current | |
89 | IPython completer pending deprecations are returning results not yet handled |
|
88 | IPython completer pending deprecations are returning results not yet handled | |
90 |
by :any:`jedi` |
|
89 | by :any:`jedi` | |
91 |
|
90 | |||
92 | Using Jedi for tab completion allow snippets like the following to work without |
|
91 | Using Jedi for tab completion allow snippets like the following to work without | |
93 | having to execute any code: |
|
92 | having to execute any code: | |
@@ -103,8 +102,6 b' Be sure to update :any:`jedi` to the latest stable version or to try the' | |||||
103 | current development version to get better completions. |
|
102 | current development version to get better completions. | |
104 | """ |
|
103 | """ | |
105 |
|
104 | |||
106 | # skip module docstests |
|
|||
107 | skip_doctest = True |
|
|||
108 |
|
105 | |||
109 | # Copyright (c) IPython Development Team. |
|
106 | # Copyright (c) IPython Development Team. | |
110 | # Distributed under the terms of the Modified BSD License. |
|
107 | # Distributed under the terms of the Modified BSD License. | |
@@ -142,9 +139,13 b' from IPython.utils.dir2 import dir2, get_real_method' | |||||
142 | from IPython.utils.process import arg_split |
|
139 | from IPython.utils.process import arg_split | |
143 | from traitlets import Bool, Enum, observe, Int |
|
140 | from traitlets import Bool, Enum, observe, Int | |
144 |
|
141 | |||
|
142 | # skip module docstests | |||
|
143 | skip_doctest = True | |||
|
144 | ||||
145 | try: |
|
145 | try: | |
146 | import jedi |
|
146 | import jedi | |
147 | import jedi.api.helpers |
|
147 | import jedi.api.helpers | |
|
148 | import jedi.api.classes | |||
148 | JEDI_INSTALLED = True |
|
149 | JEDI_INSTALLED = True | |
149 | except ImportError: |
|
150 | except ImportError: | |
150 | JEDI_INSTALLED = False |
|
151 | JEDI_INSTALLED = False | |
@@ -237,7 +238,7 b' def protect_filename(s, protectables=PROTECTABLES):' | |||||
237 | return s |
|
238 | return s | |
238 |
|
239 | |||
239 |
|
240 | |||
240 | def expand_user(path): |
|
241 | def expand_user(path:str) -> Tuple[str, bool, str]: | |
241 | """Expand ``~``-style usernames in strings. |
|
242 | """Expand ``~``-style usernames in strings. | |
242 |
|
243 | |||
243 | This is similar to :func:`os.path.expanduser`, but it computes and returns |
|
244 | This is similar to :func:`os.path.expanduser`, but it computes and returns | |
@@ -277,7 +278,7 b' def expand_user(path):' | |||||
277 | return newpath, tilde_expand, tilde_val |
|
278 | return newpath, tilde_expand, tilde_val | |
278 |
|
279 | |||
279 |
|
280 | |||
280 | def compress_user(path, tilde_expand, tilde_val): |
|
281 | def compress_user(path:str, tilde_expand:bool, tilde_val:str) -> str: | |
281 | """Does the opposite of expand_user, with its outputs. |
|
282 | """Does the opposite of expand_user, with its outputs. | |
282 | """ |
|
283 | """ | |
283 | if tilde_expand: |
|
284 | if tilde_expand: | |
@@ -338,6 +339,8 b' class _FakeJediCompletion:' | |||||
338 | self.complete = name |
|
339 | self.complete = name | |
339 | self.type = 'crashed' |
|
340 | self.type = 'crashed' | |
340 | self.name_with_symbols = name |
|
341 | self.name_with_symbols = name | |
|
342 | self.signature = '' | |||
|
343 | self._origin = 'fake' | |||
341 |
|
344 | |||
342 | def __repr__(self): |
|
345 | def __repr__(self): | |
343 | return '<Fake completion object jedi has crashed>' |
|
346 | return '<Fake completion object jedi has crashed>' | |
@@ -366,7 +369,9 b' class Completion:' | |||||
366 | ``IPython.python_matches``, ``IPython.magics_matches``...). |
|
369 | ``IPython.python_matches``, ``IPython.magics_matches``...). | |
367 | """ |
|
370 | """ | |
368 |
|
371 | |||
369 | def __init__(self, start: int, end: int, text: str, *, type: str=None, _origin='') -> None: |
|
372 | __slots__ = ['start', 'end', 'text', 'type', 'signature', '_origin'] | |
|
373 | ||||
|
374 | def __init__(self, start: int, end: int, text: str, *, type: str=None, _origin='', signature='') -> None: | |||
370 | warnings.warn("``Completion`` is a provisional API (as of IPython 6.0). " |
|
375 | warnings.warn("``Completion`` is a provisional API (as of IPython 6.0). " | |
371 | "It may change without warnings. " |
|
376 | "It may change without warnings. " | |
372 | "Use in corresponding context manager.", |
|
377 | "Use in corresponding context manager.", | |
@@ -376,10 +381,12 b' class Completion:' | |||||
376 | self.end = end |
|
381 | self.end = end | |
377 | self.text = text |
|
382 | self.text = text | |
378 | self.type = type |
|
383 | self.type = type | |
|
384 | self.signature = signature | |||
379 | self._origin = _origin |
|
385 | self._origin = _origin | |
380 |
|
386 | |||
381 | def __repr__(self): |
|
387 | def __repr__(self): | |
382 |
return '<Completion start=%s end=%s text=%r type=%r>' % |
|
388 | return '<Completion start=%s end=%s text=%r type=%r, signature=%r,>' % \ | |
|
389 | (self.start, self.end, self.text, self.type or '?', self.signature or '?') | |||
383 |
|
390 | |||
384 | def __eq__(self, other)->Bool: |
|
391 | def __eq__(self, other)->Bool: | |
385 | """ |
|
392 | """ | |
@@ -417,6 +424,10 b' def _deduplicate_completions(text: str, completions: _IC)-> _IC:' | |||||
417 | completions: Iterator[Completion] |
|
424 | completions: Iterator[Completion] | |
418 | iterator over the completions to deduplicate |
|
425 | iterator over the completions to deduplicate | |
419 |
|
426 | |||
|
427 | Yields | |||
|
428 | ------ | |||
|
429 | `Completions` objects | |||
|
430 | ||||
420 |
|
431 | |||
421 | Completions coming from multiple sources, may be different but end up having |
|
432 | Completions coming from multiple sources, may be different but end up having | |
422 | the same effect when applied to ``text``. If this is the case, this will |
|
433 | the same effect when applied to ``text``. If this is the case, this will | |
@@ -489,7 +500,7 b' def rectify_completions(text: str, completions: _IC, *, _debug=False)->_IC:' | |||||
489 | seen_jedi.add(new_text) |
|
500 | seen_jedi.add(new_text) | |
490 | elif c._origin == 'IPCompleter.python_matches': |
|
501 | elif c._origin == 'IPCompleter.python_matches': | |
491 | seen_python_matches.add(new_text) |
|
502 | seen_python_matches.add(new_text) | |
492 | yield Completion(new_start, new_end, new_text, type=c.type, _origin=c._origin) |
|
503 | yield Completion(new_start, new_end, new_text, type=c.type, _origin=c._origin, signature=c.signature) | |
493 | diff = seen_python_matches.difference(seen_jedi) |
|
504 | diff = seen_python_matches.difference(seen_jedi) | |
494 | if diff and _debug: |
|
505 | if diff and _debug: | |
495 | print('IPython.python matches have extras:', diff) |
|
506 | print('IPython.python matches have extras:', diff) | |
@@ -933,6 +944,52 b' def back_latex_name_matches(text:str):' | |||||
933 | return u'', () |
|
944 | return u'', () | |
934 |
|
945 | |||
935 |
|
946 | |||
|
947 | def _formatparamchildren(parameter) -> str: | |||
|
948 | """ | |||
|
949 | Get parameter name and value from Jedi Private API | |||
|
950 | ||||
|
951 | Jedi does not expose a simple way to get `param=value` from its API. | |||
|
952 | ||||
|
953 | Prameter | |||
|
954 | ======== | |||
|
955 | ||||
|
956 | parameter: | |||
|
957 | Jedi's function `Param` | |||
|
958 | ||||
|
959 | Returns | |||
|
960 | ======= | |||
|
961 | ||||
|
962 | A string like 'a', 'b=1', '*args', '**kwargs' | |||
|
963 | ||||
|
964 | ||||
|
965 | """ | |||
|
966 | description = parameter.description | |||
|
967 | if not description.startswith('param '): | |||
|
968 | raise ValueError('Jedi function parameter description have change format.' | |||
|
969 | 'Expected "param ...", found %r".' % description) | |||
|
970 | return description[6:] | |||
|
971 | ||||
|
972 | def _make_signature(completion)-> str: | |||
|
973 | """ | |||
|
974 | Make the signature from a jedi completion | |||
|
975 | ||||
|
976 | Parameter | |||
|
977 | ========= | |||
|
978 | ||||
|
979 | completion: jedi.Completion | |||
|
980 | object does not complete a function type | |||
|
981 | ||||
|
982 | Returns | |||
|
983 | ======= | |||
|
984 | ||||
|
985 | a string consisting of the function signature, with the parenthesis but | |||
|
986 | without the function name. example: | |||
|
987 | `(a, *args, b=1, **kwargs)` | |||
|
988 | ||||
|
989 | """ | |||
|
990 | ||||
|
991 | return '(%s)'% ', '.join([f for f in (_formatparamchildren(p) for p in completion.params) if f]) | |||
|
992 | ||||
936 | class IPCompleter(Completer): |
|
993 | class IPCompleter(Completer): | |
937 | """Extension of the completer class with IPython-specific features""" |
|
994 | """Extension of the completer class with IPython-specific features""" | |
938 |
|
995 | |||
@@ -1762,10 +1819,15 b' class IPCompleter(Completer):' | |||||
1762 | print("Error in Jedi getting type of ", jm) |
|
1819 | print("Error in Jedi getting type of ", jm) | |
1763 | type_ = None |
|
1820 | type_ = None | |
1764 | delta = len(jm.name_with_symbols) - len(jm.complete) |
|
1821 | delta = len(jm.name_with_symbols) - len(jm.complete) | |
|
1822 | if type_ == 'function': | |||
|
1823 | signature = _make_signature(jm) | |||
|
1824 | else: | |||
|
1825 | signature = '' | |||
1765 | yield Completion(start=offset - delta, |
|
1826 | yield Completion(start=offset - delta, | |
1766 | end=offset, |
|
1827 | end=offset, | |
1767 | text=jm.name_with_symbols, |
|
1828 | text=jm.name_with_symbols, | |
1768 | type=type_, |
|
1829 | type=type_, | |
|
1830 | signature=signature, | |||
1769 | _origin='jedi') |
|
1831 | _origin='jedi') | |
1770 |
|
1832 | |||
1771 | if time.monotonic() > deadline: |
|
1833 | if time.monotonic() > deadline: | |
@@ -1777,7 +1839,8 b' class IPCompleter(Completer):' | |||||
1777 | end=offset, |
|
1839 | end=offset, | |
1778 | text=jm.name_with_symbols, |
|
1840 | text=jm.name_with_symbols, | |
1779 | type='<unknown>', # don't compute type for speed |
|
1841 | type='<unknown>', # don't compute type for speed | |
1780 |
_origin='jedi' |
|
1842 | _origin='jedi', | |
|
1843 | signature='') | |||
1781 |
|
1844 | |||
1782 |
|
1845 | |||
1783 | start_offset = before.rfind(matched_text) |
|
1846 | start_offset = before.rfind(matched_text) | |
@@ -1785,13 +1848,14 b' class IPCompleter(Completer):' | |||||
1785 | # TODO: |
|
1848 | # TODO: | |
1786 | # Supress this, right now just for debug. |
|
1849 | # Supress this, right now just for debug. | |
1787 | if jedi_matches and matches and self.debug: |
|
1850 | if jedi_matches and matches and self.debug: | |
1788 |
yield Completion(start=start_offset, end=offset, text='--jedi/ipython--', |
|
1851 | yield Completion(start=start_offset, end=offset, text='--jedi/ipython--', | |
|
1852 | _origin='debug', type='none', signature='') | |||
1789 |
|
1853 | |||
1790 | # I'm unsure if this is always true, so let's assert and see if it |
|
1854 | # I'm unsure if this is always true, so let's assert and see if it | |
1791 | # crash |
|
1855 | # crash | |
1792 | assert before.endswith(matched_text) |
|
1856 | assert before.endswith(matched_text) | |
1793 | for m, t in zip(matches, matches_origin): |
|
1857 | for m, t in zip(matches, matches_origin): | |
1794 | yield Completion(start=start_offset, end=offset, text=m, _origin=t) |
|
1858 | yield Completion(start=start_offset, end=offset, text=m, _origin=t, signature='', type='<unknown>') | |
1795 |
|
1859 | |||
1796 |
|
1860 | |||
1797 | def complete(self, text=None, line_buffer=None, cursor_pos=None): |
|
1861 | def complete(self, text=None, line_buffer=None, cursor_pos=None): |
@@ -37,6 +37,8 b' from IPython.utils._process_common import arg_split' | |||||
37 | # FIXME: this should be pulled in with the right call via the component system |
|
37 | # FIXME: this should be pulled in with the right call via the component system | |
38 | from IPython import get_ipython |
|
38 | from IPython import get_ipython | |
39 |
|
39 | |||
|
40 | from typing import List | |||
|
41 | ||||
40 | #----------------------------------------------------------------------------- |
|
42 | #----------------------------------------------------------------------------- | |
41 | # Globals and constants |
|
43 | # Globals and constants | |
42 | #----------------------------------------------------------------------------- |
|
44 | #----------------------------------------------------------------------------- | |
@@ -153,7 +155,7 b' def is_importable(module, attr, only_modules):' | |||||
153 | return not(attr[:2] == '__' and attr[-2:] == '__') |
|
155 | return not(attr[:2] == '__' and attr[-2:] == '__') | |
154 |
|
156 | |||
155 |
|
157 | |||
156 | def try_import(mod: str, only_modules=False): |
|
158 | def try_import(mod: str, only_modules=False) -> List[str]: | |
157 | """ |
|
159 | """ | |
158 | Try to import given module and return list of potential completions. |
|
160 | Try to import given module and return list of potential completions. | |
159 | """ |
|
161 | """ | |
@@ -173,9 +175,9 b' def try_import(mod: str, only_modules=False):' | |||||
173 | completions.extend(getattr(m, '__all__', [])) |
|
175 | completions.extend(getattr(m, '__all__', [])) | |
174 | if m_is_init: |
|
176 | if m_is_init: | |
175 | completions.extend(module_list(os.path.dirname(m.__file__))) |
|
177 | completions.extend(module_list(os.path.dirname(m.__file__))) | |
176 | completions = {c for c in completions if isinstance(c, str)} |
|
178 | completions_set = {c for c in completions if isinstance(c, str)} | |
177 | completions.discard('__init__') |
|
179 | completions_set.discard('__init__') | |
178 | return list(completions) |
|
180 | return list(completions_set) | |
179 |
|
181 | |||
180 |
|
182 | |||
181 | #----------------------------------------------------------------------------- |
|
183 | #----------------------------------------------------------------------------- |
@@ -335,6 +335,18 b' def test_jedi():' | |||||
335 |
|
335 | |||
336 | yield _test_not_complete, 'does not mix types', 'a=(1,"foo");a[0].', 'capitalize' |
|
336 | yield _test_not_complete, 'does not mix types', 'a=(1,"foo");a[0].', 'capitalize' | |
337 |
|
337 | |||
|
338 | def test_completion_have_signature(): | |||
|
339 | """ | |||
|
340 | Lets make sure jedi is capable of pulling out the signature of the function we are completing. | |||
|
341 | """ | |||
|
342 | ip = get_ipython() | |||
|
343 | with provisionalcompleter(): | |||
|
344 | completions = ip.Completer.completions('ope', 3) | |||
|
345 | c = next(completions) # should be `open` | |||
|
346 | assert 'file' in c.signature, "Signature of function was not found by completer" | |||
|
347 | assert 'encoding' in c.signature, "Signature of function was not found by completer" | |||
|
348 | ||||
|
349 | ||||
338 | def test_deduplicate_completions(): |
|
350 | def test_deduplicate_completions(): | |
339 | """ |
|
351 | """ | |
340 | Test that completions are correctly deduplicated (even if ranges are not the same) |
|
352 | Test that completions are correctly deduplicated (even if ranges are not the same) | |
@@ -946,4 +958,4 b' def test_snake_case_completion():' | |||||
946 | ip.user_ns['some_four'] = 4 |
|
958 | ip.user_ns['some_four'] = 4 | |
947 | _, matches = ip.complete("s_", "print(s_f") |
|
959 | _, matches = ip.complete("s_", "print(s_f") | |
948 | nt.assert_in('some_three', matches) |
|
960 | nt.assert_in('some_three', matches) | |
949 | nt.assert_in('some_four', matches) No newline at end of file |
|
961 | nt.assert_in('some_four', matches) |
@@ -120,9 +120,9 b' class IPythonPTCompleter(Completer):' | |||||
120 |
|
120 | |||
121 | adjusted_text = _adjust_completion_text_based_on_context(c.text, body, offset) |
|
121 | adjusted_text = _adjust_completion_text_based_on_context(c.text, body, offset) | |
122 | if c.type == 'function': |
|
122 | if c.type == 'function': | |
123 | display_text = display_text + '()' |
|
123 | yield Completion(adjusted_text, start_position=c.start - offset, display=_elide(display_text+'()'), display_meta=c.type+c.signature) | |
124 |
|
124 | else: | ||
125 | yield Completion(adjusted_text, start_position=c.start - offset, display=_elide(display_text), display_meta=c.type) |
|
125 | yield Completion(adjusted_text, start_position=c.start - offset, display=_elide(display_text), display_meta=c.type) | |
126 |
|
126 | |||
127 | class IPythonPTLexer(Lexer): |
|
127 | class IPythonPTLexer(Lexer): | |
128 | """ |
|
128 | """ |
@@ -100,6 +100,8 b' IPython and Jedi will be able to infer that ``data[0]`` is actually a string' | |||||
100 | and should show relevant completions like ``upper()``, ``lower()`` and other |
|
100 | and should show relevant completions like ``upper()``, ``lower()`` and other | |
101 | string methods. You can use the :kbd:`Tab` key to cycle through completions, |
|
101 | string methods. You can use the :kbd:`Tab` key to cycle through completions, | |
102 | and while a completion is highlighted, its type will be shown as well. |
|
102 | and while a completion is highlighted, its type will be shown as well. | |
|
103 | When the type of the completion is a function, the completer will also show the | |||
|
104 | signature of the function when highlighted. | |||
103 |
|
105 | |||
104 | Exploring your objects |
|
106 | Exploring your objects | |
105 | ====================== |
|
107 | ====================== |
General Comments 0
You need to be logged in to leave comments.
Login now