1from __future__ import annotations
2
3from abc import ABC, abstractmethod
4from enum import Enum
5from typing import TYPE_CHECKING, Any, Callable, Union
6
7from prompt_toolkit.enums import EditingMode
8from prompt_toolkit.key_binding.vi_state import InputMode
9
10if TYPE_CHECKING:
11 from .application import Application
12
13__all__ = [
14 "CursorShape",
15 "CursorShapeConfig",
16 "SimpleCursorShapeConfig",
17 "ModalCursorShapeConfig",
18 "DynamicCursorShapeConfig",
19 "to_cursor_shape_config",
20]
21
22
23class CursorShape(Enum):
24 # Default value that should tell the output implementation to never send
25 # cursor shape escape sequences. This is the default right now, because
26 # before this `CursorShape` functionality was introduced into
27 # prompt_toolkit itself, people had workarounds to send cursor shapes
28 # escapes into the terminal, by monkey patching some of prompt_toolkit's
29 # internals. We don't want the default prompt_toolkit implementation to
30 # interfere with that. E.g., IPython patches the `ViState.input_mode`
31 # property. See: https://github.com/ipython/ipython/pull/13501/files
32 _NEVER_CHANGE = "_NEVER_CHANGE"
33
34 BLOCK = "BLOCK"
35 BEAM = "BEAM"
36 UNDERLINE = "UNDERLINE"
37 BLINKING_BLOCK = "BLINKING_BLOCK"
38 BLINKING_BEAM = "BLINKING_BEAM"
39 BLINKING_UNDERLINE = "BLINKING_UNDERLINE"
40
41
42class CursorShapeConfig(ABC):
43 @abstractmethod
44 def get_cursor_shape(self, application: Application[Any]) -> CursorShape:
45 """
46 Return the cursor shape to be used in the current state.
47 """
48
49
50AnyCursorShapeConfig = Union[CursorShape, CursorShapeConfig, None]
51
52
53class SimpleCursorShapeConfig(CursorShapeConfig):
54 """
55 Always show the given cursor shape.
56 """
57
58 def __init__(self, cursor_shape: CursorShape = CursorShape._NEVER_CHANGE) -> None:
59 self.cursor_shape = cursor_shape
60
61 def get_cursor_shape(self, application: Application[Any]) -> CursorShape:
62 return self.cursor_shape
63
64
65class ModalCursorShapeConfig(CursorShapeConfig):
66 """
67 Show cursor shape according to the current input mode.
68 """
69
70 def get_cursor_shape(self, application: Application[Any]) -> CursorShape:
71 if application.editing_mode == EditingMode.VI:
72 if application.vi_state.input_mode in {
73 InputMode.NAVIGATION,
74 }:
75 return CursorShape.BLOCK
76 if application.vi_state.input_mode in {
77 InputMode.INSERT,
78 InputMode.INSERT_MULTIPLE,
79 }:
80 return CursorShape.BEAM
81 if application.vi_state.input_mode in {
82 InputMode.REPLACE,
83 InputMode.REPLACE_SINGLE,
84 }:
85 return CursorShape.UNDERLINE
86 elif application.editing_mode == EditingMode.EMACS:
87 # like vi's INSERT
88 return CursorShape.BEAM
89
90 # Default
91 return CursorShape.BLOCK
92
93
94class DynamicCursorShapeConfig(CursorShapeConfig):
95 def __init__(
96 self, get_cursor_shape_config: Callable[[], AnyCursorShapeConfig]
97 ) -> None:
98 self.get_cursor_shape_config = get_cursor_shape_config
99
100 def get_cursor_shape(self, application: Application[Any]) -> CursorShape:
101 return to_cursor_shape_config(self.get_cursor_shape_config()).get_cursor_shape(
102 application
103 )
104
105
106def to_cursor_shape_config(value: AnyCursorShapeConfig) -> CursorShapeConfig:
107 """
108 Take a `CursorShape` instance or `CursorShapeConfig` and turn it into a
109 `CursorShapeConfig`.
110 """
111 if value is None:
112 return SimpleCursorShapeConfig()
113
114 if isinstance(value, CursorShape):
115 return SimpleCursorShapeConfig(value)
116
117 return value