Skip to content

Commit bf991c6

Browse files
Better signature abstractions. Retrieve signatures without Jedi when Jedi fails.
1 parent 2238d41 commit bf991c6

File tree

4 files changed

+296
-57
lines changed

4 files changed

+296
-57
lines changed

ptpython/completer.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ def get_completions(
157157
return
158158

159159
# Do Path completions (if there were no dictionary completions).
160+
# TODO: not if we have dictionary completions...
160161
if complete_event.completion_requested or self._complete_path_while_typing(
161162
document
162163
):
@@ -383,7 +384,7 @@ def get_completions(
383384
self, document: Document, complete_event: CompleteEvent
384385
) -> Iterable[Completion]:
385386

386-
# First, find all for-loops, and assing the first item of the
387+
# First, find all for-loops, and assign the first item of the
387388
# collections they're iterating to the iterator variable, so that we
388389
# can provide code completion on the iterators.
389390
temp_locals = self.get_locals().copy()
@@ -414,6 +415,17 @@ def _do_repr(self, obj: object) -> str:
414415
except BaseException:
415416
raise ReprFailedError
416417

418+
def eval_expression(self, document: Document, locals: Dict[str, Any]) -> object:
419+
"""
420+
Evaluate
421+
"""
422+
match = self.expression_pattern.search(document.text_before_cursor)
423+
if match is not None:
424+
object_var = match.groups()[0]
425+
return self._lookup(object_var, locals)
426+
427+
return None
428+
417429
def _get_expression_completions(
418430
self,
419431
document: Document,
@@ -423,17 +435,17 @@ def _get_expression_completions(
423435
"""
424436
Complete the [ or . operator after an object.
425437
"""
426-
match = self.expression_pattern.search(document.text_before_cursor)
427-
if match is not None:
428-
object_var = match.groups()[0]
429-
result = self._lookup(object_var, temp_locals)
438+
result = self.eval_expression(document, temp_locals)
439+
440+
if result is not None:
430441

431442
if isinstance(
432443
result,
433444
(list, tuple, dict, collections_abc.Mapping, collections_abc.Sequence),
434445
):
435446
yield Completion("[", 0)
436-
elif result is not None:
447+
448+
else:
437449
# Note: Don't call `if result` here. That can fail for types
438450
# that have custom truthness checks.
439451
yield Completion(".", 0)

ptpython/layout.py

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@
44
import platform
55
import sys
66
from enum import Enum
7+
from inspect import _ParameterKind as ParameterKind
78
from typing import TYPE_CHECKING, Optional
89

910
from prompt_toolkit.application import get_app
1011
from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER
1112
from prompt_toolkit.filters import (
1213
Condition,
13-
has_completions,
1414
has_focus,
1515
is_done,
1616
renderer_height_is_known,
@@ -248,30 +248,41 @@ def get_text_fragments() -> StyleAndTextTuples:
248248

249249
append((Signature + ",operator", "("))
250250

251-
try:
252-
enumerated_params = enumerate(sig.params)
253-
except AttributeError:
254-
# Workaround for #136: https://github.com/jonathanslenders/ptpython/issues/136
255-
# AttributeError: 'Lambda' object has no attribute 'get_subscope_by_name'
256-
return []
251+
got_positional_only = False
252+
got_keyword_only = False
253+
254+
for i, p in enumerate(sig.parameters):
255+
# Detect transition between positional-only and not positional-only.
256+
if p.kind == ParameterKind.POSITIONAL_ONLY:
257+
got_positional_only = True
258+
if got_positional_only and p.kind != ParameterKind.POSITIONAL_ONLY:
259+
got_positional_only = False
260+
append((Signature, "/"))
261+
append((Signature + ",operator", ", "))
257262

258-
for i, p in enumerated_params:
259-
# Workaround for #47: 'p' is None when we hit the '*' in the signature.
260-
# and sig has no 'index' attribute.
261-
# See: https://github.com/jonathanslenders/ptpython/issues/47
262-
# https://github.com/davidhalter/jedi/issues/598
263-
description = p.to_string() if p else "*"
263+
if not got_keyword_only and p.kind == ParameterKind.KEYWORD_ONLY:
264+
got_keyword_only = True
265+
append((Signature, "*"))
266+
append((Signature + ",operator", ", "))
267+
268+
description = p.name
264269
sig_index = getattr(sig, "index", 0)
265270

266271
if i == sig_index:
267272
# Note: we use `_Param.description` instead of
268273
# `_Param.name`, that way we also get the '*' before args.
269-
append((Signature + ",current-name", str(description)))
274+
append((Signature + ",current-name", description))
270275
else:
271276
append((Signature, str(description)))
277+
278+
if p.default:
279+
# NOTE: For the jedi-based completion, the default is
280+
# currently still part of the name.
281+
append((Signature, f"={p.default}"))
282+
272283
append((Signature + ",operator", ", "))
273284

274-
if sig.params:
285+
if sig.parameters:
275286
# Pop last comma
276287
result.pop()
277288

@@ -577,7 +588,7 @@ def menu_position():
577588
"""
578589
b = python_input.default_buffer
579590

580-
if b.complete_state is None and python_input.signatures:
591+
if python_input.signatures:
581592
row, col = python_input.signatures[0].bracket_start
582593
index = b.document.translate_row_col_to_index(row - 1, col)
583594
return index

ptpython/python_input.py

Lines changed: 13 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,9 @@
6666
from .layout import CompletionVisualisation, PtPythonLayout
6767
from .lexer import PtpythonLexer
6868
from .prompt_style import ClassicPrompt, IPythonPrompt, PromptStyle
69+
from .signatures import Signature, get_signatures_using_eval, get_signatures_using_jedi
6970
from .style import generate_style, get_all_code_styles, get_all_ui_styles
70-
from .utils import get_jedi_script_from_document, unindent_code
71+
from .utils import unindent_code
7172
from .validator import PythonValidator
7273

7374
__all__ = ["PythonInput"]
@@ -260,7 +261,7 @@ def __init__(
260261

261262
self.enable_syntax_highlighting: bool = True
262263
self.enable_fuzzy_completion: bool = False
263-
self.enable_dictionary_completion: bool = False
264+
self.enable_dictionary_completion: bool = False # Also eval-based completion.
264265
self.complete_private_attributes: CompletePrivateAttributes = (
265266
CompletePrivateAttributes.ALWAYS
266267
)
@@ -330,7 +331,7 @@ def __init__(
330331
self.current_statement_index: int = 1
331332

332333
# Code signatures. (This is set asynchronously after a timeout.)
333-
self.signatures: List[Any] = []
334+
self.signatures: List[Signature] = []
334335

335336
# Boolean indicating whether we have a signatures thread running.
336337
# (Never run more than one at the same time.)
@@ -917,36 +918,16 @@ def _on_input_timeout(self, buff: Buffer, loop=None) -> None:
917918
loop = loop or get_event_loop()
918919

919920
def run():
920-
script = get_jedi_script_from_document(
921+
# First, get signatures from Jedi. If we didn't found any and if
922+
# "dictionary completion" (eval-based completion) is enabled, then
923+
# get signatures using eval.
924+
signatures = get_signatures_using_jedi(
921925
document, self.get_locals(), self.get_globals()
922926
)
923-
924-
# Show signatures in help text.
925-
if script:
926-
try:
927-
signatures = script.get_signatures()
928-
except ValueError:
929-
# e.g. in case of an invalid \\x escape.
930-
signatures = []
931-
except Exception:
932-
# Sometimes we still get an exception (TypeError), because
933-
# of probably bugs in jedi. We can silence them.
934-
# See: https://github.com/davidhalter/jedi/issues/492
935-
signatures = []
936-
else:
937-
# Try to access the params attribute just once. For Jedi
938-
# signatures containing the keyword-only argument star,
939-
# this will crash when retrieving it the first time with
940-
# AttributeError. Every following time it works.
941-
# See: https://github.com/jonathanslenders/ptpython/issues/47
942-
# https://github.com/davidhalter/jedi/issues/598
943-
try:
944-
if signatures:
945-
signatures[0].params
946-
except AttributeError:
947-
pass
948-
else:
949-
signatures = []
927+
if not signatures and self.enable_dictionary_completion:
928+
signatures = get_signatures_using_eval(
929+
document, self.get_locals(), self.get_globals()
930+
)
950931

951932
self._get_signatures_thread_running = False
952933

@@ -957,11 +938,8 @@ def run():
957938

958939
# Set docstring in docstring buffer.
959940
if signatures:
960-
string = signatures[0].docstring()
961-
if not isinstance(string, str):
962-
string = string.decode("utf-8")
963941
self.docstring_buffer.reset(
964-
document=Document(string, cursor_position=0)
942+
document=Document(signatures[0].docstring, cursor_position=0)
965943
)
966944
else:
967945
self.docstring_buffer.reset()

0 commit comments

Comments
 (0)