summaryrefslogtreecommitdiffstats
path: root/ptpython/completer.py
diff options
context:
space:
mode:
Diffstat (limited to 'ptpython/completer.py')
-rw-r--r--ptpython/completer.py105
1 files changed, 63 insertions, 42 deletions
diff --git a/ptpython/completer.py b/ptpython/completer.py
index 9f7e10b..91d6647 100644
--- a/ptpython/completer.py
+++ b/ptpython/completer.py
@@ -1,10 +1,12 @@
+from __future__ import annotations
+
import ast
import collections.abc as collections_abc
import inspect
import keyword
import re
from enum import Enum
-from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, List, Optional
+from typing import TYPE_CHECKING, Any, Callable, Iterable
from prompt_toolkit.completion import (
CompleteEvent,
@@ -21,6 +23,7 @@ from prompt_toolkit.formatted_text import fragment_list_to_text, to_formatted_te
from ptpython.utils import get_jedi_script_from_document
if TYPE_CHECKING:
+ import jedi.api.classes
from prompt_toolkit.contrib.regular_languages.compiler import _CompiledGrammar
__all__ = ["PythonCompleter", "CompletePrivateAttributes", "HidePrivateCompleter"]
@@ -43,8 +46,8 @@ class PythonCompleter(Completer):
def __init__(
self,
- get_globals: Callable[[], dict],
- get_locals: Callable[[], dict],
+ get_globals: Callable[[], dict[str, Any]],
+ get_locals: Callable[[], dict[str, Any]],
enable_dictionary_completion: Callable[[], bool],
) -> None:
super().__init__()
@@ -57,8 +60,8 @@ class PythonCompleter(Completer):
self._jedi_completer = JediCompleter(get_globals, get_locals)
self._dictionary_completer = DictionaryCompleter(get_globals, get_locals)
- self._path_completer_cache: Optional[GrammarCompleter] = None
- self._path_completer_grammar_cache: Optional["_CompiledGrammar"] = None
+ self._path_completer_cache: GrammarCompleter | None = None
+ self._path_completer_grammar_cache: _CompiledGrammar | None = None
@property
def _path_completer(self) -> GrammarCompleter:
@@ -73,7 +76,7 @@ class PythonCompleter(Completer):
return self._path_completer_cache
@property
- def _path_completer_grammar(self) -> "_CompiledGrammar":
+ def _path_completer_grammar(self) -> _CompiledGrammar:
"""
Return the grammar for matching paths inside strings inside Python
code.
@@ -84,7 +87,7 @@ class PythonCompleter(Completer):
self._path_completer_grammar_cache = self._create_path_completer_grammar()
return self._path_completer_grammar_cache
- def _create_path_completer_grammar(self) -> "_CompiledGrammar":
+ def _create_path_completer_grammar(self) -> _CompiledGrammar:
def unwrapper(text: str) -> str:
return re.sub(r"\\(.)", r"\1", text)
@@ -188,7 +191,6 @@ class PythonCompleter(Completer):
):
# If we are inside a string, Don't do Jedi completion.
if not self._path_completer_grammar.match(document.text_before_cursor):
-
# Do Jedi Python completions.
yield from self._jedi_completer.get_completions(
document, complete_event
@@ -200,7 +202,11 @@ class JediCompleter(Completer):
Autocompleter that uses the Jedi library.
"""
- def __init__(self, get_globals, get_locals) -> None:
+ def __init__(
+ self,
+ get_globals: Callable[[], dict[str, Any]],
+ get_locals: Callable[[], dict[str, Any]],
+ ) -> None:
super().__init__()
self.get_globals = get_globals
@@ -237,7 +243,7 @@ class JediCompleter(Completer):
# Jedi issue: "KeyError: u'a_lambda'."
# https://github.com/jonathanslenders/ptpython/issues/89
pass
- except IOError:
+ except OSError:
# Jedi issue: "IOError: No such file or directory."
# https://github.com/jonathanslenders/ptpython/issues/71
pass
@@ -253,7 +259,7 @@ class JediCompleter(Completer):
# See: https://github.com/jonathanslenders/ptpython/issues/223
pass
except Exception:
- # Supress all other Jedi exceptions.
+ # Suppress all other Jedi exceptions.
pass
else:
# Move function parameters to the top.
@@ -296,7 +302,11 @@ class DictionaryCompleter(Completer):
function calls, so it only triggers attribute access.
"""
- def __init__(self, get_globals, get_locals):
+ def __init__(
+ self,
+ get_globals: Callable[[], dict[str, Any]],
+ get_locals: Callable[[], dict[str, Any]],
+ ) -> None:
super().__init__()
self.get_globals = get_globals
@@ -357,7 +367,7 @@ class DictionaryCompleter(Completer):
rf"""
{expression}
- # Dict loopup to complete (square bracket open + start of
+ # Dict lookup to complete (square bracket open + start of
# string).
\[
\s* ([^\[\]]*)$
@@ -370,14 +380,14 @@ class DictionaryCompleter(Completer):
rf"""
{expression}
- # Attribute loopup to complete (dot + varname).
+ # Attribute lookup to complete (dot + varname).
\.
\s* ([a-zA-Z0-9_]*)$
""",
re.VERBOSE,
)
- def _lookup(self, expression: str, temp_locals: Dict[str, Any]) -> object:
+ def _lookup(self, expression: str, temp_locals: dict[str, Any]) -> object:
"""
Do lookup of `object_var` in the context.
`temp_locals` is a dictionary, used for the locals.
@@ -390,7 +400,6 @@ class DictionaryCompleter(Completer):
def get_completions(
self, document: Document, complete_event: CompleteEvent
) -> Iterable[Completion]:
-
# First, find all for-loops, and assign the first item of the
# collections they're iterating to the iterator variable, so that we
# can provide code completion on the iterators.
@@ -422,7 +431,7 @@ class DictionaryCompleter(Completer):
except BaseException:
raise ReprFailedError
- def eval_expression(self, document: Document, locals: Dict[str, Any]) -> object:
+ def eval_expression(self, document: Document, locals: dict[str, Any]) -> object:
"""
Evaluate
"""
@@ -437,7 +446,7 @@ class DictionaryCompleter(Completer):
self,
document: Document,
complete_event: CompleteEvent,
- temp_locals: Dict[str, Any],
+ temp_locals: dict[str, Any],
) -> Iterable[Completion]:
"""
Complete the [ or . operator after an object.
@@ -445,7 +454,6 @@ class DictionaryCompleter(Completer):
result = self.eval_expression(document, temp_locals)
if result is not None:
-
if isinstance(
result,
(list, tuple, dict, collections_abc.Mapping, collections_abc.Sequence),
@@ -461,20 +469,29 @@ class DictionaryCompleter(Completer):
self,
document: Document,
complete_event: CompleteEvent,
- temp_locals: Dict[str, Any],
+ temp_locals: dict[str, Any],
) -> Iterable[Completion]:
"""
Complete dictionary keys.
"""
- def abbr_meta(text: str) -> str:
- " Abbreviate meta text, make sure it fits on one line. "
- # Take first line, if multiple lines.
- if len(text) > 20:
- text = text[:20] + "..."
- if "\n" in text:
- text = text.split("\n", 1)[0] + "..."
- return text
+ def meta_repr(value: object) -> Callable[[], str]:
+ "Abbreviate meta text, make sure it fits on one line."
+
+ # We return a function, so that it gets computed when it's needed.
+ # When there are many completions, that improves the performance
+ # quite a bit (for the multi-column completion menu, we only need
+ # to display one meta text).
+ def get_value_repr() -> str:
+ text = self._do_repr(value)
+
+ # Take first line, if multiple lines.
+ if "\n" in text:
+ text = text.split("\n", 1)[0] + "..."
+
+ return text
+
+ return get_value_repr
match = self.item_lookup_pattern.search(document.text_before_cursor)
if match is not None:
@@ -495,7 +512,7 @@ class DictionaryCompleter(Completer):
else:
break
- for k in result:
+ for k, v in result.items():
if str(k).startswith(str(key_obj)):
try:
k_repr = self._do_repr(k)
@@ -503,7 +520,7 @@ class DictionaryCompleter(Completer):
k_repr + "]",
-len(key),
display=f"[{k_repr}]",
- display_meta=abbr_meta(self._do_repr(result[k])),
+ display_meta=meta_repr(v),
)
except ReprFailedError:
pass
@@ -519,8 +536,12 @@ class DictionaryCompleter(Completer):
k_repr + "]",
-len(key),
display=f"[{k_repr}]",
- display_meta=abbr_meta(self._do_repr(result[k])),
+ display_meta=meta_repr(result[k]),
)
+ except KeyError:
+ # `result[k]` lookup failed. Trying to complete
+ # broken object.
+ pass
except ReprFailedError:
pass
@@ -528,7 +549,7 @@ class DictionaryCompleter(Completer):
self,
document: Document,
complete_event: CompleteEvent,
- temp_locals: Dict[str, Any],
+ temp_locals: dict[str, Any],
) -> Iterable[Completion]:
"""
Complete attribute names.
@@ -545,12 +566,11 @@ class DictionaryCompleter(Completer):
def get_suffix(name: str) -> str:
try:
obj = getattr(result, name, None)
- if inspect.isfunction(obj):
+ if inspect.isfunction(obj) or inspect.ismethod(obj):
return "()"
-
- if isinstance(obj, dict):
+ if isinstance(obj, collections_abc.Mapping):
return "{}"
- if isinstance(obj, (list, tuple)):
+ if isinstance(obj, collections_abc.Sequence):
return "[]"
except:
pass
@@ -561,13 +581,13 @@ class DictionaryCompleter(Completer):
suffix = get_suffix(name)
yield Completion(name, -len(attr_name), display=name + suffix)
- def _sort_attribute_names(self, names: List[str]) -> List[str]:
+ def _sort_attribute_names(self, names: list[str]) -> list[str]:
"""
Sort attribute names alphabetically, but move the double underscore and
underscore names to the end.
"""
- def sort_key(name: str):
+ def sort_key(name: str) -> tuple[int, str]:
if name.startswith("__"):
return (2, name) # Double underscore comes latest.
if name.startswith("_"):
@@ -579,7 +599,7 @@ class DictionaryCompleter(Completer):
class HidePrivateCompleter(Completer):
"""
- Wrapper around completer that hides private fields, deponding on whether or
+ Wrapper around completer that hides private fields, depending on whether or
not public fields are shown.
(The reason this is implemented as a `Completer` wrapper is because this
@@ -597,7 +617,6 @@ class HidePrivateCompleter(Completer):
def get_completions(
self, document: Document, complete_event: CompleteEvent
) -> Iterable[Completion]:
-
completions = list(self.completer.get_completions(document, complete_event))
complete_private_attributes = self.complete_private_attributes()
hide_private = False
@@ -621,7 +640,7 @@ class HidePrivateCompleter(Completer):
class ReprFailedError(Exception):
- " Raised when the repr() call in `DictionaryCompleter` fails. "
+ "Raised when the repr() call in `DictionaryCompleter` fails."
try:
@@ -632,7 +651,9 @@ except ImportError: # Python 2.
_builtin_names = []
-def _get_style_for_jedi_completion(jedi_completion) -> str:
+def _get_style_for_jedi_completion(
+ jedi_completion: jedi.api.classes.Completion,
+) -> str:
"""
Return completion style to use for this name.
"""