summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2023-02-27 10:40:00 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2023-02-27 10:40:00 +0000
commite0719babdc8744144449c81fac14ba7bbf21446f (patch)
tree36e17925ec537746a9bb1ce7035b216529a957a5
parentReleasing debian version 3.0.22-2. (diff)
downloadptpython-e0719babdc8744144449c81fac14ba7bbf21446f.tar.xz
ptpython-e0719babdc8744144449c81fac14ba7bbf21446f.zip
Merging upstream version 3.0.23.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
-rw-r--r--.github/workflows/test.yaml2
-rw-r--r--CHANGELOG11
-rw-r--r--README.rst2
-rw-r--r--docs/concurrency-challenges.rst2
-rw-r--r--examples/ptpython_config/config.py3
-rw-r--r--ptpython/__init__.py2
-rw-r--r--ptpython/__main__.py2
-rw-r--r--ptpython/completer.py51
-rw-r--r--ptpython/contrib/asyncssh_repl.py4
-rw-r--r--ptpython/entry_points/run_ptipython.py4
-rw-r--r--ptpython/entry_points/run_ptpython.py6
-rw-r--r--ptpython/eventloop.py2
-rw-r--r--ptpython/filters.py4
-rw-r--r--ptpython/history_browser.py34
-rw-r--r--ptpython/ipython.py6
-rw-r--r--ptpython/key_bindings.py10
-rw-r--r--ptpython/layout.py48
-rw-r--r--ptpython/lexer.py4
-rw-r--r--ptpython/prompt_style.py4
-rw-r--r--ptpython/python_input.py98
-rw-r--r--ptpython/repl.py27
-rw-r--r--ptpython/signatures.py24
-rw-r--r--ptpython/style.py8
-rw-r--r--ptpython/utils.py8
-rw-r--r--ptpython/validator.py4
-rw-r--r--setup.py10
-rwxr-xr-xtests/run_tests.py2
27 files changed, 230 insertions, 152 deletions
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index 7ec8662..31837db 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -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
diff --git a/CHANGELOG b/CHANGELOG
index 916a542..645ca60 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -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
------------------
diff --git a/README.rst b/README.rst
index 15464ba..2db3f69 100644
--- a/README.rst
+++ b/README.rst
@@ -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())
diff --git a/docs/concurrency-challenges.rst b/docs/concurrency-challenges.rst
index b56d969..0ff9c6c 100644
--- a/docs/concurrency-challenges.rst
+++ b/docs/concurrency-challenges.rst
@@ -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.
diff --git a/examples/ptpython_config/config.py b/examples/ptpython_config/config.py
index bf9d05f..2f3f49d 100644
--- a/examples/ptpython_config/config.py
+++ b/examples/ptpython_config/config.py
@@ -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.)
diff --git a/ptpython/__init__.py b/ptpython/__init__.py
index 4908eba..63c6233 100644
--- a/ptpython/__init__.py
+++ b/ptpython/__init__.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from .repl import embed
__all__ = ["embed"]
diff --git a/ptpython/__main__.py b/ptpython/__main__.py
index 83340a7..c006261 100644
--- a/ptpython/__main__.py
+++ b/ptpython/__main__.py
@@ -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()
diff --git a/ptpython/completer.py b/ptpython/completer.py
index 9252106..f28d2b1 100644
--- a/ptpython/completer.py
+++ b/ptpython/completer.py
@@ -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.
diff --git a/ptpython/contrib/asyncssh_repl.py b/ptpython/contrib/asyncssh_repl.py
index 4c36217..0347ade 100644
--- a/ptpython/contrib/asyncssh_repl.py
+++ b/ptpython/contrib/asyncssh_repl.py
@@ -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
diff --git a/ptpython/entry_points/run_ptipython.py b/ptpython/entry_points/run_ptipython.py
index 21d7063..b660a0a 100644
--- a/ptpython/entry_points/run_ptipython.py
+++ b/ptpython/entry_points/run_ptipython.py
@@ -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
diff --git a/ptpython/entry_points/run_ptpython.py b/ptpython/entry_points/run_ptpython.py
index edffa44..1b4074d 100644
--- a/ptpython/entry_points/run_ptpython.py
+++ b/ptpython/entry_points/run_ptpython.py
@@ -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.
diff --git a/ptpython/eventloop.py b/ptpython/eventloop.py
index 63dd740..14ab64b 100644
--- a/ptpython/eventloop.py
+++ b/ptpython/eventloop.py
@@ -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
diff --git a/ptpython/filters.py b/ptpython/filters.py
index be85edf..a2079fd 100644
--- a/ptpython/filters.py
+++ b/ptpython/filters.py
@@ -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
diff --git a/ptpython/history_browser.py b/ptpython/history_browser.py
index 08725ee..eea81c2 100644
--- a/ptpython/history_browser.py
+++ b/ptpython/history_browser.py
@@ -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
diff --git a/ptpython/ipython.py b/ptpython/ipython.py
index db2a204..fb4b5ed 100644
--- a/ptpython/ipython.py
+++ b/ptpython/ipython.py
@@ -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():
diff --git a/ptpython/key_bindings.py b/ptpython/key_bindings.py
index 147a321..d7bb575 100644
--- a/ptpython/key_bindings.py
+++ b/ptpython/key_bindings.py
@@ -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.
"""
diff --git a/ptpython/layout.py b/ptpython/layout.py
index 365f381..d15e52e 100644
--- a/ptpython/layout.py
+++ b/ptpython/layout.py
@@ -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`.
diff --git a/ptpython/lexer.py b/ptpython/lexer.py
index 62e470f..81924c9 100644
--- a/ptpython/lexer.py
+++ b/ptpython/lexer.py
@@ -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)
diff --git a/ptpython/prompt_style.py b/ptpython/prompt_style.py
index e7334af..96b738f 100644
--- a/ptpython/prompt_style.py
+++ b/ptpython/prompt_style.py
@@ -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:
diff --git a/ptpython/python_input.py b/ptpython/python_input.py
index c561117..da19076 100644
--- a/ptpython/python_input.py
+++ b/ptpython/python_input.py
@@ -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.
diff --git a/ptpython/repl.py b/ptpython/repl.py
index 3c729c0..02a5075 100644
--- a/ptpython/repl.py
+++ b/ptpython/repl.py
@@ -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,
diff --git a/ptpython/signatures.py b/ptpython/signatures.py
index e836d33..5a6f286 100644
--- a/ptpython/signatures.py
+++ b/ptpython/signatures.py
@@ -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
diff --git a/ptpython/style.py b/ptpython/style.py
index 4b54d0c..199d5ab 100644
--- a/ptpython/style.py
+++ b/ptpython/style.py
@@ -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}.
"""
diff --git a/ptpython/utils.py b/ptpython/utils.py
index ef96ca4..5348899 100644
--- a/ptpython/utils.py
+++ b/ptpython/utils.py
@@ -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:
diff --git a/ptpython/validator.py b/ptpython/validator.py
index ffac583..3b36d27 100644
--- a/ptpython/validator.py
+++ b/ptpython/validator.py
@@ -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:
diff --git a/setup.py b/setup.py
index 2725dac..18d2911 100644
--- a/setup.py
+++ b/setup.py
@@ -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",
diff --git a/tests/run_tests.py b/tests/run_tests.py
index 2f94516..0de3743 100755
--- a/tests/run_tests.py
+++ b/tests/run_tests.py
@@ -1,4 +1,6 @@
#!/usr/bin/env python
+from __future__ import annotations
+
import unittest
import ptpython.completer