summaryrefslogtreecommitdiffstats
path: root/ptpython/python_input.py
diff options
context:
space:
mode:
Diffstat (limited to 'ptpython/python_input.py')
-rw-r--r--ptpython/python_input.py98
1 files changed, 66 insertions, 32 deletions
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.