##// END OF EJS Templates
Show signature with Jedi....
Matthias Bussonnier -
r23753:e8c3f90d
parent child
Show More
@@ -0,0 +1,4
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
1 # encoding: utf-8
2 """Completion for IPython.
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 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 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 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 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 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 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 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 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>' % (self.start, self.end, self.text, self.type or '?')
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 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 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 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 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 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 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--', _origin='debug')
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 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 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 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 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 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 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 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