1
0
Fork 0

Adding upstream version 3.0.23.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-02-09 18:29:58 +01:00
parent 2f515d8d05
commit 6444a924bf
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 runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
python-version: [3.7, 3.8, 3.9, "3.10"] python-version: [3.7, 3.8, 3.9, "3.10", "3.11"]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2

View file

@ -1,6 +1,17 @@
CHANGELOG 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 3.0.22: 2022-12-06
------------------ ------------------

View file

@ -213,7 +213,7 @@ This is also available for embedding:
.. code:: python .. code:: python
from ptpython.ipython.repl import embed from ptpython.ipython import embed
embed(globals(), locals()) 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 When an awaitable embed is used, for embedding in a coroutine, but having the
event loop continue: event loop continue:
* We run the input method from the blocking embed in an asyncio executor * 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. * The "eval" happens again in the main thread.
* "print" is also similar, except that the pager code (if used) runs in an * "print" is also similar, except that the pager code (if used) runs in an
executor too. executor too.

View file

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

View file

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

View file

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

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import ast import ast
import collections.abc as collections_abc import collections.abc as collections_abc
import inspect import inspect
@ -44,8 +46,8 @@ class PythonCompleter(Completer):
def __init__( def __init__(
self, self,
get_globals: Callable[[], Dict[str, Any]], get_globals: Callable[[], dict[str, Any]],
get_locals: Callable[[], Dict[str, Any]], get_locals: Callable[[], dict[str, Any]],
enable_dictionary_completion: Callable[[], bool], enable_dictionary_completion: Callable[[], bool],
) -> None: ) -> None:
super().__init__() super().__init__()
@ -58,8 +60,8 @@ class PythonCompleter(Completer):
self._jedi_completer = JediCompleter(get_globals, get_locals) self._jedi_completer = JediCompleter(get_globals, get_locals)
self._dictionary_completer = DictionaryCompleter(get_globals, get_locals) self._dictionary_completer = DictionaryCompleter(get_globals, get_locals)
self._path_completer_cache: Optional[GrammarCompleter] = None self._path_completer_cache: GrammarCompleter | None = None
self._path_completer_grammar_cache: Optional["_CompiledGrammar"] = None self._path_completer_grammar_cache: _CompiledGrammar | None = None
@property @property
def _path_completer(self) -> GrammarCompleter: def _path_completer(self) -> GrammarCompleter:
@ -74,7 +76,7 @@ class PythonCompleter(Completer):
return self._path_completer_cache return self._path_completer_cache
@property @property
def _path_completer_grammar(self) -> "_CompiledGrammar": def _path_completer_grammar(self) -> _CompiledGrammar:
""" """
Return the grammar for matching paths inside strings inside Python Return the grammar for matching paths inside strings inside Python
code. code.
@ -85,7 +87,7 @@ class PythonCompleter(Completer):
self._path_completer_grammar_cache = self._create_path_completer_grammar() self._path_completer_grammar_cache = self._create_path_completer_grammar()
return self._path_completer_grammar_cache 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: def unwrapper(text: str) -> str:
return re.sub(r"\\(.)", r"\1", text) 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 we are inside a string, Don't do Jedi completion.
if not self._path_completer_grammar.match(document.text_before_cursor): if not self._path_completer_grammar.match(document.text_before_cursor):
# Do Jedi Python completions. # Do Jedi Python completions.
yield from self._jedi_completer.get_completions( yield from self._jedi_completer.get_completions(
document, complete_event document, complete_event
@ -203,8 +204,8 @@ class JediCompleter(Completer):
def __init__( def __init__(
self, self,
get_globals: Callable[[], Dict[str, Any]], get_globals: Callable[[], dict[str, Any]],
get_locals: Callable[[], Dict[str, Any]], get_locals: Callable[[], dict[str, Any]],
) -> None: ) -> None:
super().__init__() super().__init__()
@ -242,7 +243,7 @@ class JediCompleter(Completer):
# Jedi issue: "KeyError: u'a_lambda'." # Jedi issue: "KeyError: u'a_lambda'."
# https://github.com/jonathanslenders/ptpython/issues/89 # https://github.com/jonathanslenders/ptpython/issues/89
pass pass
except IOError: except OSError:
# Jedi issue: "IOError: No such file or directory." # Jedi issue: "IOError: No such file or directory."
# https://github.com/jonathanslenders/ptpython/issues/71 # https://github.com/jonathanslenders/ptpython/issues/71
pass pass
@ -303,8 +304,8 @@ class DictionaryCompleter(Completer):
def __init__( def __init__(
self, self,
get_globals: Callable[[], Dict[str, Any]], get_globals: Callable[[], dict[str, Any]],
get_locals: Callable[[], Dict[str, Any]], get_locals: Callable[[], dict[str, Any]],
) -> None: ) -> None:
super().__init__() super().__init__()
@ -386,7 +387,7 @@ class DictionaryCompleter(Completer):
re.VERBOSE, 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. Do lookup of `object_var` in the context.
`temp_locals` is a dictionary, used for the locals. `temp_locals` is a dictionary, used for the locals.
@ -399,7 +400,6 @@ class DictionaryCompleter(Completer):
def get_completions( def get_completions(
self, document: Document, complete_event: CompleteEvent self, document: Document, complete_event: CompleteEvent
) -> Iterable[Completion]: ) -> Iterable[Completion]:
# First, find all for-loops, and assign the first item of the # First, find all for-loops, and assign the first item of the
# collections they're iterating to the iterator variable, so that we # collections they're iterating to the iterator variable, so that we
# can provide code completion on the iterators. # can provide code completion on the iterators.
@ -431,7 +431,7 @@ class DictionaryCompleter(Completer):
except BaseException: except BaseException:
raise ReprFailedError 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 Evaluate
""" """
@ -446,7 +446,7 @@ class DictionaryCompleter(Completer):
self, self,
document: Document, document: Document,
complete_event: CompleteEvent, complete_event: CompleteEvent,
temp_locals: Dict[str, Any], temp_locals: dict[str, Any],
) -> Iterable[Completion]: ) -> Iterable[Completion]:
""" """
Complete the [ or . operator after an object. Complete the [ or . operator after an object.
@ -454,7 +454,6 @@ class DictionaryCompleter(Completer):
result = self.eval_expression(document, temp_locals) result = self.eval_expression(document, temp_locals)
if result is not None: if result is not None:
if isinstance( if isinstance(
result, result,
(list, tuple, dict, collections_abc.Mapping, collections_abc.Sequence), (list, tuple, dict, collections_abc.Mapping, collections_abc.Sequence),
@ -470,7 +469,7 @@ class DictionaryCompleter(Completer):
self, self,
document: Document, document: Document,
complete_event: CompleteEvent, complete_event: CompleteEvent,
temp_locals: Dict[str, Any], temp_locals: dict[str, Any],
) -> Iterable[Completion]: ) -> Iterable[Completion]:
""" """
Complete dictionary keys. Complete dictionary keys.
@ -478,6 +477,7 @@ class DictionaryCompleter(Completer):
def meta_repr(value: object) -> Callable[[], str]: def meta_repr(value: object) -> Callable[[], str]:
"Abbreviate meta text, make sure it fits on one line." "Abbreviate meta text, make sure it fits on one line."
# We return a function, so that it gets computed when it's needed. # We return a function, so that it gets computed when it's needed.
# When there are many completions, that improves the performance # When there are many completions, that improves the performance
# quite a bit (for the multi-column completion menu, we only need # quite a bit (for the multi-column completion menu, we only need
@ -549,7 +549,7 @@ class DictionaryCompleter(Completer):
self, self,
document: Document, document: Document,
complete_event: CompleteEvent, complete_event: CompleteEvent,
temp_locals: Dict[str, Any], temp_locals: dict[str, Any],
) -> Iterable[Completion]: ) -> Iterable[Completion]:
""" """
Complete attribute names. Complete attribute names.
@ -568,9 +568,9 @@ class DictionaryCompleter(Completer):
obj = getattr(result, name, None) obj = getattr(result, name, None)
if inspect.isfunction(obj) or inspect.ismethod(obj): if inspect.isfunction(obj) or inspect.ismethod(obj):
return "()" return "()"
if isinstance(obj, dict): if isinstance(obj, collections_abc.Mapping):
return "{}" return "{}"
if isinstance(obj, (list, tuple)): if isinstance(obj, collections_abc.Sequence):
return "[]" return "[]"
except: except:
pass pass
@ -581,13 +581,13 @@ class DictionaryCompleter(Completer):
suffix = get_suffix(name) suffix = get_suffix(name)
yield Completion(name, -len(attr_name), display=name + suffix) 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 Sort attribute names alphabetically, but move the double underscore and
underscore names to the end. underscore names to the end.
""" """
def sort_key(name: str) -> Tuple[int, str]: def sort_key(name: str) -> tuple[int, str]:
if name.startswith("__"): if name.startswith("__"):
return (2, name) # Double underscore comes latest. return (2, name) # Double underscore comes latest.
if name.startswith("_"): if name.startswith("_"):
@ -599,7 +599,7 @@ class DictionaryCompleter(Completer):
class HidePrivateCompleter(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. not public fields are shown.
(The reason this is implemented as a `Completer` wrapper is because this (The reason this is implemented as a `Completer` wrapper is because this
@ -617,7 +617,6 @@ class HidePrivateCompleter(Completer):
def get_completions( def get_completions(
self, document: Document, complete_event: CompleteEvent self, document: Document, complete_event: CompleteEvent
) -> Iterable[Completion]: ) -> Iterable[Completion]:
completions = list(self.completer.get_completions(document, complete_event)) completions = list(self.completer.get_completions(document, complete_event))
complete_private_attributes = self.complete_private_attributes() complete_private_attributes = self.complete_private_attributes()
hide_private = False hide_private = False
@ -653,7 +652,7 @@ except ImportError: # Python 2.
def _get_style_for_jedi_completion( def _get_style_for_jedi_completion(
jedi_completion: "jedi.api.classes.Completion", jedi_completion: jedi.api.classes.Completion,
) -> str: ) -> str:
""" """
Return completion style to use for this name. 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 should make sure not to use Python 3-only syntax, because this
package should be installable in Python 2 as well! package should be installable in Python 2 as well!
""" """
from __future__ import annotations
import asyncio import asyncio
from typing import Any, Optional, TextIO, cast from typing import Any, Optional, TextIO, cast
@ -29,7 +31,7 @@ class ReplSSHServerSession(asyncssh.SSHServerSession):
""" """
def __init__( def __init__(
self, get_globals: _GetNamespace, get_locals: Optional[_GetNamespace] = None self, get_globals: _GetNamespace, get_locals: _GetNamespace | None = None
) -> None: ) -> None:
self._chan: Any = None self._chan: Any = None

View file

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

View file

@ -21,6 +21,8 @@ environment variables:
PTPYTHON_CONFIG_HOME: a configuration directory to use PTPYTHON_CONFIG_HOME: a configuration directory to use
PYTHONSTARTUP: file executed on interactive startup (no default) PYTHONSTARTUP: file executed on interactive startup (no default)
""" """
from __future__ import annotations
import argparse import argparse
import os import os
import pathlib import pathlib
@ -44,7 +46,7 @@ __all__ = ["create_parser", "get_config_and_history_file", "run"]
class _Parser(argparse.ArgumentParser): 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() super().print_help()
print( print(
dedent( dedent(
@ -90,7 +92,7 @@ def create_parser() -> _Parser:
return 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 Check which config/history files to use, ensure that the directories for
these files exist, and return the config and history path. 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 in readline. ``prompt-toolkit`` doesn't understand that input hook, but this
will fix it for Tk.) will fix it for Tk.)
""" """
from __future__ import annotations
import sys import sys
import time import time

View file

@ -1,3 +1,5 @@
from __future__ import annotations
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from prompt_toolkit.filters import Filter from prompt_toolkit.filters import Filter
@ -9,7 +11,7 @@ __all__ = ["HasSignature", "ShowSidebar", "ShowSignature", "ShowDocstring"]
class PythonInputFilter(Filter): class PythonInputFilter(Filter):
def __init__(self, python_input: "PythonInput") -> None: def __init__(self, python_input: PythonInput) -> None:
super().__init__() super().__init__()
self.python_input = python_input 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 `create_history_application` creates an `Application` instance that runs will
run as a sub application of the Repl/PythonInput. run as a sub application of the Repl/PythonInput.
""" """
from __future__ import annotations
from functools import partial from functools import partial
from typing import TYPE_CHECKING, Callable, List, Optional, Set from typing import TYPE_CHECKING, Callable, List, Optional, Set
@ -128,7 +130,7 @@ class HistoryLayout:
application. application.
""" """
def __init__(self, history: "PythonHistory") -> None: def __init__(self, history: PythonHistory) -> None:
search_toolbar = SearchToolbar() search_toolbar = SearchToolbar()
self.help_buffer_control = BufferControl( 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")] 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 python_input = history.python_input
@if_mousedown @if_mousedown
@ -258,7 +260,7 @@ class HistoryMargin(Margin):
This displays a green bar for the selected entries. 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_buffer = history.history_buffer
self.history_mapping = history.history_mapping self.history_mapping = history.history_mapping
@ -307,7 +309,7 @@ class ResultMargin(Margin):
The margin to be shown in the result pane. 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_mapping = history.history_mapping
self.history_buffer = history.history_buffer self.history_buffer = history.history_buffer
@ -356,7 +358,7 @@ class GrayExistingText(Processor):
Turn the existing input, before and after the inserted code gray. 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.history_mapping = history_mapping
self._lines_before = len( self._lines_before = len(
history_mapping.original_document.text_before_cursor.splitlines() history_mapping.original_document.text_before_cursor.splitlines()
@ -384,7 +386,7 @@ class HistoryMapping:
def __init__( def __init__(
self, self,
history: "PythonHistory", history: PythonHistory,
python_history: History, python_history: History,
original_document: Document, original_document: Document,
) -> None: ) -> None:
@ -393,11 +395,11 @@ class HistoryMapping:
self.original_document = original_document self.original_document = original_document
self.lines_starting_new_entries = set() self.lines_starting_new_entries = set()
self.selected_lines: Set[int] = set() self.selected_lines: set[int] = set()
# Process history. # Process history.
history_strings = python_history.get_strings() 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:]: for entry_nr, entry in list(enumerate(history_strings))[-HISTORY_COUNT:]:
self.lines_starting_new_entries.add(len(history_lines)) self.lines_starting_new_entries.add(len(history_lines))
@ -419,7 +421,7 @@ class HistoryMapping:
else: else:
self.result_line_offset = 0 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. 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) 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." "Display/hide help."
help_buffer_control = history.history_layout.help_buffer_control 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 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." "Toggle focus between left/right window."
current_buffer = history.app.current_buffer current_buffer = history.app.current_buffer
layout = history.history_layout.layout layout = history.history_layout.layout
@ -472,8 +474,8 @@ def _select_other_window(history: "PythonHistory") -> None:
def create_key_bindings( def create_key_bindings(
history: "PythonHistory", history: PythonHistory,
python_input: "PythonInput", python_input: PythonInput,
history_mapping: HistoryMapping, history_mapping: HistoryMapping,
) -> KeyBindings: ) -> KeyBindings:
""" """
@ -592,14 +594,12 @@ def create_key_bindings(
class PythonHistory: class PythonHistory:
def __init__( def __init__(self, python_input: PythonInput, original_document: Document) -> None:
self, python_input: "PythonInput", original_document: Document
) -> None:
""" """
Create an `Application` for the history screen. Create an `Application` for the history screen.
This has to be run as a sub application of `python_input`. 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 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. offer.
""" """
from __future__ import annotations
from typing import Iterable from typing import Iterable
from warnings import warn from warnings import warn
@ -62,12 +64,12 @@ class IPythonPrompt(PromptStyle):
class IPythonValidator(PythonValidator): class IPythonValidator(PythonValidator):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(IPythonValidator, self).__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.isp = IPythonInputSplitter() self.isp = IPythonInputSplitter()
def validate(self, document: Document) -> None: def validate(self, document: Document) -> None:
document = Document(text=self.isp.transform_cell(document.text)) document = Document(text=self.isp.transform_cell(document.text))
super(IPythonValidator, self).validate(document) super().validate(document)
def create_ipython_grammar(): def create_ipython_grammar():

View file

@ -1,3 +1,5 @@
from __future__ import annotations
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from prompt_toolkit.application import get_app 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())) 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. Custom key bindings.
""" """
@ -157,7 +159,7 @@ def load_python_bindings(python_input: "PythonInput") -> KeyBindings:
Behaviour of the Enter key. Behaviour of the Enter key.
Auto indent after newline/Enter. 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 b = event.current_buffer
empty_lines_required = python_input.accept_input_on_enter or 10000 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 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. Load bindings for the navigation in the sidebar.
""" """
@ -273,7 +275,7 @@ def load_sidebar_bindings(python_input: "PythonInput") -> KeyBindings:
return bindings 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. 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. Creation of the `Layout` instance for the Python input/REPL.
""" """
from __future__ import annotations
import platform import platform
import sys import sys
from enum import Enum from enum import Enum
@ -78,26 +80,26 @@ class CompletionVisualisation(Enum):
TOOLBAR = "toolbar" TOOLBAR = "toolbar"
def show_completions_toolbar(python_input: "PythonInput") -> Condition: def show_completions_toolbar(python_input: PythonInput) -> Condition:
return Condition( return Condition(
lambda: python_input.completion_visualisation == CompletionVisualisation.TOOLBAR 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( return Condition(
lambda: python_input.completion_visualisation == CompletionVisualisation.POP_UP 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( return Condition(
lambda: python_input.completion_visualisation lambda: python_input.completion_visualisation
== CompletionVisualisation.MULTI_COLUMN == 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. 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: def get_text_fragments() -> StyleAndTextTuples:
tokens: StyleAndTextTuples = [] tokens: StyleAndTextTuples = []
def append_category(category: "OptionCategory[Any]") -> None: def append_category(category: OptionCategory[Any]) -> None:
tokens.extend( tokens.extend(
[ [
("class:sidebar", " "), ("class:sidebar", " "),
@ -149,7 +151,7 @@ def python_sidebar(python_input: "PythonInput") -> Window:
append_category(category) append_category(category)
for option in category.options: 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 i += 1
tokens.pop() # Remove last newline. 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. 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. 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. Return the `Layout` for the signature.
""" """
@ -318,7 +320,7 @@ class PythonPromptMargin(PromptMargin):
It shows something like "In [1]:". 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 self.python_input = python_input
def get_prompt_style() -> PromptStyle: def get_prompt_style() -> PromptStyle:
@ -339,7 +341,7 @@ class PythonPromptMargin(PromptMargin):
super().__init__(get_prompt, get_continuation) 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. 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 Return current input mode as a list of (token, text) tuples for use in a
toolbar. toolbar.
@ -440,7 +442,7 @@ def get_inputmode_fragments(python_input: "PythonInput") -> StyleAndTextTuples:
recording_register = app.vi_state.recording_register recording_register = app.vi_state.recording_register
if recording_register: if recording_register:
append((token, " ")) append((token, " "))
append((token + " class:record", "RECORD({})".format(recording_register))) append((token + " class:record", f"RECORD({recording_register})"))
append((token, " - ")) append((token, " - "))
if app.current_buffer.selection_state is not None: if app.current_buffer.selection_state is not None:
@ -473,7 +475,7 @@ def get_inputmode_fragments(python_input: "PythonInput") -> StyleAndTextTuples:
return result 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. Create `Layout` for the information in the right-bottom corner.
(The right part of the status bar.) (The right part of the status bar.)
@ -519,7 +521,7 @@ def show_sidebar_button_info(python_input: "PythonInput") -> Container:
def create_exit_confirmation( def create_exit_confirmation(
python_input: "PythonInput", style: str = "class:exit-confirmation" python_input: PythonInput, style: str = "class:exit-confirmation"
) -> Container: ) -> Container:
""" """
Create `Layout` for the exit message. 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. Create the `Layout` for the 'Meta+Enter` message.
""" """
@ -575,15 +577,15 @@ def meta_enter_message(python_input: "PythonInput") -> Container:
class PtPythonLayout: class PtPythonLayout:
def __init__( def __init__(
self, self,
python_input: "PythonInput", python_input: PythonInput,
lexer: Lexer, lexer: Lexer,
extra_body: Optional[AnyContainer] = None, extra_body: AnyContainer | None = None,
extra_toolbars: Optional[List[AnyContainer]] = None, extra_toolbars: list[AnyContainer] | None = None,
extra_buffer_processors: Optional[List[Processor]] = None, extra_buffer_processors: list[Processor] | None = None,
input_buffer_height: Optional[AnyDimension] = None, input_buffer_height: AnyDimension | None = None,
) -> None: ) -> None:
D = Dimension 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 [] extra_toolbars = extra_toolbars or []
input_buffer_height = input_buffer_height or D(min=6) input_buffer_height = input_buffer_height or D(min=6)
@ -591,7 +593,7 @@ class PtPythonLayout:
search_toolbar = SearchToolbar(python_input.search_buffer) search_toolbar = SearchToolbar(python_input.search_buffer)
def create_python_input_window() -> Window: 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 When there is no autocompletion menu to be shown, and we have a
signature, set the pop-up position at `bracket_start`. 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 typing import Callable, Optional
from prompt_toolkit.document import Document from prompt_toolkit.document import Document
@ -17,7 +19,7 @@ class PtpythonLexer(Lexer):
use a Python 3 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.python_lexer = python_lexer or PygmentsLexer(PythonLexer)
self.system_lexer = PygmentsLexer(BashLexer) self.system_lexer = PygmentsLexer(BashLexer)

View file

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

View file

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

View file

@ -7,6 +7,8 @@ Utility for creating a Python repl.
embed(globals(), locals(), vi_mode=False) embed(globals(), locals(), vi_mode=False)
""" """
from __future__ import annotations
import asyncio import asyncio
import builtins import builtins
import os import os
@ -53,7 +55,7 @@ except ImportError:
__all__ = ["PythonRepl", "enable_deprecation_warnings", "run_config", "embed"] __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(): for k, v in COMPILER_FLAG_NAMES.items():
if v == "COROUTINE": if v == "COROUTINE":
return k return k
@ -62,7 +64,7 @@ def _get_coroutine_flag() -> Optional[int]:
return None return None
COROUTINE_FLAG: Optional[int] = _get_coroutine_flag() COROUTINE_FLAG: int | None = _get_coroutine_flag()
def _has_coroutine_flag(code: types.CodeType) -> bool: def _has_coroutine_flag(code: types.CodeType) -> bool:
@ -89,14 +91,15 @@ class PythonRepl(PythonInput):
exec(code, self.get_globals(), self.get_locals()) exec(code, self.get_globals(), self.get_locals())
else: else:
output = self.app.output 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: def run_and_show_expression(self, expression: str) -> None:
try: try:
# Eval. # Eval.
try: try:
result = self.eval(expression) result = self.eval(expression)
except KeyboardInterrupt: # KeyboardInterrupt doesn't inherit from Exception. except KeyboardInterrupt:
# KeyboardInterrupt doesn't inherit from Exception.
raise raise
except SystemExit: except SystemExit:
raise raise
@ -299,7 +302,7 @@ class PythonRepl(PythonInput):
return None return None
def _store_eval_result(self, result: object) -> 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 locals["_"] = locals["_%i" % self.current_statement_index] = result
def get_compiler_flags(self) -> int: def get_compiler_flags(self) -> int:
@ -402,7 +405,7 @@ class PythonRepl(PythonInput):
def show_result(self, result: object) -> None: 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) formatted_text_output = self._format_result_output(result)
@ -523,7 +526,7 @@ class PythonRepl(PythonInput):
flush_page() flush_page()
def create_pager_prompt(self) -> PromptSession["PagerResult"]: def create_pager_prompt(self) -> PromptSession[PagerResult]:
""" """
Create pager --MORE-- prompt. Create pager --MORE-- prompt.
""" """
@ -572,8 +575,6 @@ class PythonRepl(PythonInput):
include_default_pygments_style=False, include_default_pygments_style=False,
output=output, output=output,
) )
output.write("%s\n" % e)
output.flush() output.flush()
def _handle_keyboard_interrupt(self, e: KeyboardInterrupt) -> None: def _handle_keyboard_interrupt(self, e: KeyboardInterrupt) -> None:
@ -652,7 +653,7 @@ def run_config(
# Run the config file in an empty namespace. # Run the config file in an empty namespace.
try: try:
namespace: Dict[str, Any] = {} namespace: dict[str, Any] = {}
with open(config_file, "rb") as f: with open(config_file, "rb") as f:
code = compile(f.read(), config_file, "exec") code = compile(f.read(), config_file, "exec")
@ -671,10 +672,10 @@ def run_config(
def embed( def embed(
globals=None, globals=None,
locals=None, locals=None,
configure: Optional[Callable[[PythonRepl], None]] = None, configure: Callable[[PythonRepl], None] | None = None,
vi_mode: bool = False, vi_mode: bool = False,
history_filename: Optional[str] = None, history_filename: str | None = None,
title: Optional[str] = None, title: str | None = None,
startup_paths=None, startup_paths=None,
patch_stdout: bool = False, patch_stdout: bool = False,
return_asyncio_coroutine: 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 Either with the Jedi library, or using `inspect.signature` if Jedi fails and we
can use `eval()` to evaluate the function object. can use `eval()` to evaluate the function object.
""" """
from __future__ import annotations
import inspect import inspect
from inspect import Signature as InspectSignature from inspect import Signature as InspectSignature
from inspect import _ParameterKind as ParameterKind from inspect import _ParameterKind as ParameterKind
@ -25,8 +27,8 @@ class Parameter:
def __init__( def __init__(
self, self,
name: str, name: str,
annotation: Optional[str], annotation: str | None,
default: Optional[str], default: str | None,
kind: ParameterKind, kind: ParameterKind,
) -> None: ) -> None:
self.name = name self.name = name
@ -66,9 +68,9 @@ class Signature:
name: str, name: str,
docstring: str, docstring: str,
parameters: Sequence[Parameter], parameters: Sequence[Parameter],
index: Optional[int] = None, index: int | None = None,
returns: str = "", returns: str = "",
bracket_start: Tuple[int, int] = (0, 0), bracket_start: tuple[int, int] = (0, 0),
) -> None: ) -> None:
self.name = name self.name = name
self.docstring = docstring self.docstring = docstring
@ -84,7 +86,7 @@ class Signature:
docstring: str, docstring: str,
signature: InspectSignature, signature: InspectSignature,
index: int, index: int,
) -> "Signature": ) -> Signature:
parameters = [] parameters = []
def get_annotation_name(annotation: object) -> str: def get_annotation_name(annotation: object) -> str:
@ -123,9 +125,7 @@ class Signature:
) )
@classmethod @classmethod
def from_jedi_signature( def from_jedi_signature(cls, signature: jedi.api.classes.Signature) -> Signature:
cls, signature: "jedi.api.classes.Signature"
) -> "Signature":
parameters = [] parameters = []
for p in signature.params: for p in signature.params:
@ -160,8 +160,8 @@ class Signature:
def get_signatures_using_jedi( def get_signatures_using_jedi(
document: Document, locals: Dict[str, Any], globals: Dict[str, Any] document: Document, locals: dict[str, Any], globals: dict[str, Any]
) -> List[Signature]: ) -> list[Signature]:
script = get_jedi_script_from_document(document, locals, globals) script = get_jedi_script_from_document(document, locals, globals)
# Show signatures in help text. # Show signatures in help text.
@ -195,8 +195,8 @@ def get_signatures_using_jedi(
def get_signatures_using_eval( def get_signatures_using_eval(
document: Document, locals: Dict[str, Any], globals: Dict[str, Any] document: Document, locals: dict[str, Any], globals: dict[str, Any]
) -> List[Signature]: ) -> list[Signature]:
""" """
Look for the signature of the function before the cursor position without Look for the signature of the function before the cursor position without
use of Jedi. This uses a similar approach as the `DictionaryCompleter` of 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 typing import Dict
from prompt_toolkit.styles import BaseStyle, Style, merge_styles 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"] __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. 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)) name: style_from_pygments_cls(get_style_by_name(name))
for name in get_all_styles() for name in get_all_styles()
} }
@ -20,7 +22,7 @@ def get_all_code_styles() -> Dict[str, BaseStyle]:
return result 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}. Return a dict mapping {ui_style_name -> style_dict}.
""" """

View file

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

View file

@ -1,3 +1,5 @@
from __future__ import annotations
from typing import Callable, Optional from typing import Callable, Optional
from prompt_toolkit.document import Document from prompt_toolkit.document import Document
@ -16,7 +18,7 @@ class PythonValidator(Validator):
active compiler flags. 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 self.get_compiler_flags = get_compiler_flags
def validate(self, document: Document) -> None: 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( setup(
name="ptpython", name="ptpython",
author="Jonathan Slenders", author="Jonathan Slenders",
version="3.0.22", version="3.0.23",
url="https://github.com/prompt-toolkit/ptpython", url="https://github.com/prompt-toolkit/ptpython",
description="Python REPL build on top of prompt_toolkit", description="Python REPL build on top of prompt_toolkit",
long_description=long_description, long_description=long_description,
@ -21,14 +21,14 @@ setup(
"appdirs", "appdirs",
"importlib_metadata;python_version<'3.8'", "importlib_metadata;python_version<'3.8'",
"jedi>=0.16.0", "jedi>=0.16.0",
# Use prompt_toolkit 3.0.18, because of the `in_thread` option. # Use prompt_toolkit 3.0.28, because of cursor shape support.
"prompt_toolkit>=3.0.18,<3.1.0", "prompt_toolkit>=3.0.28,<3.1.0",
"pygments", "pygments",
], ],
python_requires=">=3.6", python_requires=">=3.7",
classifiers=[ classifiers=[
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3 :: Only",

View file

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