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
euporie/core/io.py
CHANGED
@@ -5,40 +5,115 @@ from __future__ import annotations
|
|
5
5
|
import logging
|
6
6
|
import re
|
7
7
|
from base64 import b64encode
|
8
|
-
from
|
8
|
+
from functools import lru_cache
|
9
|
+
from typing import TYPE_CHECKING, cast
|
9
10
|
|
10
11
|
from prompt_toolkit.input import vt100_parser
|
12
|
+
from prompt_toolkit.input.ansi_escape_sequences import ANSI_SEQUENCES
|
11
13
|
from prompt_toolkit.input.base import DummyInput, _dummy_context_manager
|
14
|
+
from prompt_toolkit.output.color_depth import ColorDepth
|
12
15
|
from prompt_toolkit.output.vt100 import Vt100_Output as PtkVt100_Output
|
13
16
|
|
17
|
+
from euporie.core.app.current import get_app
|
18
|
+
from euporie.core.filters import in_screen, in_tmux
|
19
|
+
|
14
20
|
if TYPE_CHECKING:
|
15
|
-
from
|
21
|
+
from contextlib import AbstractContextManager
|
22
|
+
from typing import IO, Any, Callable, TextIO
|
16
23
|
|
17
24
|
from prompt_toolkit.keys import Keys
|
18
25
|
|
26
|
+
from euporie.core.config import Config
|
27
|
+
|
19
28
|
log = logging.getLogger(__name__)
|
20
29
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
30
|
+
COLOR_DEPTHS = {
|
31
|
+
1: ColorDepth.DEPTH_1_BIT,
|
32
|
+
4: ColorDepth.DEPTH_4_BIT,
|
33
|
+
8: ColorDepth.DEPTH_8_BIT,
|
34
|
+
24: ColorDepth.DEPTH_24_BIT,
|
35
|
+
}
|
36
|
+
|
37
|
+
|
38
|
+
@lru_cache
|
39
|
+
def _have_termios_tty_fcntl() -> bool:
|
40
|
+
try:
|
41
|
+
import fcntl # noqa F401
|
42
|
+
import termios # noqa F401
|
43
|
+
import tty # noqa F401
|
44
|
+
except ModuleNotFoundError:
|
45
|
+
return False
|
46
|
+
else:
|
47
|
+
return True
|
48
|
+
|
49
|
+
|
50
|
+
def _tiocgwinsz() -> tuple[int, int, int, int]:
|
51
|
+
"""Get the size and pixel dimensions of the terminal with `termios`."""
|
52
|
+
import array
|
53
|
+
|
54
|
+
output = array.array("H", [0, 0, 0, 0])
|
55
|
+
if _have_termios_tty_fcntl():
|
56
|
+
import fcntl
|
57
|
+
import termios
|
58
|
+
|
59
|
+
try:
|
60
|
+
fcntl.ioctl(1, termios.TIOCGWINSZ, output)
|
61
|
+
except OSError:
|
62
|
+
pass
|
63
|
+
rows, cols, xpixels, ypixels = output
|
64
|
+
return rows, cols, xpixels, ypixels
|
65
|
+
|
66
|
+
|
67
|
+
def passthrough(cmd: str, config: Config | None = None) -> str:
|
68
|
+
"""Wrap an escape sequence for terminal passthrough."""
|
69
|
+
config = config or get_app().config
|
70
|
+
if config.multiplexer_passthrough:
|
71
|
+
if in_tmux():
|
72
|
+
cmd = cmd.replace("\x1b", "\x1b\x1b")
|
73
|
+
cmd = f"\x1bPtmux;{cmd}\x1b\\"
|
74
|
+
elif in_screen():
|
75
|
+
# Screen limits escape sequences to 768 bytes, so we have to chunk it
|
76
|
+
cmd = "".join(
|
77
|
+
f"\x1bP{cmd[i : i + 764]}\x1b\\" for i in range(0, len(cmd), 764)
|
78
|
+
)
|
79
|
+
return cmd
|
33
80
|
|
34
81
|
|
35
82
|
class _IsPrefixOfLongerMatchCache(vt100_parser._IsPrefixOfLongerMatchCache):
|
83
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
84
|
+
super().__init__(*args, **kwargs)
|
85
|
+
# Pattern for any ANSI escape sequence
|
86
|
+
self._response_prefix_re = re.compile(
|
87
|
+
r"""^\x1b(
|
88
|
+
\][^\\\x07]* # Operating System Commands
|
89
|
+
|
|
90
|
+
_[^\\]* # Application Program Command
|
91
|
+
|
|
92
|
+
\[\?[\d;]* # Primary device attribute responses
|
93
|
+
|
|
94
|
+
P[ -~]*(\x1b|x1b\\)?
|
95
|
+
)\Z""",
|
96
|
+
re.VERBOSE,
|
97
|
+
)
|
98
|
+
# Generate prefix matches for all known ansi escape sequences
|
99
|
+
# This is faster than PTK's method
|
100
|
+
self._ansi_sequence_prefixes = {
|
101
|
+
seq[:i] for seq in ANSI_SEQUENCES for i in range(len(seq))
|
102
|
+
}
|
103
|
+
|
36
104
|
def __missing__(self, prefix: str) -> bool:
|
37
105
|
"""Check if the response might match an OSC or APC code, or DA response."""
|
38
|
-
result =
|
39
|
-
|
40
|
-
|
41
|
-
|
106
|
+
result = bool(
|
107
|
+
# (hard coded) If this could be a prefix of a CPR response, return True.
|
108
|
+
vt100_parser._cpr_response_prefix_re.match(prefix)
|
109
|
+
# True if this could be a mouse event sequence
|
110
|
+
or vt100_parser._mouse_event_prefix_re.match(prefix)
|
111
|
+
# True if this could be the prefix of an expected escape sequence
|
112
|
+
or prefix in self._ansi_sequence_prefixes
|
113
|
+
# If this could be a prefix of any other escape sequence, return True
|
114
|
+
or self._response_prefix_re.match(prefix)
|
115
|
+
)
|
116
|
+
self[prefix] = result
|
42
117
|
return result
|
43
118
|
|
44
119
|
|
@@ -51,14 +126,39 @@ class Vt100Parser(vt100_parser.Vt100Parser):
|
|
51
126
|
|
52
127
|
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
53
128
|
"""Create a new VT100 parser."""
|
129
|
+
from euporie.core.keys import MoreKeys
|
130
|
+
|
54
131
|
super().__init__(*args, **kwargs)
|
55
|
-
self.
|
132
|
+
self.patterns: dict[Keys | MoreKeys, re.Pattern] = {
|
133
|
+
MoreKeys.ColorsResponse: re.compile(
|
134
|
+
r"^\x1b\](?P<c>(\d+;)?\d+)+;rgb:"
|
135
|
+
r"(?P<r>[0-9A-Fa-f]{2,4})\/"
|
136
|
+
r"(?P<g>[0-9A-Fa-f]{2,4})\/"
|
137
|
+
r"(?P<b>[0-9A-Fa-f]{2,4})"
|
138
|
+
# Allow BEL or ST as terminator
|
139
|
+
r"(?:\x1b\\|\x9c|\x07)"
|
140
|
+
),
|
141
|
+
MoreKeys.PixelSizeResponse: re.compile(r"^\x1b\[4;(?P<y>\d+);(?P<x>\d+)t"),
|
142
|
+
MoreKeys.KittyGraphicsStatusResponse: re.compile(
|
143
|
+
r"^\x1b_Gi=(4294967295|0);(?P<status>OK)\x1b\\"
|
144
|
+
),
|
145
|
+
MoreKeys.SixelGraphicsStatusResponse: re.compile(
|
146
|
+
r"^\x1b\[\?(?:\d+;)*(?P<sixel>4)(?:;\d+)*c"
|
147
|
+
),
|
148
|
+
MoreKeys.ItermGraphicsStatusResponse: re.compile(
|
149
|
+
r"^\x1bP>\|(?P<term>[^\x1b]+)\x1b\\"
|
150
|
+
),
|
151
|
+
MoreKeys.SgrPixelStatusResponse: re.compile(r"^\x1b\[\?1016;(?P<Pm>\d)\$"),
|
152
|
+
MoreKeys.ClipboardDataResponse: re.compile(
|
153
|
+
r"^\x1b\]52;(?:c|p)?;(?P<data>[A-Za-z0-9+/=]+)\x1b\\"
|
154
|
+
),
|
155
|
+
}
|
56
156
|
|
57
157
|
def _get_match(self, prefix: str) -> None | Keys | tuple[Keys, ...]:
|
58
158
|
"""Check for additional key matches first."""
|
59
|
-
for key, pattern in self.
|
159
|
+
for key, pattern in self.patterns.items():
|
60
160
|
if pattern.match(prefix):
|
61
|
-
return key
|
161
|
+
return cast("Keys", key)
|
62
162
|
|
63
163
|
return super()._get_match(prefix)
|
64
164
|
|
@@ -66,7 +166,9 @@ class Vt100Parser(vt100_parser.Vt100Parser):
|
|
66
166
|
class IgnoredInput(DummyInput):
|
67
167
|
"""An input which ignores input but does not immediately close the app."""
|
68
168
|
|
69
|
-
def attach(
|
169
|
+
def attach(
|
170
|
+
self, input_ready_callback: Callable[[], None]
|
171
|
+
) -> AbstractContextManager[None]:
|
70
172
|
"""Do not call the callback, so the input is never closed."""
|
71
173
|
return _dummy_context_manager()
|
72
174
|
|
@@ -74,14 +176,12 @@ class IgnoredInput(DummyInput):
|
|
74
176
|
class Vt100_Output(PtkVt100_Output):
|
75
177
|
"""A Vt100 output which enables SGR pixel mouse positioning."""
|
76
178
|
|
77
|
-
def
|
78
|
-
"""
|
79
|
-
super().enable_mouse_support()
|
179
|
+
def enable_sgr_pixel(self) -> None:
|
180
|
+
"""Enable SGR-pixel mouse positioning."""
|
80
181
|
self.write_raw("\x1b[?1016h")
|
81
182
|
|
82
|
-
def
|
83
|
-
"""
|
84
|
-
super().disable_mouse_support()
|
183
|
+
def disable_sgr_pixel(self) -> None:
|
184
|
+
"""Disable SGR-pixel mouse positioning."""
|
85
185
|
self.write_raw("\x1b[?1016l")
|
86
186
|
|
87
187
|
def enable_private_sixel_colors(self) -> None:
|
@@ -115,6 +215,44 @@ class Vt100_Output(PtkVt100_Output):
|
|
115
215
|
"""Get clipboard contents using OSC-52."""
|
116
216
|
self.write_raw("\x1b]52;c;?\x1b\\")
|
117
217
|
|
218
|
+
def get_colors(self) -> None:
|
219
|
+
"""Query terminal colors."""
|
220
|
+
self.write_raw(
|
221
|
+
passthrough(
|
222
|
+
("\x1b]10;?\x1b\\\x1b]11;?\x1b\\")
|
223
|
+
+ "".join(f"\x1b]4;{i};?\x1b\\" for i in range(16))
|
224
|
+
)
|
225
|
+
)
|
226
|
+
|
227
|
+
def get_pixel_size(self) -> None:
|
228
|
+
"""Check the terminal's dimensions in pixels."""
|
229
|
+
self.write_raw("\x1b[14t")
|
230
|
+
|
231
|
+
def get_kitty_graphics_status(self) -> None:
|
232
|
+
"""Query terminal to check for kitty graphics support."""
|
233
|
+
self.write_raw(
|
234
|
+
"\x1b[s"
|
235
|
+
+ passthrough("\x1b_gi=4294967295,s=1,v=1,a=q,t=d,f=24;aaaa\x1b\\")
|
236
|
+
+ "\x1b[u\x1b[2k"
|
237
|
+
)
|
238
|
+
|
239
|
+
def get_sixel_graphics_status(self) -> None:
|
240
|
+
"""Query terminal for sixel graphics support."""
|
241
|
+
self.write_raw(passthrough("\x1b[c"))
|
242
|
+
|
243
|
+
def get_iterm_graphics_status(self) -> None:
|
244
|
+
"""Query terminal for iTerm graphics support."""
|
245
|
+
self.write_raw(passthrough("\x1b[>q"))
|
246
|
+
|
247
|
+
def get_sgr_pixel_status(self) -> None:
|
248
|
+
"""Query terminal to check for Pixel SGR support."""
|
249
|
+
# Enable, check, disable
|
250
|
+
self.write_raw("\x1b[?1016h\x1b[?1016$p\x1b[?1016l")
|
251
|
+
|
252
|
+
def get_csiu_status(self) -> None:
|
253
|
+
"""Query terminal to check for CSI-u support."""
|
254
|
+
self.write_raw("\x1b[?u")
|
255
|
+
|
118
256
|
|
119
257
|
class PseudoTTY:
|
120
258
|
"""Make an output stream look like a TTY."""
|
@@ -139,3 +277,44 @@ class PseudoTTY:
|
|
139
277
|
def __getattr__(self, name: str) -> Any:
|
140
278
|
"""Return an attribute of the wrappeed stream."""
|
141
279
|
return getattr(self._underlying, name)
|
280
|
+
|
281
|
+
|
282
|
+
def edit_in_editor(filename: str, line_number: int = 0) -> None:
|
283
|
+
"""Suspend the current app and edit a file in an external editor."""
|
284
|
+
import os
|
285
|
+
import shlex
|
286
|
+
import subprocess
|
287
|
+
|
288
|
+
from prompt_toolkit.application.run_in_terminal import run_in_terminal
|
289
|
+
|
290
|
+
def _open_file_in_editor(filename: str) -> None:
|
291
|
+
"""Call editor executable."""
|
292
|
+
# If the 'VISUAL' or 'EDITOR' environment variable has been set, use that.
|
293
|
+
# Otherwise, fall back to the first available editor that we can find.
|
294
|
+
for editor in [
|
295
|
+
os.environ.get("VISUAL"),
|
296
|
+
os.environ.get("EDITOR"),
|
297
|
+
"editor",
|
298
|
+
"micro",
|
299
|
+
"nano",
|
300
|
+
"pico",
|
301
|
+
"vi",
|
302
|
+
"emacs",
|
303
|
+
]:
|
304
|
+
if editor:
|
305
|
+
try:
|
306
|
+
# Use 'shlex.split()' because $VISUAL can contain spaces and quotes
|
307
|
+
subprocess.call([*shlex.split(editor), filename])
|
308
|
+
return
|
309
|
+
except OSError:
|
310
|
+
# Executable does not exist, try the next one.
|
311
|
+
pass
|
312
|
+
|
313
|
+
async def run() -> None:
|
314
|
+
# Open in editor
|
315
|
+
# (We need to use `run_in_terminal`, because not all editors go to
|
316
|
+
# the alternate screen buffer, and some could influence the cursor
|
317
|
+
# position)
|
318
|
+
await run_in_terminal(lambda: _open_file_in_editor(filename), in_executor=True)
|
319
|
+
|
320
|
+
get_app().create_background_task(run())
|
@@ -0,0 +1 @@
|
|
1
|
+
"""Concerns the interaction with Jupyter kernels."""
|