From a395a25f3307c7ece9c1fffe7c833f04556648b2 Mon Sep 17 00:00:00 2001 From: Jonathan Slenders Date: Fri, 25 Sep 2020 17:16:42 +0200 Subject: [PATCH] Added option for hiding/showing private completions. --- ptpython/completer.py | 79 ++++++++++++++++++++++++++++++++++------ ptpython/layout.py | 5 ++- ptpython/python_input.py | 46 +++++++++++++++++++---- 3 files changed, 111 insertions(+), 19 deletions(-) diff --git a/ptpython/completer.py b/ptpython/completer.py index e4b43fc0..535d2e2e 100644 --- a/ptpython/completer.py +++ b/ptpython/completer.py @@ -1,7 +1,8 @@ import ast import keyword import re -from typing import TYPE_CHECKING, Any, Dict, Iterable, List +from enum import Enum +from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, List, Optional from prompt_toolkit.completion import ( CompleteEvent, @@ -12,13 +13,24 @@ from prompt_toolkit.contrib.regular_languages.compiler import compile as compile_grammar from prompt_toolkit.contrib.regular_languages.completion import GrammarCompleter from prompt_toolkit.document import Document +from prompt_toolkit.formatted_text import fragment_list_to_text, to_formatted_text from ptpython.utils import get_jedi_script_from_document if TYPE_CHECKING: from prompt_toolkit.contrib.regular_languages.compiler import _CompiledGrammar -__all__ = ["PythonCompleter"] +__all__ = ["PythonCompleter", "CompletePrivateAttributes", "HidePrivateCompleter"] + + +class CompletePrivateAttributes(Enum): + """ + Should we display private attributes in the completion pop-up? + """ + + NEVER = "NEVER" + IF_NO_PUBLIC = "IF_NO_PUBLIC" + ALWAYS = "ALWAYS" class PythonCompleter(Completer): @@ -26,7 +38,9 @@ class PythonCompleter(Completer): Completer for Python code. """ - def __init__(self, get_globals, get_locals, get_enable_dictionary_completion): + def __init__( + self, get_globals, get_locals, get_enable_dictionary_completion + ) -> None: super().__init__() self.get_globals = get_globals @@ -35,8 +49,8 @@ def __init__(self, get_globals, get_locals, get_enable_dictionary_completion): self.dictionary_completer = DictionaryCompleter(get_globals, get_locals) - self._path_completer_cache = None - self._path_completer_grammar_cache = None + self._path_completer_cache: Optional[GrammarCompleter] = None + self._path_completer_grammar_cache: Optional["_CompiledGrammar"] = None @property def _path_completer(self) -> GrammarCompleter: @@ -158,7 +172,7 @@ def get_completions( if script: try: - completions = script.completions() + jedi_completions = script.completions() except TypeError: # Issue #9: bad syntax causes completions() to fail in jedi. # https://github.com/jonathanslenders/python-prompt-toolkit/issues/9 @@ -196,12 +210,12 @@ def get_completions( # Supress all other Jedi exceptions. pass else: - for c in completions: + for jc in jedi_completions: yield Completion( - c.name_with_symbols, - len(c.complete) - len(c.name_with_symbols), - display=c.name_with_symbols, - style=_get_style_for_name(c.name_with_symbols), + jc.name_with_symbols, + len(jc.complete) - len(jc.name_with_symbols), + display=jc.name_with_symbols, + style=_get_style_for_name(jc.name_with_symbols), ) @@ -464,6 +478,49 @@ def sort_key(name: str): return sorted(names, key=sort_key) +class HidePrivateCompleter(Completer): + """ + Wrapper around completer that hides private fields, deponding on whether or + not public fields are shown. + + (The reason this is implemented as a `Completer` wrapper is because this + way it works also with `FuzzyCompleter`.) + """ + + def __init__( + self, + completer: Completer, + complete_private_attributes: Callable[[], CompletePrivateAttributes], + ) -> None: + self.completer = completer + self.complete_private_attributes = complete_private_attributes + + 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 + + def is_private(completion: Completion) -> bool: + text = fragment_list_to_text(to_formatted_text(completion.display)) + return text.startswith("_") + + if complete_private_attributes == CompletePrivateAttributes.NEVER: + hide_private = True + + elif complete_private_attributes == CompletePrivateAttributes.IF_NO_PUBLIC: + hide_private = any(not is_private(completion) for completion in completions) + + if hide_private: + completions = [ + completion for completion in completions if not is_private(completion) + ] + + return completions + + class ReprFailedError(Exception): " Raised when the repr() call in `DictionaryCompleter` fails. " diff --git a/ptpython/layout.py b/ptpython/layout.py index d50a3a53..b06b95d3 100644 --- a/ptpython/layout.py +++ b/ptpython/layout.py @@ -213,7 +213,10 @@ def get_help_text(): return ConditionalContainer( content=Window( - FormattedTextControl(get_help_text), style=token, height=Dimension(min=3) + FormattedTextControl(get_help_text), + style=token, + height=Dimension(min=3), + wrap_lines=True, ), filter=ShowSidebar(python_input) & Condition(lambda: python_input.show_sidebar_help) diff --git a/ptpython/python_input.py b/ptpython/python_input.py index 5c08c1b4..c119e391 100644 --- a/ptpython/python_input.py +++ b/ptpython/python_input.py @@ -51,7 +51,7 @@ from prompt_toolkit.validation import ConditionalValidator, Validator from pygments.lexers import Python3Lexer as PythonLexer -from .completer import PythonCompleter +from .completer import CompletePrivateAttributes, HidePrivateCompleter, PythonCompleter from .history_browser import PythonHistory from .key_bindings import ( load_confirm_exit_bindings, @@ -180,13 +180,17 @@ def __init__( self.get_globals: _GetNamespace = get_globals or (lambda: {}) self.get_locals: _GetNamespace = get_locals or self.get_globals - self._completer = _completer or FuzzyCompleter( - PythonCompleter( - self.get_globals, - self.get_locals, - lambda: self.enable_dictionary_completion, + self._completer = HidePrivateCompleter( + _completer + or FuzzyCompleter( + PythonCompleter( + self.get_globals, + self.get_locals, + lambda: self.enable_dictionary_completion, + ), + enable_fuzzy=Condition(lambda: self.enable_fuzzy_completion), ), - enable_fuzzy=Condition(lambda: self.enable_fuzzy_completion), + lambda: self.complete_private_attributes, ) self._validator = _validator or PythonValidator(self.get_compiler_flags) self._lexer = _lexer or PygmentsLexer(PythonLexer) @@ -239,6 +243,9 @@ def __init__( self.enable_syntax_highlighting: bool = True self.enable_fuzzy_completion: bool = False self.enable_dictionary_completion: bool = False + self.complete_private_attributes: CompletePrivateAttributes = ( + CompletePrivateAttributes.ALWAYS + ) self.swap_light_and_dark: bool = False self.highlight_matching_parenthesis: bool = False self.show_sidebar: bool = False # Currently show the sidebar. @@ -530,6 +537,31 @@ def get_values(): "off": lambda: disable("complete_while_typing"), }, ), + Option( + title="Complete private attrs", + description="Show or hide private attributes in the completions. " + "'If no public' means: show private attributes only if no public " + "matches are found or if an underscore was typed.", + get_current_value=lambda: { + CompletePrivateAttributes.NEVER: "Never", + CompletePrivateAttributes.ALWAYS: "Always", + CompletePrivateAttributes.IF_NO_PUBLIC: "If no public", + }[self.complete_private_attributes], + get_values=lambda: { + "Never": lambda: enable( + "complete_private_attributes", + CompletePrivateAttributes.NEVER, + ), + "Always": lambda: enable( + "complete_private_attributes", + CompletePrivateAttributes.ALWAYS, + ), + "If no public": lambda: enable( + "complete_private_attributes", + CompletePrivateAttributes.IF_NO_PUBLIC, + ), + }, + ), Option( title="Enable fuzzy completion", description="Enable fuzzy completion.",