euporie 2.8.1__py3-none-any.whl → 2.8.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- euporie/console/_commands.py +143 -0
- euporie/console/_settings.py +58 -0
- euporie/console/app.py +25 -71
- euporie/console/tabs/console.py +267 -147
- euporie/core/__init__.py +1 -9
- euporie/core/__main__.py +31 -5
- euporie/core/_settings.py +104 -0
- euporie/core/app/__init__.py +3 -0
- euporie/core/app/_commands.py +70 -0
- euporie/core/app/_settings.py +427 -0
- euporie/core/{app.py → app/app.py} +214 -572
- euporie/core/app/base.py +51 -0
- euporie/core/{current.py → app/current.py} +13 -4
- euporie/core/app/cursor.py +35 -0
- euporie/core/app/dummy.py +12 -0
- euporie/core/app/launch.py +28 -0
- euporie/core/bars/__init__.py +11 -0
- euporie/core/bars/command.py +182 -0
- euporie/core/bars/menu.py +258 -0
- euporie/core/{widgets → bars}/search.py +154 -57
- euporie/core/{widgets → bars}/status.py +9 -26
- euporie/core/clipboard.py +19 -80
- euporie/core/comm/base.py +8 -6
- euporie/core/comm/ipywidgets.py +21 -12
- euporie/core/comm/registry.py +2 -1
- euporie/core/commands.py +11 -5
- euporie/core/completion.py +3 -2
- euporie/core/config.py +368 -341
- euporie/core/convert/__init__.py +0 -30
- euporie/core/convert/datum.py +131 -60
- euporie/core/convert/formats/__init__.py +31 -0
- euporie/core/convert/formats/ansi.py +46 -30
- euporie/core/convert/formats/common.py +11 -23
- euporie/core/convert/formats/html.py +45 -40
- euporie/core/convert/formats/pil.py +1 -1
- euporie/core/convert/formats/png.py +3 -5
- euporie/core/convert/formats/sixel.py +3 -3
- euporie/core/convert/registry.py +11 -8
- euporie/core/convert/utils.py +50 -23
- euporie/core/diagnostics.py +2 -2
- euporie/core/filters.py +72 -82
- euporie/core/format.py +13 -2
- euporie/core/ft/ansi.py +1 -1
- euporie/core/ft/html.py +36 -36
- euporie/core/ft/table.py +1 -3
- euporie/core/ft/utils.py +4 -1
- euporie/core/graphics.py +216 -124
- euporie/core/history.py +2 -2
- euporie/core/inspection.py +3 -2
- euporie/core/io.py +207 -28
- euporie/core/kernel/__init__.py +1 -0
- euporie/core/{kernel.py → kernel/client.py} +100 -139
- euporie/core/kernel/manager.py +114 -0
- euporie/core/key_binding/bindings/__init__.py +2 -8
- euporie/core/key_binding/bindings/basic.py +47 -7
- euporie/core/key_binding/bindings/completion.py +3 -8
- euporie/core/key_binding/bindings/micro.py +5 -7
- euporie/core/key_binding/bindings/mouse.py +26 -24
- euporie/core/key_binding/bindings/terminal.py +193 -0
- euporie/core/key_binding/bindings/vi.py +46 -0
- euporie/core/key_binding/key_processor.py +43 -2
- euporie/core/key_binding/registry.py +2 -0
- euporie/core/key_binding/utils.py +22 -2
- euporie/core/keys.py +7156 -93
- euporie/core/layout/cache.py +35 -25
- euporie/core/layout/containers.py +280 -74
- euporie/core/layout/decor.py +5 -5
- euporie/core/layout/mouse.py +1 -1
- euporie/core/layout/print.py +16 -3
- euporie/core/layout/scroll.py +26 -28
- euporie/core/log.py +75 -60
- euporie/core/lsp.py +118 -24
- euporie/core/margins.py +60 -31
- euporie/core/path.py +2 -1
- euporie/core/renderer.py +58 -17
- euporie/core/style.py +60 -40
- euporie/core/suggest.py +103 -85
- euporie/core/tabs/__init__.py +34 -0
- euporie/core/tabs/_settings.py +113 -0
- euporie/core/tabs/base.py +11 -435
- euporie/core/tabs/kernel.py +420 -0
- euporie/core/tabs/notebook.py +20 -54
- euporie/core/utils.py +98 -6
- euporie/core/validation.py +1 -1
- euporie/core/widgets/_settings.py +188 -0
- euporie/core/widgets/cell.py +90 -158
- euporie/core/widgets/cell_outputs.py +25 -36
- euporie/core/widgets/decor.py +11 -41
- euporie/core/widgets/dialog.py +55 -44
- euporie/core/widgets/display.py +27 -24
- euporie/core/widgets/file_browser.py +5 -26
- euporie/core/widgets/forms.py +16 -12
- euporie/core/widgets/inputs.py +37 -81
- euporie/core/widgets/layout.py +7 -6
- euporie/core/widgets/logo.py +49 -0
- euporie/core/widgets/menu.py +13 -11
- euporie/core/widgets/pager.py +8 -11
- euporie/core/widgets/palette.py +6 -6
- euporie/hub/app.py +52 -31
- euporie/notebook/_commands.py +24 -0
- euporie/notebook/_settings.py +107 -0
- euporie/notebook/app.py +109 -210
- euporie/notebook/filters.py +1 -1
- euporie/notebook/tabs/__init__.py +46 -7
- euporie/notebook/tabs/_commands.py +714 -0
- euporie/notebook/tabs/_settings.py +32 -0
- euporie/notebook/tabs/display.py +2 -2
- euporie/notebook/tabs/edit.py +12 -7
- euporie/notebook/tabs/json.py +3 -3
- euporie/notebook/tabs/log.py +1 -18
- euporie/notebook/tabs/notebook.py +21 -674
- euporie/notebook/widgets/_commands.py +11 -0
- euporie/notebook/widgets/_settings.py +19 -0
- euporie/notebook/widgets/side_bar.py +14 -34
- euporie/preview/_settings.py +104 -0
- euporie/preview/app.py +8 -30
- euporie/preview/tabs/notebook.py +15 -86
- euporie/web/tabs/web.py +4 -6
- euporie/web/widgets/webview.py +5 -12
- {euporie-2.8.1.dist-info → euporie-2.8.5.dist-info}/METADATA +11 -15
- euporie-2.8.5.dist-info/RECORD +172 -0
- {euporie-2.8.1.dist-info → euporie-2.8.5.dist-info}/WHEEL +1 -1
- {euporie-2.8.1.dist-info → euporie-2.8.5.dist-info}/entry_points.txt +2 -2
- {euporie-2.8.1.dist-info → euporie-2.8.5.dist-info}/licenses/LICENSE +1 -1
- euporie/core/launch.py +0 -59
- euporie/core/terminal.py +0 -527
- euporie-2.8.1.dist-info/RECORD +0 -146
- {euporie-2.8.1.data → euporie-2.8.5.data}/data/share/applications/euporie-console.desktop +0 -0
- {euporie-2.8.1.data → euporie-2.8.5.data}/data/share/applications/euporie-notebook.desktop +0 -0
@@ -3,16 +3,18 @@
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
5
|
import logging
|
6
|
+
from functools import partial
|
6
7
|
from typing import TYPE_CHECKING
|
7
8
|
|
8
9
|
from prompt_toolkit.filters import (
|
9
10
|
buffer_has_focus,
|
10
11
|
)
|
11
|
-
from prompt_toolkit.key_binding import ConditionalKeyBindings
|
12
12
|
|
13
13
|
from euporie.core.commands import add_cmd
|
14
14
|
from euporie.core.filters import (
|
15
|
-
|
15
|
+
char_after_cursor,
|
16
|
+
has_matching_bracket,
|
17
|
+
insert_mode,
|
16
18
|
replace_mode,
|
17
19
|
)
|
18
20
|
from euporie.core.key_binding.registry import (
|
@@ -37,6 +39,12 @@ class TextEntry:
|
|
37
39
|
{
|
38
40
|
"euporie.core.key_binding.bindings.basic.TextEntry": {
|
39
41
|
"type-key": "<any>",
|
42
|
+
"complete-bracket-()": "(",
|
43
|
+
"complete-bracket-[]": "[",
|
44
|
+
"complete-bracket-{}": "{",
|
45
|
+
"close-bracket-()": ")",
|
46
|
+
"close-bracket-[]": "]",
|
47
|
+
"close-bracket-{}": "}",
|
40
48
|
},
|
41
49
|
}
|
42
50
|
)
|
@@ -44,16 +52,18 @@ class TextEntry:
|
|
44
52
|
|
45
53
|
def load_basic_bindings(config: Config | None = None) -> KeyBindingsBase:
|
46
54
|
"""Load basic key-bindings for text entry."""
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
55
|
+
# Load additional key definitions
|
56
|
+
from euporie.core import keys # noqa: F401
|
57
|
+
|
58
|
+
return load_registered_bindings(
|
59
|
+
"euporie.core.key_binding.bindings.basic.TextEntry", config=config
|
52
60
|
)
|
53
61
|
|
54
62
|
|
55
63
|
# Commands
|
56
64
|
|
65
|
+
## Typing keys
|
66
|
+
|
57
67
|
|
58
68
|
@add_cmd(filter=buffer_has_focus, save_before=if_no_repeat, hidden=True)
|
59
69
|
def type_key(event: KeyPressEvent) -> None:
|
@@ -63,3 +73,33 @@ def type_key(event: KeyPressEvent) -> None:
|
|
63
73
|
event.current_buffer.insert_text(
|
64
74
|
event.data * event.arg, overwrite=replace_mode()
|
65
75
|
)
|
76
|
+
|
77
|
+
|
78
|
+
## Add automatic bracket completion
|
79
|
+
|
80
|
+
|
81
|
+
def _complete_bracket(right: str, event: KeyPressEvent) -> None:
|
82
|
+
event.current_buffer.insert_text(right, move_cursor=False)
|
83
|
+
event.key_processor.feed(event.key_sequence[0], first=True)
|
84
|
+
|
85
|
+
|
86
|
+
def _close_bracket(right: str, event: KeyPressEvent) -> None:
|
87
|
+
event.current_buffer.cursor_position += 1
|
88
|
+
|
89
|
+
|
90
|
+
for left, right in [("(", ")"), ("[", "]"), ("{", "}")]:
|
91
|
+
add_cmd(
|
92
|
+
name=f"complete-bracket-{left}{right}",
|
93
|
+
filter=buffer_has_focus & insert_mode & ~char_after_cursor(right),
|
94
|
+
save_before=if_no_repeat,
|
95
|
+
hidden=True,
|
96
|
+
)(partial(_complete_bracket, right))
|
97
|
+
add_cmd(
|
98
|
+
name=f"close-bracket-{left}{right}",
|
99
|
+
filter=buffer_has_focus
|
100
|
+
& insert_mode
|
101
|
+
& char_after_cursor(right)
|
102
|
+
& has_matching_bracket,
|
103
|
+
save_before=if_no_repeat,
|
104
|
+
hidden=True,
|
105
|
+
)(partial(_close_bracket, right))
|
@@ -18,18 +18,14 @@ from prompt_toolkit.key_binding.bindings.named_commands import (
|
|
18
18
|
)
|
19
19
|
|
20
20
|
from euporie.core.commands import add_cmd
|
21
|
-
from euporie.core.filters import
|
21
|
+
from euporie.core.filters import cursor_in_leading_ws, insert_mode
|
22
22
|
from euporie.core.key_binding.registry import register_bindings
|
23
23
|
|
24
24
|
log = logging.getLogger(__name__)
|
25
25
|
|
26
26
|
|
27
27
|
add_cmd(
|
28
|
-
filter=buffer_has_focus
|
29
|
-
& insert_mode
|
30
|
-
& ~has_selection
|
31
|
-
& buffer_is_code
|
32
|
-
& ~cursor_in_leading_ws,
|
28
|
+
filter=buffer_has_focus & insert_mode & ~has_selection & ~cursor_in_leading_ws,
|
33
29
|
hidden=True,
|
34
30
|
name="next-completion",
|
35
31
|
description="Show the completion menu and select the next completion.",
|
@@ -65,12 +61,11 @@ def accept_completion() -> None:
|
|
65
61
|
complete_state = buffer.complete_state
|
66
62
|
if complete_state and isinstance(complete_state.current_completion, Completion):
|
67
63
|
buffer.apply_completion(complete_state.current_completion)
|
68
|
-
get_app().layout.focus(buffer)
|
69
64
|
|
70
65
|
|
71
66
|
register_bindings(
|
72
67
|
{
|
73
|
-
"euporie.core.app.BaseApp": {
|
68
|
+
"euporie.core.app.app:BaseApp": {
|
74
69
|
"next-completion": "c-i",
|
75
70
|
"previous-completion": "s-tab",
|
76
71
|
"cancel-completion": "escape",
|
@@ -7,10 +7,8 @@ import re
|
|
7
7
|
from functools import partial
|
8
8
|
from typing import TYPE_CHECKING
|
9
9
|
|
10
|
-
from aenum import extend_enum
|
11
10
|
from prompt_toolkit.buffer import indent, unindent
|
12
11
|
from prompt_toolkit.document import Document
|
13
|
-
from prompt_toolkit.enums import EditingMode
|
14
12
|
from prompt_toolkit.filters import (
|
15
13
|
buffer_has_focus,
|
16
14
|
has_selection,
|
@@ -40,8 +38,8 @@ from prompt_toolkit.key_binding.bindings.scroll import (
|
|
40
38
|
from prompt_toolkit.keys import Keys
|
41
39
|
from prompt_toolkit.selection import SelectionState, SelectionType
|
42
40
|
|
41
|
+
from euporie.core.app.current import get_app
|
43
42
|
from euporie.core.commands import add_cmd, get_cmd
|
44
|
-
from euporie.core.current import get_app
|
45
43
|
from euporie.core.filters import (
|
46
44
|
buffer_is_code,
|
47
45
|
buffer_is_markdown,
|
@@ -74,9 +72,6 @@ class EditMode:
|
|
74
72
|
"""Micro style editor key-bindings."""
|
75
73
|
|
76
74
|
|
77
|
-
# Register micro edit mode
|
78
|
-
extend_enum(EditingMode, "MICRO", "MICRO")
|
79
|
-
|
80
75
|
# Register default bindings for micro edit mode
|
81
76
|
register_bindings(
|
82
77
|
{
|
@@ -680,6 +675,9 @@ def dent_buffer(event: KeyPressEvent, indenting: bool = True) -> None:
|
|
680
675
|
)
|
681
676
|
* sign
|
682
677
|
)
|
678
|
+
selection_state.original_cursor_position = max(
|
679
|
+
min(selection_state.original_cursor_position, len(buffer.text)), 0
|
680
|
+
)
|
683
681
|
# Maintain the selection state before indentation
|
684
682
|
buffer.selection_state = selection_state
|
685
683
|
|
@@ -722,7 +720,7 @@ def indent_lines(event: KeyPressEvent) -> None:
|
|
722
720
|
@add_cmd(
|
723
721
|
filter=buffer_has_focus
|
724
722
|
& (cursor_in_leading_ws | has_selection)
|
725
|
-
& ~cursor_at_start_of_line,
|
723
|
+
& (~cursor_at_start_of_line | cursor_at_start_of_line),
|
726
724
|
)
|
727
725
|
def unindent_lines(event: KeyPressEvent) -> None:
|
728
726
|
"""Unindent the current or selected lines."""
|
@@ -22,8 +22,9 @@ from prompt_toolkit.key_binding.bindings.mouse import (
|
|
22
22
|
from prompt_toolkit.keys import Keys
|
23
23
|
from prompt_toolkit.mouse_events import MouseButton, MouseEventType, MouseModifier
|
24
24
|
from prompt_toolkit.mouse_events import MouseEvent as PtkMouseEvent
|
25
|
+
from prompt_toolkit.renderer import HeightIsUnknownError
|
25
26
|
|
26
|
-
from euporie.core.app import BaseApp
|
27
|
+
from euporie.core.app.app import BaseApp
|
27
28
|
|
28
29
|
if TYPE_CHECKING:
|
29
30
|
from prompt_toolkit.key_binding.key_bindings import NotImplementedOrNone
|
@@ -102,29 +103,23 @@ def _parse_mouse_data(
|
|
102
103
|
# Parse event type.
|
103
104
|
if sgr:
|
104
105
|
if sgr_pixels:
|
105
|
-
#
|
106
|
+
# Scale down pixel-wise mouse position to cell based, and calculate
|
107
|
+
# relative position of mouse within the cell
|
106
108
|
cell_x, cell_y = cell_size_xy
|
107
109
|
px, py = x, y
|
108
110
|
fx, fy = px / cell_x + 1, py / cell_y + 1
|
109
111
|
x, y = int(fx), int(fy)
|
110
112
|
rx, ry = fx - x, fy - y
|
111
|
-
|
112
113
|
try:
|
113
|
-
(
|
114
|
-
|
115
|
-
|
116
|
-
mouse_modifiers,
|
117
|
-
) = xterm_sgr_mouse_events[mouse_event, m]
|
114
|
+
(mouse_button, mouse_event_type, mouse_modifiers) = (
|
115
|
+
xterm_sgr_mouse_events[mouse_event, m]
|
116
|
+
)
|
118
117
|
except KeyError:
|
119
118
|
return None
|
120
119
|
|
121
120
|
else:
|
122
121
|
# Some other terminals, like urxvt, Hyper terminal, ...
|
123
|
-
(
|
124
|
-
mouse_button,
|
125
|
-
mouse_event_type,
|
126
|
-
mouse_modifiers,
|
127
|
-
) = urxvt_mouse_events.get(
|
122
|
+
(mouse_button, mouse_event_type, mouse_modifiers) = urxvt_mouse_events.get(
|
128
123
|
mouse_event, (UNKNOWN_BUTTON, MOUSE_MOVE, UNKNOWN_MODIFIER)
|
129
124
|
)
|
130
125
|
|
@@ -153,13 +148,14 @@ def load_mouse_bindings() -> KeyBindings:
|
|
153
148
|
def _(event: KeyPressEvent) -> NotImplementedOrNone:
|
154
149
|
"""Handle incoming mouse event, include SGR-pixel mode."""
|
155
150
|
# Ensure mypy knows this would only run in a euporie appo
|
156
|
-
|
151
|
+
app = event.app
|
152
|
+
assert isinstance(app, BaseApp)
|
157
153
|
|
158
|
-
if not
|
154
|
+
if not app.renderer.height_is_known:
|
159
155
|
return NotImplemented
|
160
156
|
|
161
157
|
mouse_event = _MOUSE_EVENT_CACHE[
|
162
|
-
event.data, app.
|
158
|
+
event.data, app.term_sgr_pixel, app.cell_size_px
|
163
159
|
]
|
164
160
|
|
165
161
|
if mouse_event is None:
|
@@ -169,10 +165,9 @@ def load_mouse_bindings() -> KeyBindings:
|
|
169
165
|
if mouse_event.event_type is not None:
|
170
166
|
# Take region above the layout into account. The reported
|
171
167
|
# coordinates are absolute to the visible part of the terminal.
|
172
|
-
from prompt_toolkit.renderer import HeightIsUnknownError
|
173
|
-
|
174
168
|
x, y = mouse_event.position
|
175
169
|
|
170
|
+
# Adjust position to take into account space above non-full screen apps
|
176
171
|
try:
|
177
172
|
rows_above = app.renderer.rows_above_layout
|
178
173
|
except HeightIsUnknownError:
|
@@ -180,28 +175,35 @@ def load_mouse_bindings() -> KeyBindings:
|
|
180
175
|
else:
|
181
176
|
y -= rows_above
|
182
177
|
|
183
|
-
# Save
|
184
|
-
app.mouse_position =
|
178
|
+
# Save mouse position within the app
|
179
|
+
app.mouse_position = Point(x=x, y=y)
|
185
180
|
|
186
181
|
# Apply limits to mouse position if enabled
|
187
182
|
if (mouse_limits := app.mouse_limits) is not None:
|
188
183
|
x = max(
|
189
184
|
mouse_limits.xpos,
|
190
|
-
min(x, mouse_limits.xpos + (mouse_limits.width - 1)
|
185
|
+
min(x, mouse_limits.xpos + (mouse_limits.width) - 1),
|
191
186
|
)
|
192
187
|
y = max(
|
193
188
|
mouse_limits.ypos,
|
194
|
-
min(y, mouse_limits.ypos + (mouse_limits.height - 1)
|
189
|
+
min(y, mouse_limits.ypos + (mouse_limits.height) - 1),
|
195
190
|
)
|
196
191
|
|
197
|
-
|
192
|
+
# Do not modify the mouse event in the cache, instead create a new instance
|
193
|
+
mouse_event = MouseEvent(
|
194
|
+
position=Point(x=x, y=y),
|
195
|
+
event_type=mouse_event.event_type,
|
196
|
+
button=mouse_event.button,
|
197
|
+
modifiers=mouse_event.modifiers,
|
198
|
+
cell_position=mouse_event.cell_position,
|
199
|
+
)
|
198
200
|
|
199
201
|
# Call the mouse handler from the renderer.
|
200
202
|
# Note: This can return `NotImplemented` if no mouse handler was
|
201
203
|
# found for this position, or if no repainting needs to
|
202
204
|
# happen. this way, we avoid excessive repaints during mouse
|
203
205
|
# movements.
|
204
|
-
handler =
|
206
|
+
handler = app.renderer.mouse_handlers.mouse_handlers[y][x]
|
205
207
|
|
206
208
|
return handler(mouse_event)
|
207
209
|
|
@@ -0,0 +1,193 @@
|
|
1
|
+
"""Contains key handlers for terminal queries."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
import logging
|
6
|
+
from typing import TYPE_CHECKING
|
7
|
+
|
8
|
+
from euporie.core.commands import add_cmd
|
9
|
+
from euporie.core.key_binding.registry import (
|
10
|
+
load_registered_bindings,
|
11
|
+
register_bindings,
|
12
|
+
)
|
13
|
+
|
14
|
+
if TYPE_CHECKING:
|
15
|
+
from prompt_toolkit.key_binding import KeyBindingsBase, KeyPressEvent
|
16
|
+
|
17
|
+
from euporie.core.config import Config
|
18
|
+
|
19
|
+
log = logging.getLogger(__name__)
|
20
|
+
|
21
|
+
_COLOR_NAMES: dict[str, str] = {
|
22
|
+
"10": "fg",
|
23
|
+
"11": "bg",
|
24
|
+
"4;0": "ansiblack",
|
25
|
+
"4;1": "ansired",
|
26
|
+
"4;2": "ansigreen",
|
27
|
+
"4;3": "ansiyellow",
|
28
|
+
"4;4": "ansiblue",
|
29
|
+
"4;5": "ansipurple",
|
30
|
+
"4;6": "ansicyan",
|
31
|
+
"4;7": "ansiwhite",
|
32
|
+
"4;8": "ansirbightblack",
|
33
|
+
"4;9": "ansirbightred",
|
34
|
+
"4;10": "ansirbightgreen",
|
35
|
+
"4;11": "ansirbightyellow",
|
36
|
+
"4;12": "ansirbightblue",
|
37
|
+
"4;13": "ansirbightpurple",
|
38
|
+
"4;14": "ansirbightcyan",
|
39
|
+
"4;15": "ansirbightwhite",
|
40
|
+
}
|
41
|
+
|
42
|
+
|
43
|
+
def get_match(event: KeyPressEvent) -> dict[str, str] | None:
|
44
|
+
"""Get pattern matches from a key press event."""
|
45
|
+
if (
|
46
|
+
(parser := getattr(event.app.input, "vt100_parser", None))
|
47
|
+
and (patterns := getattr(parser, "patterns", None))
|
48
|
+
and (pattern := patterns.get(event.key_sequence[-1].key))
|
49
|
+
and (match := pattern.match(event.data))
|
50
|
+
and (values := match.groupdict())
|
51
|
+
):
|
52
|
+
return values
|
53
|
+
return None
|
54
|
+
|
55
|
+
|
56
|
+
@add_cmd(hidden=True, is_global=True)
|
57
|
+
def _set_terminal_color(event: KeyPressEvent) -> object:
|
58
|
+
"""Run when the terminal receives a terminal color query response.
|
59
|
+
|
60
|
+
Args:
|
61
|
+
event: The key press event received when the termina sends a response
|
62
|
+
|
63
|
+
Returns:
|
64
|
+
:py:obj:`NotImplemented`, so the application is not invalidated when a
|
65
|
+
response from the terminal is received
|
66
|
+
|
67
|
+
"""
|
68
|
+
from euporie.core.app.app import BaseApp
|
69
|
+
|
70
|
+
if isinstance(app := event.app, BaseApp) and (colors := get_match(event)):
|
71
|
+
c = colors["c"]
|
72
|
+
r, g, b = colors.get("r", "00"), colors.get("g", "00"), colors.get("b", "00")
|
73
|
+
app.term_colors[_COLOR_NAMES.get(c, c)] = f"#{r[:2]}{g[:2]}{b[:2]}"
|
74
|
+
app.update_style()
|
75
|
+
return NotImplemented
|
76
|
+
|
77
|
+
|
78
|
+
@add_cmd(hidden=True, is_global=True)
|
79
|
+
def _set_terminal_pixel_size(event: KeyPressEvent) -> object:
|
80
|
+
"""Run when the terminal receives a pixel dimension query response."""
|
81
|
+
from euporie.core.app.app import BaseApp
|
82
|
+
|
83
|
+
if (
|
84
|
+
isinstance(app := event.app, BaseApp)
|
85
|
+
and (values := get_match(event))
|
86
|
+
and (x := values.get("x"))
|
87
|
+
and (y := values.get("y"))
|
88
|
+
):
|
89
|
+
app.term_size_px = int(x), int(y)
|
90
|
+
return NotImplemented
|
91
|
+
|
92
|
+
|
93
|
+
@add_cmd(hidden=True, is_global=True)
|
94
|
+
def _set_terminal_graphics_sixel(event: KeyPressEvent) -> object:
|
95
|
+
"""Run when the terminal receives a sixel graphics support query response."""
|
96
|
+
from euporie.core.app.app import BaseApp
|
97
|
+
|
98
|
+
if (
|
99
|
+
isinstance(app := event.app, BaseApp)
|
100
|
+
and (values := get_match(event))
|
101
|
+
and values.get("sixel")
|
102
|
+
):
|
103
|
+
app.term_graphics_sixel = True
|
104
|
+
return NotImplemented
|
105
|
+
|
106
|
+
|
107
|
+
@add_cmd(hidden=True, is_global=True)
|
108
|
+
def _set_terminal_graphics_iterm(event: KeyPressEvent) -> object:
|
109
|
+
"""Run when the terminal receives a iterm graphics support query response."""
|
110
|
+
from euporie.core.app.app import BaseApp
|
111
|
+
|
112
|
+
if (
|
113
|
+
isinstance(app := event.app, BaseApp)
|
114
|
+
and (values := get_match(event))
|
115
|
+
and (term := values.get("term"))
|
116
|
+
and term.startswith(("WezTerm", "Konsole", "mlterm"))
|
117
|
+
):
|
118
|
+
app.term_graphics_iterm = True
|
119
|
+
return NotImplemented
|
120
|
+
|
121
|
+
|
122
|
+
@add_cmd(hidden=True, is_global=True)
|
123
|
+
def _set_terminal_graphics_kitty(event: KeyPressEvent) -> object:
|
124
|
+
"""Run when the terminal receives a kitty graphics support query response."""
|
125
|
+
from euporie.core.app.app import BaseApp
|
126
|
+
|
127
|
+
if (
|
128
|
+
isinstance(app := event.app, BaseApp)
|
129
|
+
and (values := get_match(event))
|
130
|
+
and values.get("status") == "OK"
|
131
|
+
):
|
132
|
+
app.term_graphics_kitty = True
|
133
|
+
return NotImplemented
|
134
|
+
|
135
|
+
|
136
|
+
@add_cmd(hidden=True, is_global=True)
|
137
|
+
def _set_terminal_sgr_pixel(event: KeyPressEvent) -> object:
|
138
|
+
"""Run when the terminal receives a SGR-pixel mode support query response."""
|
139
|
+
from euporie.core.app.app import BaseApp
|
140
|
+
|
141
|
+
if (
|
142
|
+
isinstance(app := event.app, BaseApp)
|
143
|
+
and (values := get_match(event))
|
144
|
+
and (values.get("Pm") in {"1", "3"})
|
145
|
+
):
|
146
|
+
app.term_sgr_pixel = True
|
147
|
+
return NotImplemented
|
148
|
+
|
149
|
+
|
150
|
+
@add_cmd(hidden=True, is_global=True)
|
151
|
+
def _set_terminal_clipboard_data(event: KeyPressEvent) -> object:
|
152
|
+
"""Run when the terminal receives a clipboard data query response."""
|
153
|
+
from base64 import b64decode
|
154
|
+
|
155
|
+
from euporie.core.app.app import BaseApp
|
156
|
+
from euporie.core.clipboard import Osc52Clipboard
|
157
|
+
|
158
|
+
if (
|
159
|
+
isinstance(app := event.app, BaseApp)
|
160
|
+
and isinstance(clipboard := app.clipboard, Osc52Clipboard)
|
161
|
+
and (values := get_match(event))
|
162
|
+
):
|
163
|
+
value = values.get("data", "")
|
164
|
+
text = b64decode(value).decode()
|
165
|
+
log.warning(repr(text))
|
166
|
+
clipboard.sync(text)
|
167
|
+
return NotImplemented
|
168
|
+
|
169
|
+
|
170
|
+
class TerminalQueries:
|
171
|
+
"""Key bindings for terminal query responses."""
|
172
|
+
|
173
|
+
|
174
|
+
register_bindings(
|
175
|
+
{
|
176
|
+
"euporie.core.io.TerminalInfo": {
|
177
|
+
"set-terminal-color": "<colors-response>",
|
178
|
+
"set-terminal-pixel-size": "<pixel-size-response>",
|
179
|
+
"set-terminal-graphics-kitty": "<kitty-graphics-status-response>",
|
180
|
+
"set-terminal-graphics-sixel": "<sixel-graphics-status-response>",
|
181
|
+
"set-terminal-graphics-iterm": "<iterm-graphics-status-response>",
|
182
|
+
"set-terminal-sgr-pixel": "<sgr-pixel-status-response>",
|
183
|
+
"set-terminal-clipboard-data": "<clipboard-data-response>",
|
184
|
+
}
|
185
|
+
}
|
186
|
+
)
|
187
|
+
|
188
|
+
|
189
|
+
def load_terminal_bindings(config: Config | None = None) -> KeyBindingsBase:
|
190
|
+
"""Load editor key-bindings in the style of the ``micro`` text editor."""
|
191
|
+
return load_registered_bindings(
|
192
|
+
"euporie.core.key_binding.bindings.terminal.TerminalQueries", config=config
|
193
|
+
)
|
@@ -0,0 +1,46 @@
|
|
1
|
+
"""Add additional keys to the prompt_toolkit vi key-bindings."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
from typing import TYPE_CHECKING, cast
|
6
|
+
|
7
|
+
from prompt_toolkit.buffer import indent, unindent
|
8
|
+
from prompt_toolkit.filters.app import vi_insert_mode
|
9
|
+
from prompt_toolkit.key_binding.bindings.vi import (
|
10
|
+
load_vi_bindings as load_ptk_vi_bindings,
|
11
|
+
)
|
12
|
+
|
13
|
+
from euporie.core.filters import cursor_in_leading_ws
|
14
|
+
|
15
|
+
if TYPE_CHECKING:
|
16
|
+
from prompt_toolkit.key_binding.key_bindings import (
|
17
|
+
ConditionalKeyBindings,
|
18
|
+
KeyBindings,
|
19
|
+
KeyBindingsBase,
|
20
|
+
)
|
21
|
+
from prompt_toolkit.key_binding.key_processor import KeyPressEvent
|
22
|
+
|
23
|
+
|
24
|
+
def load_vi_bindings() -> KeyBindingsBase:
|
25
|
+
"""Load vi keybindings from PTK, adding additional bindings."""
|
26
|
+
# We know the type of the vi bindings
|
27
|
+
vi_bindings = cast(
|
28
|
+
"KeyBindings",
|
29
|
+
cast("ConditionalKeyBindings", load_ptk_vi_bindings()).key_bindings,
|
30
|
+
)
|
31
|
+
handle = vi_bindings.add
|
32
|
+
|
33
|
+
@handle("c-i", filter=vi_insert_mode & cursor_in_leading_ws)
|
34
|
+
def _indent(event: KeyPressEvent) -> None:
|
35
|
+
"""Indent lines."""
|
36
|
+
buffer = event.current_buffer
|
37
|
+
current_row = buffer.document.cursor_position_row
|
38
|
+
indent(buffer, current_row, current_row + event.arg)
|
39
|
+
|
40
|
+
@handle("s-tab", filter=vi_insert_mode & cursor_in_leading_ws)
|
41
|
+
def _unindent(event: KeyPressEvent) -> None:
|
42
|
+
"""Unindent lines."""
|
43
|
+
current_row = event.current_buffer.document.cursor_position_row
|
44
|
+
unindent(event.current_buffer, current_row, current_row + event.arg)
|
45
|
+
|
46
|
+
return vi_bindings
|
@@ -4,21 +4,39 @@ from __future__ import annotations
|
|
4
4
|
|
5
5
|
import asyncio
|
6
6
|
import logging
|
7
|
+
import time
|
7
8
|
from typing import TYPE_CHECKING
|
8
9
|
|
9
10
|
from prompt_toolkit.application.current import get_app
|
11
|
+
from prompt_toolkit.key_binding.key_processor import KeyPress, _Flush
|
10
12
|
from prompt_toolkit.key_binding.key_processor import KeyProcessor as PtKeyProcessor
|
11
|
-
from prompt_toolkit.key_binding.key_processor import _Flush
|
12
13
|
from prompt_toolkit.keys import Keys
|
13
14
|
|
15
|
+
from euporie.core.keys import MoreKeys
|
16
|
+
|
14
17
|
if TYPE_CHECKING:
|
15
18
|
from typing import Any
|
16
19
|
|
17
|
-
from prompt_toolkit.key_binding.key_processor import KeyPress
|
18
20
|
|
19
21
|
log = logging.getLogger(__name__)
|
20
22
|
|
21
23
|
|
24
|
+
def _kp_init(
|
25
|
+
self: KeyPress, key: Keys | MoreKeys | str, data: str | None = None
|
26
|
+
) -> None:
|
27
|
+
"""Include more keys when creating a KeyPress."""
|
28
|
+
assert isinstance(key, (Keys | MoreKeys)) or len(key) == 1, (
|
29
|
+
f"key {key!r} ({type(key)}) not recoognised {MoreKeys(key)}"
|
30
|
+
)
|
31
|
+
if data is None:
|
32
|
+
data = key.value if isinstance(key, (Keys, MoreKeys)) else key
|
33
|
+
self.key = key
|
34
|
+
self.data = data
|
35
|
+
|
36
|
+
|
37
|
+
setattr(KeyPress, "__init__", _kp_init) # noqa: B010
|
38
|
+
|
39
|
+
|
22
40
|
class KeyProcessor(PtKeyProcessor):
|
23
41
|
"""A subclass of prompt_toolkit's keyprocessor.
|
24
42
|
|
@@ -125,3 +143,26 @@ class KeyProcessor(PtKeyProcessor):
|
|
125
143
|
# Skip timeout if the last key was flush.
|
126
144
|
if not is_flush:
|
127
145
|
self._start_timeout()
|
146
|
+
|
147
|
+
def await_key(self, key: Keys | MoreKeys, timeout: float = 1.0) -> None:
|
148
|
+
"""Wait for a particular key, processing it before all other keys.
|
149
|
+
|
150
|
+
Args:
|
151
|
+
key: The key to wait for
|
152
|
+
timeout: How long to wait for the key, in seconds
|
153
|
+
"""
|
154
|
+
# Wait up to 1 second for response from terminal
|
155
|
+
start = time.monotonic()
|
156
|
+
input = get_app().input
|
157
|
+
tkp = self.__class__(key_bindings=self._bindings)
|
158
|
+
while (time.monotonic() - start) < timeout:
|
159
|
+
time.sleep(0.05)
|
160
|
+
for press in input.read_keys():
|
161
|
+
if press.key == key:
|
162
|
+
# If we find the key we're after, process it immediately
|
163
|
+
tkp.feed_multiple([press, _Flush])
|
164
|
+
tkp.process_keys()
|
165
|
+
return
|
166
|
+
else:
|
167
|
+
# If we get other keys, add them to the input queue
|
168
|
+
self.feed(press)
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
|
+
from functools import lru_cache
|
5
6
|
from typing import TYPE_CHECKING
|
6
7
|
|
7
8
|
from prompt_toolkit.key_binding import KeyBindings
|
@@ -31,6 +32,7 @@ def register_bindings(bindings: dict[str, KeyBindingDefs]) -> None:
|
|
31
32
|
BINDINGS[group][command] = keys
|
32
33
|
|
33
34
|
|
35
|
+
@lru_cache
|
34
36
|
def load_registered_bindings(
|
35
37
|
*names: str, config: Config | None = None
|
36
38
|
) -> KeyBindingsBase:
|
@@ -4,9 +4,11 @@ from __future__ import annotations
|
|
4
4
|
|
5
5
|
from typing import TYPE_CHECKING
|
6
6
|
|
7
|
-
from prompt_toolkit.key_binding
|
7
|
+
from prompt_toolkit.key_binding import key_bindings
|
8
|
+
from prompt_toolkit.key_binding.key_bindings import _parse_key as _ptk_parse_key
|
9
|
+
from prompt_toolkit.keys import Keys
|
8
10
|
|
9
|
-
from euporie.core.keys import
|
11
|
+
from euporie.core.keys import MoreKeys
|
10
12
|
|
11
13
|
if TYPE_CHECKING:
|
12
14
|
from prompt_toolkit.key_binding import KeyPressEvent
|
@@ -28,6 +30,24 @@ def if_no_repeat(event: KeyPressEvent) -> bool:
|
|
28
30
|
return not event.is_repeat
|
29
31
|
|
30
32
|
|
33
|
+
def _parse_key(key: AnyKeys | MoreKeys | str) -> Keys | MoreKeys | str:
|
34
|
+
"""Parse a key or string, including additional keys."""
|
35
|
+
if isinstance(key, (Keys, str)):
|
36
|
+
try:
|
37
|
+
return _ptk_parse_key(key)
|
38
|
+
except ValueError:
|
39
|
+
pass
|
40
|
+
if isinstance(key, MoreKeys):
|
41
|
+
return key
|
42
|
+
try:
|
43
|
+
return MoreKeys(key)
|
44
|
+
except ValueError as err:
|
45
|
+
raise ValueError("Key binding not recognised") from err
|
46
|
+
|
47
|
+
|
48
|
+
key_bindings._parse_key = _parse_key
|
49
|
+
|
50
|
+
|
31
51
|
def parse_keys(keys: AnyKeys) -> list[tuple[str | Keys, ...]]:
|
32
52
|
"""Pare a list of keys."""
|
33
53
|
output: list[tuple[str | Keys, ...]] = []
|