euporie 2.8.4__py3-none-any.whl → 2.8.6__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 +58 -62
- euporie/core/__init__.py +1 -1
- euporie/core/__main__.py +28 -11
- euporie/core/_settings.py +109 -0
- euporie/core/app/__init__.py +3 -0
- euporie/core/app/_commands.py +95 -0
- euporie/core/app/_settings.py +457 -0
- euporie/core/{app.py → app/app.py} +212 -576
- 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 +205 -0
- euporie/core/bars/menu.py +258 -0
- euporie/core/{widgets → bars}/search.py +20 -16
- euporie/core/{widgets → bars}/status.py +6 -23
- euporie/core/clipboard.py +19 -80
- euporie/core/comm/base.py +8 -6
- euporie/core/comm/ipywidgets.py +16 -7
- euporie/core/comm/registry.py +2 -1
- euporie/core/commands.py +10 -20
- euporie/core/completion.py +3 -2
- euporie/core/config.py +368 -341
- euporie/core/convert/__init__.py +0 -30
- euporie/core/convert/datum.py +116 -53
- euporie/core/convert/formats/__init__.py +31 -0
- euporie/core/convert/formats/ansi.py +9 -23
- 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 +4 -6
- euporie/core/convert/utils.py +41 -4
- euporie/core/diagnostics.py +2 -2
- euporie/core/filters.py +98 -40
- euporie/core/format.py +2 -3
- euporie/core/ft/ansi.py +1 -1
- euporie/core/ft/html.py +12 -21
- euporie/core/ft/table.py +1 -3
- euporie/core/ft/utils.py +4 -1
- euporie/core/graphics.py +386 -133
- 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} +45 -108
- euporie/core/kernel/manager.py +114 -0
- euporie/core/key_binding/bindings/__init__.py +1 -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 +1 -6
- euporie/core/key_binding/bindings/mouse.py +2 -2
- euporie/core/key_binding/bindings/terminal.py +193 -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 +3 -3
- euporie/core/layout/containers.py +48 -4
- euporie/core/layout/decor.py +2 -2
- euporie/core/layout/mouse.py +1 -1
- euporie/core/layout/print.py +2 -1
- euporie/core/layout/scroll.py +39 -34
- euporie/core/log.py +76 -64
- euporie/core/lsp.py +118 -24
- euporie/core/margins.py +1 -1
- euporie/core/path.py +62 -13
- euporie/core/renderer.py +58 -17
- euporie/core/style.py +57 -39
- euporie/core/suggest.py +103 -85
- euporie/core/tabs/__init__.py +32 -0
- euporie/core/tabs/_settings.py +113 -0
- euporie/core/tabs/base.py +80 -470
- euporie/core/tabs/kernel.py +419 -0
- euporie/core/tabs/notebook.py +24 -101
- euporie/core/utils.py +92 -15
- euporie/core/validation.py +1 -1
- euporie/core/widgets/_settings.py +188 -0
- euporie/core/widgets/cell.py +19 -50
- euporie/core/widgets/cell_outputs.py +25 -36
- euporie/core/widgets/decor.py +11 -41
- euporie/core/widgets/dialog.py +62 -27
- euporie/core/widgets/display.py +12 -15
- euporie/core/widgets/file_browser.py +2 -23
- euporie/core/widgets/forms.py +8 -5
- euporie/core/widgets/inputs.py +13 -70
- euporie/core/widgets/layout.py +2 -1
- euporie/core/widgets/logo.py +49 -0
- euporie/core/widgets/menu.py +10 -8
- euporie/core/widgets/pager.py +6 -10
- euporie/core/widgets/palette.py +6 -6
- euporie/hub/app.py +52 -35
- euporie/notebook/_commands.py +24 -0
- euporie/notebook/_settings.py +107 -0
- euporie/notebook/app.py +49 -171
- 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 +4 -4
- euporie/notebook/tabs/edit.py +11 -44
- euporie/notebook/tabs/json.py +5 -5
- euporie/notebook/tabs/log.py +1 -18
- euporie/notebook/tabs/notebook.py +11 -660
- 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 +6 -31
- euporie/preview/tabs/notebook.py +6 -72
- euporie/web/__init__.py +1 -0
- euporie/web/tabs/__init__.py +14 -0
- euporie/web/tabs/web.py +11 -6
- euporie/web/widgets/__init__.py +1 -0
- euporie/web/widgets/webview.py +5 -15
- {euporie-2.8.4.dist-info → euporie-2.8.6.dist-info}/METADATA +10 -8
- euporie-2.8.6.dist-info/RECORD +175 -0
- {euporie-2.8.4.dist-info → euporie-2.8.6.dist-info}/WHEEL +1 -1
- {euporie-2.8.4.dist-info → euporie-2.8.6.dist-info}/entry_points.txt +2 -2
- {euporie-2.8.4.dist-info → euporie-2.8.6.dist-info}/licenses/LICENSE +1 -1
- euporie/core/launch.py +0 -64
- euporie/core/terminal.py +0 -522
- euporie-2.8.4.dist-info/RECORD +0 -147
- {euporie-2.8.4.data → euporie-2.8.6.data}/data/share/applications/euporie-console.desktop +0 -0
- {euporie-2.8.4.data → euporie-2.8.6.data}/data/share/applications/euporie-notebook.desktop +0 -0
@@ -0,0 +1,114 @@
|
|
1
|
+
"""Contain classes relating to kernel management."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
import logging
|
6
|
+
import os
|
7
|
+
import re
|
8
|
+
import sys
|
9
|
+
import threading
|
10
|
+
from typing import TYPE_CHECKING
|
11
|
+
|
12
|
+
from jupyter_client import AsyncKernelManager
|
13
|
+
from jupyter_client.provisioning.local_provisioner import LocalProvisioner
|
14
|
+
|
15
|
+
if TYPE_CHECKING:
|
16
|
+
from typing import Any, Callable, TextIO
|
17
|
+
|
18
|
+
from jupyter_client.connect import KernelConnectionInfo
|
19
|
+
|
20
|
+
|
21
|
+
log = logging.getLogger(__name__)
|
22
|
+
|
23
|
+
|
24
|
+
class LoggingLocalProvisioner(LocalProvisioner): # type:ignore[misc]
|
25
|
+
"""A Jupyter kernel provisionser which logs kernel output."""
|
26
|
+
|
27
|
+
async def launch_kernel(
|
28
|
+
self, cmd: list[str], **kwargs: Any
|
29
|
+
) -> KernelConnectionInfo:
|
30
|
+
"""Launch a kernel with a command."""
|
31
|
+
await super().launch_kernel(cmd, **kwargs)
|
32
|
+
|
33
|
+
def log_kernel_output(pipe: TextIO, log_func: Callable) -> None:
|
34
|
+
try:
|
35
|
+
with pipe:
|
36
|
+
for line in iter(pipe.readline, ""):
|
37
|
+
log_func(line.rstrip())
|
38
|
+
except StopIteration:
|
39
|
+
pass
|
40
|
+
|
41
|
+
if self.process is not None:
|
42
|
+
# Start thread to listen for kernel output
|
43
|
+
threading.Thread(
|
44
|
+
target=log_kernel_output,
|
45
|
+
args=(self.process.stdout, log.warning),
|
46
|
+
daemon=True,
|
47
|
+
).start()
|
48
|
+
|
49
|
+
return self.connection_info
|
50
|
+
|
51
|
+
|
52
|
+
def set_default_provisioner() -> None:
|
53
|
+
"""Set the default kernel provisioner to euporie's logging provisioner."""
|
54
|
+
from jupyter_client.provisioning import KernelProvisionerFactory as KPF
|
55
|
+
|
56
|
+
KPF.instance().default_provisioner_name = "logging-local-provisioner"
|
57
|
+
|
58
|
+
|
59
|
+
class EuporieKernelManager(AsyncKernelManager):
|
60
|
+
"""Kernel Manager subclass.
|
61
|
+
|
62
|
+
``jupyter_client`` replaces a plain ``python`` command with the current executable,
|
63
|
+
but this is not desirable if the client is running in its own prefix (e.g. with
|
64
|
+
``pipx``). We work around this here.
|
65
|
+
|
66
|
+
See https://github.com/jupyter/jupyter_client/issues/949
|
67
|
+
"""
|
68
|
+
|
69
|
+
def format_kernel_cmd(self, extra_arguments: list[str] | None = None) -> list[str]:
|
70
|
+
"""Replace templated args (e.g. {connection_file})."""
|
71
|
+
extra_arguments = extra_arguments or []
|
72
|
+
assert self.kernel_spec is not None
|
73
|
+
cmd = self.kernel_spec.argv + extra_arguments
|
74
|
+
|
75
|
+
v_major, v_minor = sys.version_info[:2]
|
76
|
+
if cmd and cmd[0] in {
|
77
|
+
"python",
|
78
|
+
f"python{v_major}",
|
79
|
+
f"python{v_major}.{v_minor}",
|
80
|
+
}:
|
81
|
+
# If the command is `python` without an absolute path and euporie is
|
82
|
+
# running in the same prefix as the kernel_spec file is located, use
|
83
|
+
# sys.executable: otherwise fall back to the executable in the base prefix
|
84
|
+
if (
|
85
|
+
os.path.commonpath((sys.prefix, self.kernel_spec.resource_dir))
|
86
|
+
== sys.prefix
|
87
|
+
):
|
88
|
+
cmd[0] = sys.executable
|
89
|
+
else:
|
90
|
+
cmd[0] = sys._base_executable # type: ignore [attr-defined]
|
91
|
+
|
92
|
+
# Make sure to use the realpath for the connection_file
|
93
|
+
# On windows, when running with the store python, the connection_file path
|
94
|
+
# is not usable by non python kernels because the path is being rerouted when
|
95
|
+
# inside of a store app.
|
96
|
+
# See this bug here: https://bugs.python.org/issue41196
|
97
|
+
ns = {
|
98
|
+
"connection_file": os.path.realpath(self.connection_file),
|
99
|
+
"prefix": sys.prefix,
|
100
|
+
}
|
101
|
+
|
102
|
+
if self.kernel_spec:
|
103
|
+
ns["resource_dir"] = self.kernel_spec.resource_dir
|
104
|
+
|
105
|
+
if self._launch_args:
|
106
|
+
ns.update({str(k): str(v) for k, v in self._launch_args.items()})
|
107
|
+
|
108
|
+
pat = re.compile(r"\{([A-Za-z0-9_]+)\}")
|
109
|
+
|
110
|
+
def _from_ns(match: re.Match) -> str:
|
111
|
+
"""Get the key out of ns if it's there, otherwise no change."""
|
112
|
+
return ns.get(match.group(1), match.group())
|
113
|
+
|
114
|
+
return [pat.sub(_from_ns, arg) for arg in cmd]
|
@@ -1,12 +1,5 @@
|
|
1
1
|
"""Define collections of generic key-bindings which do not belong to widgets."""
|
2
2
|
|
3
|
-
from
|
4
|
-
basic,
|
5
|
-
completion,
|
6
|
-
micro,
|
7
|
-
mouse,
|
8
|
-
page_navigation,
|
9
|
-
vi,
|
10
|
-
)
|
3
|
+
from . import basic, completion, micro, mouse, page_navigation, vi
|
11
4
|
|
12
5
|
__all__ = ["basic", "completion", "micro", "mouse", "page_navigation", "vi"]
|
@@ -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
|
{
|
@@ -24,7 +24,7 @@ from prompt_toolkit.mouse_events import MouseButton, MouseEventType, MouseModifi
|
|
24
24
|
from prompt_toolkit.mouse_events import MouseEvent as PtkMouseEvent
|
25
25
|
from prompt_toolkit.renderer import HeightIsUnknownError
|
26
26
|
|
27
|
-
from euporie.core.app import BaseApp
|
27
|
+
from euporie.core.app.app import BaseApp
|
28
28
|
|
29
29
|
if TYPE_CHECKING:
|
30
30
|
from prompt_toolkit.key_binding.key_bindings import NotImplementedOrNone
|
@@ -155,7 +155,7 @@ def load_mouse_bindings() -> KeyBindings:
|
|
155
155
|
return NotImplemented
|
156
156
|
|
157
157
|
mouse_event = _MOUSE_EVENT_CACHE[
|
158
|
-
event.data, app.
|
158
|
+
event.data, app.term_sgr_pixel, app.cell_size_px
|
159
159
|
]
|
160
160
|
|
161
161
|
if mouse_event is None:
|
@@ -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
|
+
)
|
@@ -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, ...]] = []
|