Add configuring cursor shape for prompt toolkit (#5785)

* env: add `PROMPT_TOOLKIT_CURSOR_SHAPE` for configuring `prompt_toolkit` cursor shape

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* accepting prompt_toolkit.cursor_shapes.CursorShape as value

* rename PROMPT_TOOLKIT_CURSOR_SHAPE => XONSH_PROMPT_CURSOR_SHAPE

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
Đỗ Trung Nguyên 2025-01-27 15:56:19 +07:00 committed by GitHub
parent 62edf9e381
commit e7426ce709
Failed to generate hash of commit
4 changed files with 97 additions and 9 deletions

View file

@ -0,0 +1,23 @@
**Added:**
* env: add ``$XONSH_PROMPT_CURSOR_SHAPE`` for configuring prompt cursor shape.
**Changed:**
* <news item>
**Deprecated:**
* <news item>
**Removed:**
* <news item>
**Fixed:**
* <news item>
**Security:**
* <news item>

View file

@ -98,6 +98,8 @@ from xonsh.tools import (
to_int_or_none,
to_itself,
to_logfile_opt,
to_ptk_cursor_shape,
to_ptk_cursor_shape_display_value,
to_repr_pretty_,
to_shlvl,
to_tok_color_dict,
@ -1743,6 +1745,19 @@ class PTKSetting(PromptSetting): # sub-classing -> sub-group
"``DEPTH_1_BIT``, ``DEPTH_4_BIT``, ``DEPTH_8_BIT``, ``DEPTH_24_BIT`` "
"colors. Default is an empty string which means that prompt toolkit decide.",
)
XONSH_PROMPT_CURSOR_SHAPE = Var(
always_false,
to_ptk_cursor_shape,
to_ptk_cursor_shape_display_value,
to_ptk_cursor_shape("modal-vi-mode-only"),
"The cursor shape. Possible values for prompt toolkit are: "
"``block``, ``beam``, ``underline``, "
"``blinking-block``, ``blinking-beam``, ``blinking-underline``, "
"``modal``, ``modal-vi-mode-only``, ``never-change``. "
"Default value is ``modal-vi-mode-only`` which means "
"``modal`` if in vi mode and ``never-change`` if not in vi mode.",
doc_default="modal-vi-mode-only",
)
PTK_STYLE_OVERRIDES = Var(
is_tok_color_dict,
to_tok_color_dict,

View file

@ -46,13 +46,6 @@ try:
except ImportError:
HAVE_SYS_CLIPBOARD = False
try:
from prompt_toolkit.cursor_shapes import ModalCursorShapeConfig
HAVE_CURSOR_SHAPE = True
except ImportError:
HAVE_CURSOR_SHAPE = False
CAPITAL_PATTERN = re.compile(r"([a-z])([A-Z])")
Token = _TokenType()
@ -377,8 +370,10 @@ class PromptToolkitShell(BaseShell):
for attr, val in self.get_lazy_ptk_kwargs():
prompt_args[attr] = val
if editing_mode == EditingMode.VI and HAVE_CURSOR_SHAPE:
prompt_args["cursor"] = ModalCursorShapeConfig()
cursor_shape = env.get("XONSH_PROMPT_CURSOR_SHAPE")
if cursor_shape:
prompt_args["cursor"] = cursor_shape
events.on_pre_prompt.fire()
line = self.prompter.prompt(**prompt_args)
events.on_post_prompt.fire()

View file

@ -41,6 +41,19 @@ import typing as tp
import warnings
from contextlib import contextmanager
try:
from prompt_toolkit.cursor_shapes import (
CursorShape,
CursorShapeConfig,
DynamicCursorShapeConfig,
ModalCursorShapeConfig,
SimpleCursorShapeConfig,
)
HAVE_CURSOR_SHAPE = True
except ImportError:
HAVE_CURSOR_SHAPE = False
# adding imports from further xonsh modules is discouraged to avoid circular
# dependencies
from xonsh import __version__
@ -1728,6 +1741,48 @@ def ptk2_color_depth_setter(x):
return x
def ptk_cursor_shape_vi_modal():
if xsh.env.get("VI_MODE"):
return ModalCursorShapeConfig()
else:
return SimpleCursorShapeConfig()
def to_ptk_cursor_shape(x):
if not HAVE_CURSOR_SHAPE:
return None
if isinstance(x, (CursorShape, CursorShapeConfig)):
return x
if not isinstance(x, str):
raise ValueError("invalid cursor shape")
x = str(x).upper().replace("-", "_")
if x == "MODAL":
return ModalCursorShapeConfig()
elif x == "MODAL_VI_MODE_ONLY":
return DynamicCursorShapeConfig(ptk_cursor_shape_vi_modal)
try:
return CursorShape[x]
except KeyError:
return SimpleCursorShapeConfig()
def to_ptk_cursor_shape_display_value(x):
if not x:
return ""
if isinstance(x, SimpleCursorShapeConfig):
x = x.get_cursor_shape(None)
if isinstance(x, CursorShape):
x = x.value.lower().replace("_", "-")
if x.startswith("-"):
x = x[1:]
return x
if isinstance(x, ModalCursorShapeConfig):
return "modal"
if isinstance(x, DynamicCursorShapeConfig):
return "modal-vi-mode-only"
return "unknown"
def is_completions_display_value(x):
"""Enumerated values of ``$COMPLETIONS_DISPLAY``"""
return x in {"none", "single", "multi"}