1
0
Fork 0

Merging upstream version 3.0.23.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-02-09 18:30:14 +01:00
parent 4c4da9307f
commit 568aae77cf
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
27 changed files with 230 additions and 152 deletions

View file

@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.7, 3.8, 3.9, "3.10"]
python-version: [3.7, 3.8, 3.9, "3.10", "3.11"]
steps:
- uses: actions/checkout@v2

View file

@ -1,6 +1,17 @@
CHANGELOG
=========
3.0.23: 2023-02-22
------------------
Fixes:
- Don't print exception messages twice for unhandled exceptions.
- Added cursor shape support.
Breaking changes:
- Drop Python 3.6 support.
3.0.22: 2022-12-06
------------------

View file

@ -213,7 +213,7 @@ This is also available for embedding:
.. code:: python
from ptpython.ipython.repl import embed
from ptpython.ipython import embed
embed(globals(), locals())

View file

@ -67,7 +67,7 @@ When a normal blocking embed is used:
When an awaitable embed is used, for embedding in a coroutine, but having the
event loop continue:
* We run the input method from the blocking embed in an asyncio executor
and do an `await loop.run_in_excecutor(...)`.
and do an `await loop.run_in_executor(...)`.
* The "eval" happens again in the main thread.
* "print" is also similar, except that the pager code (if used) runs in an
executor too.

View file

@ -3,6 +3,7 @@ Configuration example for ``ptpython``.
Copy this file to $XDG_CONFIG_HOME/ptpython/config.py
On Linux, this is: ~/.config/ptpython/config.py
On macOS, this is: ~/Library/Application Support/ptpython/config.py
"""
from prompt_toolkit.filters import ViInsertMode
from prompt_toolkit.key_binding.key_processor import KeyPress
@ -49,7 +50,7 @@ def configure(repl):
# Swap light/dark colors on or off
repl.swap_light_and_dark = False
# Highlight matching parethesis.
# Highlight matching parentheses.
repl.highlight_matching_parenthesis = True
# Line wrapping. (Instead of horizontal scrolling.)

View file

@ -1,3 +1,5 @@
from __future__ import annotations
from .repl import embed
__all__ = ["embed"]

View file

@ -1,6 +1,8 @@
"""
Make `python -m ptpython` an alias for running `./ptpython`.
"""
from __future__ import annotations
from .entry_points.run_ptpython import run
run()

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import ast
import collections.abc as collections_abc
import inspect
@ -44,8 +46,8 @@ class PythonCompleter(Completer):
def __init__(
self,
get_globals: Callable[[], Dict[str, Any]],
get_locals: Callable[[], Dict[str, Any]],
get_globals: Callable[[], dict[str, Any]],
get_locals: Callable[[], dict[str, Any]],
enable_dictionary_completion: Callable[[], bool],
) -> None:
super().__init__()
@ -58,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:
@ -74,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.
@ -85,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)
@ -189,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
@ -203,8 +204,8 @@ class JediCompleter(Completer):
def __init__(
self,
get_globals: Callable[[], Dict[str, Any]],
get_locals: Callable[[], Dict[str, Any]],
get_globals: Callable[[], dict[str, Any]],
get_locals: Callable[[], dict[str, Any]],
) -> None:
super().__init__()
@ -242,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
@ -303,8 +304,8 @@ class DictionaryCompleter(Completer):
def __init__(
self,
get_globals: Callable[[], Dict[str, Any]],
get_locals: Callable[[], Dict[str, Any]],
get_globals: Callable[[], dict[str, Any]],
get_locals: Callable[[], dict[str, Any]],
) -> None:
super().__init__()
@ -386,7 +387,7 @@ class DictionaryCompleter(Completer):
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.
@ -399,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.
@ -431,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
"""
@ -446,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.
@ -454,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),
@ -470,7 +469,7 @@ class DictionaryCompleter(Completer):
self,
document: Document,
complete_event: CompleteEvent,
temp_locals: Dict[str, Any],
temp_locals: dict[str, Any],
) -> Iterable[Completion]:
"""
Complete dictionary keys.
@ -478,6 +477,7 @@ class DictionaryCompleter(Completer):
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
@ -549,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.
@ -568,9 +568,9 @@ class DictionaryCompleter(Completer):
obj = getattr(result, name, None)
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
@ -581,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) -> Tuple[int, str]:
def sort_key(name: str) -> tuple[int, str]:
if name.startswith("__"):
return (2, name) # Double underscore comes latest.
if name.startswith("_"):
@ -599,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
@ -617,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
@ -653,7 +652,7 @@ except ImportError: # Python 2.
def _get_style_for_jedi_completion(
jedi_completion: "jedi.api.classes.Completion",
jedi_completion: jedi.api.classes.Completion,
) -> str:
"""
Return completion style to use for this name.

View file

@ -6,6 +6,8 @@ Note that the code in this file is Python 3 only. However, we
should make sure not to use Python 3-only syntax, because this
package should be installable in Python 2 as well!
"""
from __future__ import annotations
import asyncio
from typing import Any, Optional, TextIO, cast
@ -29,7 +31,7 @@ class ReplSSHServerSession(asyncssh.SSHServerSession):
"""
def __init__(
self, get_globals: _GetNamespace, get_locals: Optional[_GetNamespace] = None
self, get_globals: _GetNamespace, get_locals: _GetNamespace | None = None
) -> None:
self._chan: Any = None

View file

@ -1,4 +1,6 @@
#!/usr/bin/env python
from __future__ import annotations
import os
import sys
@ -58,7 +60,7 @@ def run(user_ns=None):
code = compile(f.read(), path, "exec")
exec(code, user_ns, user_ns)
else:
print("File not found: {}\n\n".format(path))
print(f"File not found: {path}\n\n")
sys.exit(1)
# Apply config file

View file

@ -21,6 +21,8 @@ environment variables:
PTPYTHON_CONFIG_HOME: a configuration directory to use
PYTHONSTARTUP: file executed on interactive startup (no default)
"""
from __future__ import annotations
import argparse
import os
import pathlib
@ -44,7 +46,7 @@ __all__ = ["create_parser", "get_config_and_history_file", "run"]
class _Parser(argparse.ArgumentParser):
def print_help(self, file: Optional[IO[str]] = None) -> None:
def print_help(self, file: IO[str] | None = None) -> None:
super().print_help()
print(
dedent(
@ -90,7 +92,7 @@ def create_parser() -> _Parser:
return parser
def get_config_and_history_file(namespace: argparse.Namespace) -> Tuple[str, str]:
def get_config_and_history_file(namespace: argparse.Namespace) -> tuple[str, str]:
"""
Check which config/history files to use, ensure that the directories for
these files exist, and return the config and history path.

View file

@ -7,6 +7,8 @@ way we don't block the UI of for instance ``turtle`` and other Tk libraries.
in readline. ``prompt-toolkit`` doesn't understand that input hook, but this
will fix it for Tk.)
"""
from __future__ import annotations
import sys
import time

View file

@ -1,3 +1,5 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from prompt_toolkit.filters import Filter
@ -9,7 +11,7 @@ __all__ = ["HasSignature", "ShowSidebar", "ShowSignature", "ShowDocstring"]
class PythonInputFilter(Filter):
def __init__(self, python_input: "PythonInput") -> None:
def __init__(self, python_input: PythonInput) -> None:
super().__init__()
self.python_input = python_input

View file

@ -4,6 +4,8 @@ Utility to easily select lines from the history and execute them again.
`create_history_application` creates an `Application` instance that runs will
run as a sub application of the Repl/PythonInput.
"""
from __future__ import annotations
from functools import partial
from typing import TYPE_CHECKING, Callable, List, Optional, Set
@ -128,7 +130,7 @@ class HistoryLayout:
application.
"""
def __init__(self, history: "PythonHistory") -> None:
def __init__(self, history: PythonHistory) -> None:
search_toolbar = SearchToolbar()
self.help_buffer_control = BufferControl(
@ -224,7 +226,7 @@ def _get_top_toolbar_fragments() -> StyleAndTextTuples:
return [("class:status-bar.title", "History browser - Insert from history")]
def _get_bottom_toolbar_fragments(history: "PythonHistory") -> StyleAndTextTuples:
def _get_bottom_toolbar_fragments(history: PythonHistory) -> StyleAndTextTuples:
python_input = history.python_input
@if_mousedown
@ -258,7 +260,7 @@ class HistoryMargin(Margin):
This displays a green bar for the selected entries.
"""
def __init__(self, history: "PythonHistory") -> None:
def __init__(self, history: PythonHistory) -> None:
self.history_buffer = history.history_buffer
self.history_mapping = history.history_mapping
@ -307,7 +309,7 @@ class ResultMargin(Margin):
The margin to be shown in the result pane.
"""
def __init__(self, history: "PythonHistory") -> None:
def __init__(self, history: PythonHistory) -> None:
self.history_mapping = history.history_mapping
self.history_buffer = history.history_buffer
@ -356,7 +358,7 @@ class GrayExistingText(Processor):
Turn the existing input, before and after the inserted code gray.
"""
def __init__(self, history_mapping: "HistoryMapping") -> None:
def __init__(self, history_mapping: HistoryMapping) -> None:
self.history_mapping = history_mapping
self._lines_before = len(
history_mapping.original_document.text_before_cursor.splitlines()
@ -384,7 +386,7 @@ class HistoryMapping:
def __init__(
self,
history: "PythonHistory",
history: PythonHistory,
python_history: History,
original_document: Document,
) -> None:
@ -393,11 +395,11 @@ class HistoryMapping:
self.original_document = original_document
self.lines_starting_new_entries = set()
self.selected_lines: Set[int] = set()
self.selected_lines: set[int] = set()
# Process history.
history_strings = python_history.get_strings()
history_lines: List[str] = []
history_lines: list[str] = []
for entry_nr, entry in list(enumerate(history_strings))[-HISTORY_COUNT:]:
self.lines_starting_new_entries.add(len(history_lines))
@ -419,7 +421,7 @@ class HistoryMapping:
else:
self.result_line_offset = 0
def get_new_document(self, cursor_pos: Optional[int] = None) -> Document:
def get_new_document(self, cursor_pos: int | None = None) -> Document:
"""
Create a `Document` instance that contains the resulting text.
"""
@ -449,7 +451,7 @@ class HistoryMapping:
b.set_document(self.get_new_document(b.cursor_position), bypass_readonly=True)
def _toggle_help(history: "PythonHistory") -> None:
def _toggle_help(history: PythonHistory) -> None:
"Display/hide help."
help_buffer_control = history.history_layout.help_buffer_control
@ -459,7 +461,7 @@ def _toggle_help(history: "PythonHistory") -> None:
history.app.layout.current_control = help_buffer_control
def _select_other_window(history: "PythonHistory") -> None:
def _select_other_window(history: PythonHistory) -> None:
"Toggle focus between left/right window."
current_buffer = history.app.current_buffer
layout = history.history_layout.layout
@ -472,8 +474,8 @@ def _select_other_window(history: "PythonHistory") -> None:
def create_key_bindings(
history: "PythonHistory",
python_input: "PythonInput",
history: PythonHistory,
python_input: PythonInput,
history_mapping: HistoryMapping,
) -> KeyBindings:
"""
@ -592,14 +594,12 @@ def create_key_bindings(
class PythonHistory:
def __init__(
self, python_input: "PythonInput", original_document: Document
) -> None:
def __init__(self, python_input: PythonInput, original_document: Document) -> None:
"""
Create an `Application` for the history screen.
This has to be run as a sub application of `python_input`.
When this application runs and returns, it retuns the selected lines.
When this application runs and returns, it returns the selected lines.
"""
self.python_input = python_input

View file

@ -8,6 +8,8 @@ also the power of for instance all the %-magic functions that IPython has to
offer.
"""
from __future__ import annotations
from typing import Iterable
from warnings import warn
@ -62,12 +64,12 @@ class IPythonPrompt(PromptStyle):
class IPythonValidator(PythonValidator):
def __init__(self, *args, **kwargs):
super(IPythonValidator, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
self.isp = IPythonInputSplitter()
def validate(self, document: Document) -> None:
document = Document(text=self.isp.transform_cell(document.text))
super(IPythonValidator, self).validate(document)
super().validate(document)
def create_ipython_grammar():

View file

@ -1,3 +1,5 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from prompt_toolkit.application import get_app
@ -47,7 +49,7 @@ def tab_should_insert_whitespace() -> bool:
return bool(b.text and (not before_cursor or before_cursor.isspace()))
def load_python_bindings(python_input: "PythonInput") -> KeyBindings:
def load_python_bindings(python_input: PythonInput) -> KeyBindings:
"""
Custom key bindings.
"""
@ -157,7 +159,7 @@ def load_python_bindings(python_input: "PythonInput") -> KeyBindings:
Behaviour of the Enter key.
Auto indent after newline/Enter.
(When not in Vi navigaton mode, and when multiline is enabled.)
(When not in Vi navigation mode, and when multiline is enabled.)
"""
b = event.current_buffer
empty_lines_required = python_input.accept_input_on_enter or 10000
@ -218,7 +220,7 @@ def load_python_bindings(python_input: "PythonInput") -> KeyBindings:
return bindings
def load_sidebar_bindings(python_input: "PythonInput") -> KeyBindings:
def load_sidebar_bindings(python_input: PythonInput) -> KeyBindings:
"""
Load bindings for the navigation in the sidebar.
"""
@ -273,7 +275,7 @@ def load_sidebar_bindings(python_input: "PythonInput") -> KeyBindings:
return bindings
def load_confirm_exit_bindings(python_input: "PythonInput") -> KeyBindings:
def load_confirm_exit_bindings(python_input: PythonInput) -> KeyBindings:
"""
Handle yes/no key presses when the exit confirmation is shown.
"""

View file

@ -1,6 +1,8 @@
"""
Creation of the `Layout` instance for the Python input/REPL.
"""
from __future__ import annotations
import platform
import sys
from enum import Enum
@ -78,26 +80,26 @@ class CompletionVisualisation(Enum):
TOOLBAR = "toolbar"
def show_completions_toolbar(python_input: "PythonInput") -> Condition:
def show_completions_toolbar(python_input: PythonInput) -> Condition:
return Condition(
lambda: python_input.completion_visualisation == CompletionVisualisation.TOOLBAR
)
def show_completions_menu(python_input: "PythonInput") -> Condition:
def show_completions_menu(python_input: PythonInput) -> Condition:
return Condition(
lambda: python_input.completion_visualisation == CompletionVisualisation.POP_UP
)
def show_multi_column_completions_menu(python_input: "PythonInput") -> Condition:
def show_multi_column_completions_menu(python_input: PythonInput) -> Condition:
return Condition(
lambda: python_input.completion_visualisation
== CompletionVisualisation.MULTI_COLUMN
)
def python_sidebar(python_input: "PythonInput") -> Window:
def python_sidebar(python_input: PythonInput) -> Window:
"""
Create the `Layout` for the sidebar with the configurable options.
"""
@ -105,7 +107,7 @@ def python_sidebar(python_input: "PythonInput") -> Window:
def get_text_fragments() -> StyleAndTextTuples:
tokens: StyleAndTextTuples = []
def append_category(category: "OptionCategory[Any]") -> None:
def append_category(category: OptionCategory[Any]) -> None:
tokens.extend(
[
("class:sidebar", " "),
@ -149,7 +151,7 @@ def python_sidebar(python_input: "PythonInput") -> Window:
append_category(category)
for option in category.options:
append(i, option.title, "%s" % option.get_current_value())
append(i, option.title, "%s" % (option.get_current_value(),))
i += 1
tokens.pop() # Remove last newline.
@ -172,7 +174,7 @@ def python_sidebar(python_input: "PythonInput") -> Window:
)
def python_sidebar_navigation(python_input: "PythonInput") -> Window:
def python_sidebar_navigation(python_input: PythonInput) -> Window:
"""
Create the `Layout` showing the navigation information for the sidebar.
"""
@ -198,7 +200,7 @@ def python_sidebar_navigation(python_input: "PythonInput") -> Window:
)
def python_sidebar_help(python_input: "PythonInput") -> Container:
def python_sidebar_help(python_input: PythonInput) -> Container:
"""
Create the `Layout` for the help text for the current item in the sidebar.
"""
@ -232,7 +234,7 @@ def python_sidebar_help(python_input: "PythonInput") -> Container:
)
def signature_toolbar(python_input: "PythonInput") -> Container:
def signature_toolbar(python_input: PythonInput) -> Container:
"""
Return the `Layout` for the signature.
"""
@ -318,7 +320,7 @@ class PythonPromptMargin(PromptMargin):
It shows something like "In [1]:".
"""
def __init__(self, python_input: "PythonInput") -> None:
def __init__(self, python_input: PythonInput) -> None:
self.python_input = python_input
def get_prompt_style() -> PromptStyle:
@ -339,7 +341,7 @@ class PythonPromptMargin(PromptMargin):
super().__init__(get_prompt, get_continuation)
def status_bar(python_input: "PythonInput") -> Container:
def status_bar(python_input: PythonInput) -> Container:
"""
Create the `Layout` for the status bar.
"""
@ -412,7 +414,7 @@ def status_bar(python_input: "PythonInput") -> Container:
)
def get_inputmode_fragments(python_input: "PythonInput") -> StyleAndTextTuples:
def get_inputmode_fragments(python_input: PythonInput) -> StyleAndTextTuples:
"""
Return current input mode as a list of (token, text) tuples for use in a
toolbar.
@ -440,7 +442,7 @@ def get_inputmode_fragments(python_input: "PythonInput") -> StyleAndTextTuples:
recording_register = app.vi_state.recording_register
if recording_register:
append((token, " "))
append((token + " class:record", "RECORD({})".format(recording_register)))
append((token + " class:record", f"RECORD({recording_register})"))
append((token, " - "))
if app.current_buffer.selection_state is not None:
@ -473,7 +475,7 @@ def get_inputmode_fragments(python_input: "PythonInput") -> StyleAndTextTuples:
return result
def show_sidebar_button_info(python_input: "PythonInput") -> Container:
def show_sidebar_button_info(python_input: PythonInput) -> Container:
"""
Create `Layout` for the information in the right-bottom corner.
(The right part of the status bar.)
@ -519,7 +521,7 @@ def show_sidebar_button_info(python_input: "PythonInput") -> Container:
def create_exit_confirmation(
python_input: "PythonInput", style: str = "class:exit-confirmation"
python_input: PythonInput, style: str = "class:exit-confirmation"
) -> Container:
"""
Create `Layout` for the exit message.
@ -543,7 +545,7 @@ def create_exit_confirmation(
)
def meta_enter_message(python_input: "PythonInput") -> Container:
def meta_enter_message(python_input: PythonInput) -> Container:
"""
Create the `Layout` for the 'Meta+Enter` message.
"""
@ -575,15 +577,15 @@ def meta_enter_message(python_input: "PythonInput") -> Container:
class PtPythonLayout:
def __init__(
self,
python_input: "PythonInput",
python_input: PythonInput,
lexer: Lexer,
extra_body: Optional[AnyContainer] = None,
extra_toolbars: Optional[List[AnyContainer]] = None,
extra_buffer_processors: Optional[List[Processor]] = None,
input_buffer_height: Optional[AnyDimension] = None,
extra_body: AnyContainer | None = None,
extra_toolbars: list[AnyContainer] | None = None,
extra_buffer_processors: list[Processor] | None = None,
input_buffer_height: AnyDimension | None = None,
) -> None:
D = Dimension
extra_body_list: List[AnyContainer] = [extra_body] if extra_body else []
extra_body_list: list[AnyContainer] = [extra_body] if extra_body else []
extra_toolbars = extra_toolbars or []
input_buffer_height = input_buffer_height or D(min=6)
@ -591,7 +593,7 @@ class PtPythonLayout:
search_toolbar = SearchToolbar(python_input.search_buffer)
def create_python_input_window() -> Window:
def menu_position() -> Optional[int]:
def menu_position() -> int | None:
"""
When there is no autocompletion menu to be shown, and we have a
signature, set the pop-up position at `bracket_start`.

View file

@ -1,3 +1,5 @@
from __future__ import annotations
from typing import Callable, Optional
from prompt_toolkit.document import Document
@ -17,7 +19,7 @@ class PtpythonLexer(Lexer):
use a Python 3 lexer.
"""
def __init__(self, python_lexer: Optional[Lexer] = None) -> None:
def __init__(self, python_lexer: Lexer | None = None) -> None:
self.python_lexer = python_lexer or PygmentsLexer(PythonLexer)
self.system_lexer = PygmentsLexer(BashLexer)

View file

@ -1,3 +1,5 @@
from __future__ import annotations
from abc import ABCMeta, abstractmethod
from typing import TYPE_CHECKING
@ -40,7 +42,7 @@ class IPythonPrompt(PromptStyle):
A prompt resembling the IPython prompt.
"""
def __init__(self, python_input: "PythonInput") -> None:
def __init__(self, python_input: PythonInput) -> None:
self.python_input = python_input
def in_prompt(self) -> AnyFormattedText:

View file

@ -2,7 +2,7 @@
Application for reading Python input.
This can be used for creation of Python REPLs.
"""
import __future__
from __future__ import annotations
from asyncio import get_event_loop
from functools import partial
@ -34,6 +34,12 @@ from prompt_toolkit.completion import (
ThreadedCompleter,
merge_completers,
)
from prompt_toolkit.cursor_shapes import (
AnyCursorShapeConfig,
CursorShape,
DynamicCursorShapeConfig,
ModalCursorShapeConfig,
)
from prompt_toolkit.document import Document
from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
from prompt_toolkit.filters import Condition
@ -84,6 +90,11 @@ from .style import generate_style, get_all_code_styles, get_all_ui_styles
from .utils import unindent_code
from .validator import PythonValidator
# Isort introduces a SyntaxError, if we'd write `import __future__`.
# https://github.com/PyCQA/isort/issues/2100
__future__ = __import__("__future__")
__all__ = ["PythonInput"]
@ -101,7 +112,7 @@ _T = TypeVar("_T", bound="_SupportsLessThan")
class OptionCategory(Generic[_T]):
def __init__(self, title: str, options: List["Option[_T]"]) -> None:
def __init__(self, title: str, options: list[Option[_T]]) -> None:
self.title = title
self.options = options
@ -194,26 +205,25 @@ class PythonInput:
def __init__(
self,
get_globals: Optional[_GetNamespace] = None,
get_locals: Optional[_GetNamespace] = None,
history_filename: Optional[str] = None,
get_globals: _GetNamespace | None = None,
get_locals: _GetNamespace | None = None,
history_filename: str | None = None,
vi_mode: bool = False,
color_depth: Optional[ColorDepth] = None,
color_depth: ColorDepth | None = None,
# Input/output.
input: Optional[Input] = None,
output: Optional[Output] = None,
input: Input | None = None,
output: Output | None = None,
# For internal use.
extra_key_bindings: Optional[KeyBindings] = None,
extra_key_bindings: KeyBindings | None = None,
create_app: bool = True,
_completer: Optional[Completer] = None,
_validator: Optional[Validator] = None,
_lexer: Optional[Lexer] = None,
_completer: Completer | None = None,
_validator: Validator | None = None,
_lexer: Lexer | None = None,
_extra_buffer_processors=None,
_extra_layout_body: Optional[AnyContainer] = None,
_extra_layout_body: AnyContainer | None = None,
_extra_toolbars=None,
_input_buffer_height=None,
) -> None:
self.get_globals: _GetNamespace = get_globals or (lambda: {})
self.get_locals: _GetNamespace = get_locals or self.get_globals
@ -310,7 +320,7 @@ class PythonInput:
self.show_exit_confirmation: bool = False
# The title to be displayed in the terminal. (None or string.)
self.terminal_title: Optional[str] = None
self.terminal_title: str | None = None
self.exit_message: str = "Do you really want to exit?"
self.insert_blank_line_after_output: bool = True # (For the REPL.)
@ -321,11 +331,23 @@ class PythonInput:
self.search_buffer: Buffer = Buffer()
self.docstring_buffer: Buffer = Buffer(read_only=True)
# Cursor shapes.
self.cursor_shape_config = "Block"
self.all_cursor_shape_configs: Dict[str, AnyCursorShapeConfig] = {
"Block": CursorShape.BLOCK,
"Underline": CursorShape.UNDERLINE,
"Beam": CursorShape.BEAM,
"Modal (vi)": ModalCursorShapeConfig(),
"Blink block": CursorShape.BLINKING_BLOCK,
"Blink under": CursorShape.BLINKING_UNDERLINE,
"Blink beam": CursorShape.BLINKING_BEAM,
}
# Tokens to be shown at the prompt.
self.prompt_style: str = "classic" # The currently active style.
# Styles selectable from the menu.
self.all_prompt_styles: Dict[str, PromptStyle] = {
self.all_prompt_styles: dict[str, PromptStyle] = {
"ipython": IPythonPrompt(self),
"classic": ClassicPrompt(),
}
@ -339,7 +361,7 @@ class PythonInput:
].out_prompt()
#: Load styles.
self.code_styles: Dict[str, BaseStyle] = get_all_code_styles()
self.code_styles: dict[str, BaseStyle] = get_all_code_styles()
self.ui_styles = get_all_ui_styles()
self._current_code_style_name: str = "default"
self._current_ui_style_name: str = "default"
@ -361,7 +383,7 @@ class PythonInput:
self.current_statement_index: int = 1
# Code signatures. (This is set asynchronously after a timeout.)
self.signatures: List[Signature] = []
self.signatures: list[Signature] = []
# Boolean indicating whether we have a signatures thread running.
# (Never run more than one at the same time.)
@ -400,9 +422,7 @@ class PythonInput:
# Create an app if requested. If not, the global get_app() is returned
# for self.app via property getter.
if create_app:
self._app: Optional[Application[str]] = self._create_application(
input, output
)
self._app: Application[str] | None = self._create_application(input, output)
# Setting vi_mode will not work unless the prompt_toolkit
# application has been created.
if vi_mode:
@ -528,7 +548,7 @@ class PythonInput:
self.ui_styles[self._current_ui_style_name],
)
def _create_options(self) -> List[OptionCategory[Any]]:
def _create_options(self) -> list[OptionCategory[Any]]:
"""
Create a list of `Option` instances for the options sidebar.
"""
@ -547,14 +567,14 @@ class PythonInput:
title: str,
description: str,
field_name: str,
values: Tuple[str, str] = ("off", "on"),
values: tuple[str, str] = ("off", "on"),
) -> Option[str]:
"Create Simple on/of option."
def get_current_value() -> str:
return values[bool(getattr(self, field_name))]
def get_values() -> Dict[str, Callable[[], bool]]:
def get_values() -> dict[str, Callable[[], bool]]:
return {
values[1]: lambda: enable(field_name),
values[0]: lambda: disable(field_name),
@ -582,6 +602,16 @@ class PythonInput:
"Vi": lambda: enable("vi_mode"),
},
),
Option(
title="Cursor shape",
description="Change the cursor style, possibly according "
"to the Vi input mode.",
get_current_value=lambda: self.cursor_shape_config,
get_values=lambda: dict(
(s, partial(enable, "cursor_shape_config", s))
for s in self.all_cursor_shape_configs
),
),
simple_option(
title="Paste mode",
description="When enabled, don't indent automatically.",
@ -731,10 +761,10 @@ class PythonInput:
title="Prompt",
description="Visualisation of the prompt. ('>>>' or 'In [1]:')",
get_current_value=lambda: self.prompt_style,
get_values=lambda: dict(
(s, partial(enable, "prompt_style", s))
get_values=lambda: {
s: partial(enable, "prompt_style", s)
for s in self.all_prompt_styles
),
},
),
simple_option(
title="Blank line after input",
@ -826,10 +856,10 @@ class PythonInput:
title="User interface",
description="Color scheme to use for the user interface.",
get_current_value=lambda: self._current_ui_style_name,
get_values=lambda: dict(
(name, partial(self.use_ui_colorscheme, name))
get_values=lambda: {
name: partial(self.use_ui_colorscheme, name)
for name in self.ui_styles
),
},
),
Option(
title="Color depth",
@ -863,7 +893,7 @@ class PythonInput:
]
def _create_application(
self, input: Optional[Input], output: Optional[Output]
self, input: Input | None, output: Output | None
) -> Application[str]:
"""
Create an `Application` instance.
@ -894,6 +924,9 @@ class PythonInput:
style_transformation=self.style_transformation,
include_default_pygments_style=False,
reverse_vi_search_direction=True,
cursor=DynamicCursorShapeConfig(
lambda: self.all_cursor_shape_configs[self.cursor_shape_config]
),
input=input,
output=output,
)
@ -953,7 +986,7 @@ class PythonInput:
in another thread, get the signature of the current code.
"""
def get_signatures_in_executor(document: Document) -> List[Signature]:
def get_signatures_in_executor(document: Document) -> list[Signature]:
# First, get signatures from Jedi. If we didn't found any and if
# "dictionary completion" (eval-based completion) is enabled, then
# get signatures using eval.
@ -1043,6 +1076,7 @@ class PythonInput:
This can raise EOFError, when Control-D is pressed.
"""
# Capture the current input_mode in order to restore it after reset,
# for ViState.reset() sets it to InputMode.INSERT unconditionally and
# doesn't accept any arguments.

View file

@ -7,6 +7,8 @@ Utility for creating a Python repl.
embed(globals(), locals(), vi_mode=False)
"""
from __future__ import annotations
import asyncio
import builtins
import os
@ -53,7 +55,7 @@ except ImportError:
__all__ = ["PythonRepl", "enable_deprecation_warnings", "run_config", "embed"]
def _get_coroutine_flag() -> Optional[int]:
def _get_coroutine_flag() -> int | None:
for k, v in COMPILER_FLAG_NAMES.items():
if v == "COROUTINE":
return k
@ -62,7 +64,7 @@ def _get_coroutine_flag() -> Optional[int]:
return None
COROUTINE_FLAG: Optional[int] = _get_coroutine_flag()
COROUTINE_FLAG: int | None = _get_coroutine_flag()
def _has_coroutine_flag(code: types.CodeType) -> bool:
@ -89,14 +91,15 @@ class PythonRepl(PythonInput):
exec(code, self.get_globals(), self.get_locals())
else:
output = self.app.output
output.write("WARNING | File not found: {}\n\n".format(path))
output.write(f"WARNING | File not found: {path}\n\n")
def run_and_show_expression(self, expression: str) -> None:
try:
# Eval.
try:
result = self.eval(expression)
except KeyboardInterrupt: # KeyboardInterrupt doesn't inherit from Exception.
except KeyboardInterrupt:
# KeyboardInterrupt doesn't inherit from Exception.
raise
except SystemExit:
raise
@ -299,7 +302,7 @@ class PythonRepl(PythonInput):
return None
def _store_eval_result(self, result: object) -> None:
locals: Dict[str, Any] = self.get_locals()
locals: dict[str, Any] = self.get_locals()
locals["_"] = locals["_%i" % self.current_statement_index] = result
def get_compiler_flags(self) -> int:
@ -402,7 +405,7 @@ class PythonRepl(PythonInput):
def show_result(self, result: object) -> None:
"""
Show __repr__ for an `eval` result and print to ouptut.
Show __repr__ for an `eval` result and print to output.
"""
formatted_text_output = self._format_result_output(result)
@ -523,7 +526,7 @@ class PythonRepl(PythonInput):
flush_page()
def create_pager_prompt(self) -> PromptSession["PagerResult"]:
def create_pager_prompt(self) -> PromptSession[PagerResult]:
"""
Create pager --MORE-- prompt.
"""
@ -572,8 +575,6 @@ class PythonRepl(PythonInput):
include_default_pygments_style=False,
output=output,
)
output.write("%s\n" % e)
output.flush()
def _handle_keyboard_interrupt(self, e: KeyboardInterrupt) -> None:
@ -652,7 +653,7 @@ def run_config(
# Run the config file in an empty namespace.
try:
namespace: Dict[str, Any] = {}
namespace: dict[str, Any] = {}
with open(config_file, "rb") as f:
code = compile(f.read(), config_file, "exec")
@ -671,10 +672,10 @@ def run_config(
def embed(
globals=None,
locals=None,
configure: Optional[Callable[[PythonRepl], None]] = None,
configure: Callable[[PythonRepl], None] | None = None,
vi_mode: bool = False,
history_filename: Optional[str] = None,
title: Optional[str] = None,
history_filename: str | None = None,
title: str | None = None,
startup_paths=None,
patch_stdout: bool = False,
return_asyncio_coroutine: bool = False,

View file

@ -5,6 +5,8 @@ editing.
Either with the Jedi library, or using `inspect.signature` if Jedi fails and we
can use `eval()` to evaluate the function object.
"""
from __future__ import annotations
import inspect
from inspect import Signature as InspectSignature
from inspect import _ParameterKind as ParameterKind
@ -25,8 +27,8 @@ class Parameter:
def __init__(
self,
name: str,
annotation: Optional[str],
default: Optional[str],
annotation: str | None,
default: str | None,
kind: ParameterKind,
) -> None:
self.name = name
@ -66,9 +68,9 @@ class Signature:
name: str,
docstring: str,
parameters: Sequence[Parameter],
index: Optional[int] = None,
index: int | None = None,
returns: str = "",
bracket_start: Tuple[int, int] = (0, 0),
bracket_start: tuple[int, int] = (0, 0),
) -> None:
self.name = name
self.docstring = docstring
@ -84,7 +86,7 @@ class Signature:
docstring: str,
signature: InspectSignature,
index: int,
) -> "Signature":
) -> Signature:
parameters = []
def get_annotation_name(annotation: object) -> str:
@ -123,9 +125,7 @@ class Signature:
)
@classmethod
def from_jedi_signature(
cls, signature: "jedi.api.classes.Signature"
) -> "Signature":
def from_jedi_signature(cls, signature: jedi.api.classes.Signature) -> Signature:
parameters = []
for p in signature.params:
@ -160,8 +160,8 @@ class Signature:
def get_signatures_using_jedi(
document: Document, locals: Dict[str, Any], globals: Dict[str, Any]
) -> List[Signature]:
document: Document, locals: dict[str, Any], globals: dict[str, Any]
) -> list[Signature]:
script = get_jedi_script_from_document(document, locals, globals)
# Show signatures in help text.
@ -195,8 +195,8 @@ def get_signatures_using_jedi(
def get_signatures_using_eval(
document: Document, locals: Dict[str, Any], globals: Dict[str, Any]
) -> List[Signature]:
document: Document, locals: dict[str, Any], globals: dict[str, Any]
) -> list[Signature]:
"""
Look for the signature of the function before the cursor position without
use of Jedi. This uses a similar approach as the `DictionaryCompleter` of

View file

@ -1,3 +1,5 @@
from __future__ import annotations
from typing import Dict
from prompt_toolkit.styles import BaseStyle, Style, merge_styles
@ -8,11 +10,11 @@ from pygments.styles import get_all_styles, get_style_by_name
__all__ = ["get_all_code_styles", "get_all_ui_styles", "generate_style"]
def get_all_code_styles() -> Dict[str, BaseStyle]:
def get_all_code_styles() -> dict[str, BaseStyle]:
"""
Return a mapping from style names to their classes.
"""
result: Dict[str, BaseStyle] = {
result: dict[str, BaseStyle] = {
name: style_from_pygments_cls(get_style_by_name(name))
for name in get_all_styles()
}
@ -20,7 +22,7 @@ def get_all_code_styles() -> Dict[str, BaseStyle]:
return result
def get_all_ui_styles() -> Dict[str, BaseStyle]:
def get_all_ui_styles() -> dict[str, BaseStyle]:
"""
Return a dict mapping {ui_style_name -> style_dict}.
"""

View file

@ -1,6 +1,8 @@
"""
For internal use only.
"""
from __future__ import annotations
import re
from typing import (
TYPE_CHECKING,
@ -65,8 +67,8 @@ def has_unclosed_brackets(text: str) -> bool:
def get_jedi_script_from_document(
document: Document, locals: Dict[str, Any], globals: Dict[str, Any]
) -> "Interpreter":
document: Document, locals: dict[str, Any], globals: dict[str, Any]
) -> Interpreter:
import jedi # We keep this import in-line, to improve start-up time.
# Importing Jedi is 'slow'.
@ -154,7 +156,7 @@ def if_mousedown(handler: _T) -> _T:
by the Window.)
"""
def handle_if_mouse_down(mouse_event: MouseEvent) -> "NotImplementedOrNone":
def handle_if_mouse_down(mouse_event: MouseEvent) -> NotImplementedOrNone:
if mouse_event.event_type == MouseEventType.MOUSE_DOWN:
return handler(mouse_event)
else:

View file

@ -1,3 +1,5 @@
from __future__ import annotations
from typing import Callable, Optional
from prompt_toolkit.document import Document
@ -16,7 +18,7 @@ class PythonValidator(Validator):
active compiler flags.
"""
def __init__(self, get_compiler_flags: Optional[Callable[[], int]] = None) -> None:
def __init__(self, get_compiler_flags: Callable[[], int] | None = None) -> None:
self.get_compiler_flags = get_compiler_flags
def validate(self, document: Document) -> None:

View file

@ -11,7 +11,7 @@ with open(os.path.join(os.path.dirname(__file__), "README.rst")) as f:
setup(
name="ptpython",
author="Jonathan Slenders",
version="3.0.22",
version="3.0.23",
url="https://github.com/prompt-toolkit/ptpython",
description="Python REPL build on top of prompt_toolkit",
long_description=long_description,
@ -21,14 +21,14 @@ setup(
"appdirs",
"importlib_metadata;python_version<'3.8'",
"jedi>=0.16.0",
# Use prompt_toolkit 3.0.18, because of the `in_thread` option.
"prompt_toolkit>=3.0.18,<3.1.0",
# Use prompt_toolkit 3.0.28, because of cursor shape support.
"prompt_toolkit>=3.0.28,<3.1.0",
"pygments",
],
python_requires=">=3.6",
python_requires=">=3.7",
classifiers=[
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3 :: Only",

View file

@ -1,4 +1,6 @@
#!/usr/bin/env python
from __future__ import annotations
import unittest
import ptpython.completer