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
@@ -4,22 +4,18 @@ from __future__ import annotations
|
|
4
4
|
|
5
5
|
import logging
|
6
6
|
from abc import ABCMeta, abstractmethod
|
7
|
+
from functools import cache
|
7
8
|
from pathlib import PurePath
|
8
9
|
from typing import TYPE_CHECKING
|
9
10
|
|
10
11
|
from prompt_toolkit.cache import SimpleCache
|
11
|
-
from prompt_toolkit.filters import buffer_has_focus
|
12
12
|
from prompt_toolkit.layout.containers import (
|
13
13
|
DynamicContainer,
|
14
14
|
to_container,
|
15
15
|
)
|
16
16
|
|
17
|
-
from euporie.core.
|
18
|
-
from euporie.core.convert.datum import Datum
|
19
|
-
from euporie.core.convert.formats import BASE64_FORMATS
|
20
|
-
from euporie.core.convert.mime import MIME_FORMATS
|
17
|
+
from euporie.core.app.current import get_app
|
21
18
|
from euporie.core.convert.registry import find_route
|
22
|
-
from euporie.core.current import get_app
|
23
19
|
from euporie.core.layout.containers import HSplit
|
24
20
|
from euporie.core.widgets.display import Display
|
25
21
|
from euporie.core.widgets.layout import Box
|
@@ -30,7 +26,7 @@ if TYPE_CHECKING:
|
|
30
26
|
|
31
27
|
from prompt_toolkit.layout.containers import AnyContainer
|
32
28
|
|
33
|
-
from euporie.core.
|
29
|
+
from euporie.core.tabs.kernel import KernelTab
|
34
30
|
|
35
31
|
KTParent = TypeVar("KTParent", bound=KernelTab)
|
36
32
|
|
@@ -98,6 +94,10 @@ class CellOutputDataElement(CellOutputElement):
|
|
98
94
|
metadata: Any metadata relating to the data
|
99
95
|
parent: The cell the output-element is attached to
|
100
96
|
"""
|
97
|
+
from euporie.core.convert.datum import Datum
|
98
|
+
from euporie.core.convert.formats import BASE64_FORMATS
|
99
|
+
from euporie.core.convert.mime import MIME_FORMATS
|
100
|
+
|
101
101
|
self.parent = parent
|
102
102
|
|
103
103
|
# Get foreground and background colors
|
@@ -129,16 +129,14 @@ class CellOutputDataElement(CellOutputElement):
|
|
129
129
|
self._datum,
|
130
130
|
focusable=False,
|
131
131
|
focus_on_click=False,
|
132
|
-
wrap_lines=config.
|
132
|
+
wrap_lines=config.filters.wrap_cell_outputs,
|
133
133
|
always_hide_cursor=True,
|
134
|
-
style=f"class:mime-{mime.replace('/','-')}",
|
134
|
+
style=f"class:mime-{mime.replace('/', '-')}",
|
135
135
|
scrollbar=False,
|
136
136
|
)
|
137
137
|
|
138
138
|
# Ensure container gets invalidated if `wrap_cell_output` changes
|
139
|
-
self.container.control.invalidate_events.append(
|
140
|
-
config.settings["wrap_cell_outputs"].event
|
141
|
-
)
|
139
|
+
self.container.control.invalidate_events.append(config.events.wrap_cell_outputs)
|
142
140
|
|
143
141
|
@property
|
144
142
|
def data(self) -> Any:
|
@@ -148,6 +146,8 @@ class CellOutputDataElement(CellOutputElement):
|
|
148
146
|
@data.setter
|
149
147
|
def data(self, value: Any) -> None:
|
150
148
|
"""Set the cell output's data."""
|
149
|
+
from euporie.core.convert.datum import Datum
|
150
|
+
|
151
151
|
self._datum = Datum(
|
152
152
|
value,
|
153
153
|
self._datum.format,
|
@@ -260,12 +260,12 @@ MIME_ORDER = [
|
|
260
260
|
]
|
261
261
|
|
262
262
|
|
263
|
-
|
263
|
+
@cache
|
264
|
+
def _calculate_mime_rank(mime: str, have_escapes: bool) -> int:
|
264
265
|
"""Score the richness of mime output types."""
|
265
|
-
mime, data = mime_data
|
266
266
|
for i, ranked_mime in enumerate(MIME_ORDER):
|
267
267
|
# Uprank plain text with escape sequences
|
268
|
-
if mime == "text/plain" and
|
268
|
+
if mime == "text/plain" and have_escapes:
|
269
269
|
i -= 7
|
270
270
|
if PurePath(mime).match(ranked_mime):
|
271
271
|
return i
|
@@ -273,6 +273,12 @@ def _calculate_mime_rank(mime_data: tuple[str, Any]) -> int:
|
|
273
273
|
return 999
|
274
274
|
|
275
275
|
|
276
|
+
def _mime_ranker(mime_data: tuple[str, Any]) -> int:
|
277
|
+
"""Score the richness of mime output types."""
|
278
|
+
mime, data = mime_data
|
279
|
+
return _calculate_mime_rank(mime, isinstance(data, str) and "\x1b[" in data)
|
280
|
+
|
281
|
+
|
276
282
|
class CellOutput:
|
277
283
|
"""Represent a single cell output.
|
278
284
|
|
@@ -319,7 +325,7 @@ class CellOutput:
|
|
319
325
|
data = {}
|
320
326
|
output_type = self.json.get("output_type", "unknown")
|
321
327
|
if output_type == "stream":
|
322
|
-
data = {f
|
328
|
+
data = {f"stream/{self.json.get('name')}": self.json.get("text", "")}
|
323
329
|
elif output_type == "error":
|
324
330
|
ename = self.json.get("ename", "")
|
325
331
|
evalue = self.json.get("evalue", "")
|
@@ -327,7 +333,7 @@ class CellOutput:
|
|
327
333
|
data = {"text/x-python-traceback": f"{ename}: {evalue}\n{traceback}"}
|
328
334
|
else:
|
329
335
|
data = self.json.get("data", {"text/plain": ""})
|
330
|
-
return dict(sorted(data.items(), key=
|
336
|
+
return dict(sorted(data.items(), key=_mime_ranker))
|
331
337
|
|
332
338
|
def update(self) -> None:
|
333
339
|
"""Update the output by updating all child containers."""
|
@@ -372,9 +378,8 @@ class CellOutput:
|
|
372
378
|
if mime not in self._elements:
|
373
379
|
element = self.make_element(mime)
|
374
380
|
self._elements[mime] = element
|
375
|
-
|
376
|
-
|
377
|
-
return element
|
381
|
+
return element
|
382
|
+
return self._elements[mime]
|
378
383
|
|
379
384
|
@property
|
380
385
|
def element(self) -> CellOutputElement:
|
@@ -511,19 +516,3 @@ class CellOutputArea:
|
|
511
516
|
):
|
512
517
|
outputs.append(to_plain_text(line))
|
513
518
|
return "\n".join(outputs)
|
514
|
-
|
515
|
-
# ################################### Settings ####################################
|
516
|
-
|
517
|
-
add_setting(
|
518
|
-
name="wrap_cell_outputs",
|
519
|
-
title="wrap cell outputs",
|
520
|
-
flags=["--wrap-cell-outputs"],
|
521
|
-
type_=bool,
|
522
|
-
help_="Wrap cell output text.",
|
523
|
-
default=False,
|
524
|
-
schema={"type": "boolean"},
|
525
|
-
description="""
|
526
|
-
Whether text-based cell outputs should be wrapped.
|
527
|
-
""",
|
528
|
-
cmd_filter=~buffer_has_focus,
|
529
|
-
)
|
euporie/core/widgets/decor.py
CHANGED
@@ -9,15 +9,12 @@ from prompt_toolkit.filters import to_filter
|
|
9
9
|
from prompt_toolkit.layout.containers import (
|
10
10
|
ConditionalContainer,
|
11
11
|
DynamicContainer,
|
12
|
-
Float,
|
13
|
-
FloatContainer,
|
14
12
|
)
|
15
13
|
|
14
|
+
from euporie.core.app.current import get_app
|
16
15
|
from euporie.core.border import ThinLine
|
17
|
-
from euporie.core.config import add_setting
|
18
|
-
from euporie.core.current import get_app
|
19
16
|
from euporie.core.data_structures import DiBool
|
20
|
-
from euporie.core.layout.containers import HSplit, VSplit, Window
|
17
|
+
from euporie.core.layout.containers import DummyContainer, HSplit, VSplit, Window
|
21
18
|
from euporie.core.layout.decor import DropShadow
|
22
19
|
|
23
20
|
if TYPE_CHECKING:
|
@@ -183,8 +180,7 @@ class Border:
|
|
183
180
|
class Shadow:
|
184
181
|
"""Draw a shadow underneath/behind this container.
|
185
182
|
|
186
|
-
|
187
|
-
:py:class:`prompt_toolkit.widows.base.Shadow` class.
|
183
|
+
The container must be in a float.
|
188
184
|
"""
|
189
185
|
|
190
186
|
def __init__(self, body: AnyContainer) -> None:
|
@@ -193,27 +189,14 @@ class Shadow:
|
|
193
189
|
Args:
|
194
190
|
body: Another container object.
|
195
191
|
"""
|
196
|
-
filter_ = get_app().config.
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
right=0,
|
205
|
-
transparent=True,
|
206
|
-
content=DropShadow(),
|
207
|
-
),
|
208
|
-
Float(
|
209
|
-
bottom=-1,
|
210
|
-
top=1,
|
211
|
-
width=1,
|
212
|
-
right=-1,
|
213
|
-
transparent=True,
|
214
|
-
content=DropShadow(),
|
215
|
-
),
|
216
|
-
],
|
192
|
+
filter_ = get_app().config.filters.show_shadows
|
193
|
+
|
194
|
+
spacer = DummyContainer(width=1, height=1)
|
195
|
+
shadow = VSplit(
|
196
|
+
[
|
197
|
+
HSplit([body, VSplit([spacer, DropShadow()])]),
|
198
|
+
HSplit([spacer, DropShadow()]),
|
199
|
+
]
|
217
200
|
)
|
218
201
|
|
219
202
|
def get_contents() -> AnyContainer:
|
@@ -227,16 +210,3 @@ class Shadow:
|
|
227
210
|
def __pt_container__(self) -> AnyContainer:
|
228
211
|
"""Return the container's content."""
|
229
212
|
return self.container
|
230
|
-
|
231
|
-
# ################################### Settings ####################################
|
232
|
-
|
233
|
-
add_setting(
|
234
|
-
name="show_shadows",
|
235
|
-
flags=["--show-shadows"],
|
236
|
-
type_=bool,
|
237
|
-
help_="Show or hide shadows under menus and dialogs",
|
238
|
-
default=True,
|
239
|
-
description="""
|
240
|
-
Sets whether shadows are shown under dialogs and popup-menus.
|
241
|
-
""",
|
242
|
-
)
|
euporie/core/widgets/dialog.py
CHANGED
@@ -3,7 +3,6 @@
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
5
|
import logging
|
6
|
-
import traceback
|
7
6
|
from abc import ABCMeta, abstractmethod
|
8
7
|
from functools import partial
|
9
8
|
from pathlib import Path
|
@@ -26,6 +25,7 @@ from prompt_toolkit.layout.containers import (
|
|
26
25
|
ConditionalContainer,
|
27
26
|
DynamicContainer,
|
28
27
|
Float,
|
28
|
+
to_container,
|
29
29
|
)
|
30
30
|
from prompt_toolkit.layout.controls import FormattedTextControl, UIContent, UIControl
|
31
31
|
from prompt_toolkit.layout.dimension import Dimension
|
@@ -33,7 +33,7 @@ from prompt_toolkit.layout.screen import WritePosition
|
|
33
33
|
from prompt_toolkit.mouse_events import MouseButton, MouseEventType
|
34
34
|
from prompt_toolkit.widgets.base import Label
|
35
35
|
|
36
|
-
from euporie.core.app import get_app
|
36
|
+
from euporie.core.app.current import get_app
|
37
37
|
from euporie.core.border import (
|
38
38
|
FullLine,
|
39
39
|
LowerLeftHalfLine,
|
@@ -45,14 +45,14 @@ from euporie.core.ft.utils import FormattedTextAlign, align, lex
|
|
45
45
|
from euporie.core.key_binding.registry import register_bindings
|
46
46
|
from euporie.core.layout.containers import HSplit, VSplit, Window
|
47
47
|
from euporie.core.layout.decor import FocusedStyle
|
48
|
-
from euporie.core.tabs.base import Tab
|
49
48
|
from euporie.core.widgets.decor import Border, Shadow
|
50
49
|
from euporie.core.widgets.file_browser import FileBrowser
|
51
50
|
from euporie.core.widgets.forms import Button, LabelledWidget, Select, Text
|
52
51
|
from euporie.core.widgets.layout import Box
|
53
52
|
|
54
53
|
if TYPE_CHECKING:
|
55
|
-
from
|
54
|
+
from collections.abc import Hashable
|
55
|
+
from typing import Any, Callable
|
56
56
|
|
57
57
|
from prompt_toolkit.buffer import Buffer
|
58
58
|
from prompt_toolkit.data_structures import Point
|
@@ -63,9 +63,9 @@ if TYPE_CHECKING:
|
|
63
63
|
from prompt_toolkit.layout.layout import FocusableElement
|
64
64
|
from prompt_toolkit.mouse_events import MouseEvent
|
65
65
|
|
66
|
-
from euporie.core.app import BaseApp
|
67
|
-
from euporie.core.tabs.base import
|
68
|
-
from euporie.core.
|
66
|
+
from euporie.core.app.app import BaseApp
|
67
|
+
from euporie.core.tabs.base import Tab
|
68
|
+
from euporie.core.tabs.kernel import KernelTab
|
69
69
|
|
70
70
|
log = logging.getLogger(__name__)
|
71
71
|
|
@@ -138,8 +138,12 @@ class DialogTitleControl(UIControl):
|
|
138
138
|
dl_width, max_y
|
139
139
|
).preferred
|
140
140
|
# Calculate new dialog position
|
141
|
-
new_x = max(
|
142
|
-
|
141
|
+
new_x = max(
|
142
|
+
1, min(gx - self.drag_start.x, max_x - dl_width + 1)
|
143
|
+
)
|
144
|
+
new_y = max(
|
145
|
+
1, min(gy - self.drag_start.y, max_y - dl_height + 1)
|
146
|
+
)
|
143
147
|
# Move dialog
|
144
148
|
self.dialog.left = new_x - 1
|
145
149
|
self.dialog.top = new_y - 1
|
@@ -189,7 +193,7 @@ class Dialog(Float, metaclass=ABCMeta):
|
|
189
193
|
# Set default body & buttons
|
190
194
|
self.body: AnyContainer = Window()
|
191
195
|
self.buttons: dict[str, Callable | None] = {"OK": None}
|
192
|
-
self.
|
196
|
+
self._button_widgets: list[AnyContainer] = []
|
193
197
|
|
194
198
|
# Create key-bindings
|
195
199
|
kb = KeyBindings()
|
@@ -225,16 +229,12 @@ class Dialog(Float, metaclass=ABCMeta):
|
|
225
229
|
)
|
226
230
|
|
227
231
|
# The buttons.
|
232
|
+
self.button_split = VSplit(self.button_widgets, padding=1)
|
228
233
|
buttons_row = ConditionalContainer(
|
229
234
|
Box(
|
230
|
-
body=
|
231
|
-
lambda: VSplit(
|
232
|
-
self.button_widgets,
|
233
|
-
padding=1,
|
234
|
-
key_bindings=DynamicKeyBindings(lambda: self.buttons_kb),
|
235
|
-
)
|
236
|
-
),
|
235
|
+
body=self.button_split,
|
237
236
|
height=Dimension(min=1, max=3, preferred=3),
|
237
|
+
key_bindings=DynamicKeyBindings(lambda: self.buttons_kb),
|
238
238
|
),
|
239
239
|
filter=Condition(lambda: bool(self.buttons)),
|
240
240
|
)
|
@@ -263,6 +263,16 @@ class Dialog(Float, metaclass=ABCMeta):
|
|
263
263
|
# Set the body as the float's contents
|
264
264
|
super().__init__(content=self.container)
|
265
265
|
|
266
|
+
@property
|
267
|
+
def button_widgets(self) -> list[AnyContainer]:
|
268
|
+
"""A list of button widgets to show in the dialog's row of buttons."""
|
269
|
+
return self._button_widgets
|
270
|
+
|
271
|
+
@button_widgets.setter
|
272
|
+
def button_widgets(self, value: list[AnyContainer]) -> None:
|
273
|
+
self._button_widgets = list(value)
|
274
|
+
self.button_split.children = [to_container(c) for c in value]
|
275
|
+
|
266
276
|
def _button_handler(
|
267
277
|
self, button: str = "", event: KeyPressEvent | None = None
|
268
278
|
) -> None:
|
@@ -280,8 +290,7 @@ class Dialog(Float, metaclass=ABCMeta):
|
|
280
290
|
self.load(**params)
|
281
291
|
|
282
292
|
# Create button widgets & callbacks
|
283
|
-
|
284
|
-
|
293
|
+
new_button_widgets: list[AnyContainer] = []
|
285
294
|
if self.buttons:
|
286
295
|
width = max(map(len, self.buttons)) + 2
|
287
296
|
used_keys = set()
|
@@ -296,7 +305,7 @@ class Dialog(Float, metaclass=ABCMeta):
|
|
296
305
|
rest = text
|
297
306
|
# Add a button with a handler
|
298
307
|
handler = partial(self._button_handler, text)
|
299
|
-
|
308
|
+
new_button_widgets.append(
|
300
309
|
FocusedStyle(
|
301
310
|
Button(
|
302
311
|
[("underline", key), ("", rest)],
|
@@ -309,6 +318,7 @@ class Dialog(Float, metaclass=ABCMeta):
|
|
309
318
|
# Add a key-handler
|
310
319
|
if key:
|
311
320
|
self.buttons_kb.add(f"A-{key.lower()}", is_global=True)(handler)
|
321
|
+
self.button_widgets = new_button_widgets
|
312
322
|
|
313
323
|
# When a button is selected, handle left/right key bindings.
|
314
324
|
if len(self.button_widgets) > 1:
|
@@ -333,15 +343,13 @@ class Dialog(Float, metaclass=ABCMeta):
|
|
333
343
|
self._load(**params)
|
334
344
|
self.last_focused = self.app.layout.current_control
|
335
345
|
self._visible = True
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
self.app.layout.focus(self.container)
|
344
|
-
self.app.invalidate()
|
346
|
+
to_focus = self.to_focus or self.container
|
347
|
+
try:
|
348
|
+
self.app.layout.focus(to_focus)
|
349
|
+
except ValueError:
|
350
|
+
pass
|
351
|
+
finally:
|
352
|
+
self.app.invalidate()
|
345
353
|
|
346
354
|
def hide(self, event: KeyPressEvent | None = None) -> None:
|
347
355
|
"""Hide the dialog."""
|
@@ -404,7 +412,7 @@ class AboutDialog(Dialog):
|
|
404
412
|
@add_cmd()
|
405
413
|
def _about() -> None:
|
406
414
|
"""Show the about dialog."""
|
407
|
-
from euporie.core.current import get_app
|
415
|
+
from euporie.core.app.current import get_app
|
408
416
|
|
409
417
|
if dialog := get_app().dialogs.get("about"):
|
410
418
|
dialog.toggle()
|
@@ -493,12 +501,13 @@ class OpenFileDialog(FileDialog):
|
|
493
501
|
|
494
502
|
self.tab_dd = tab_dd = Dropdown(options=[])
|
495
503
|
|
496
|
-
def _update_options(
|
497
|
-
tabs = get_app().get_file_tabs(path) if
|
504
|
+
def _update_options(path: Path) -> None:
|
505
|
+
tabs = get_app().get_file_tabs(path) if path.is_file() else []
|
498
506
|
tab_dd.options = tabs
|
499
507
|
tab_dd.labels = [tab.name for tab in tabs]
|
500
508
|
|
501
|
-
self.file_browser.control.on_select += _update_options
|
509
|
+
self.file_browser.control.on_select += lambda fb: _update_options(fb.path)
|
510
|
+
self.filepath.buffer.on_text_changed += lambda b: _update_options(Path(b.text))
|
502
511
|
|
503
512
|
if isinstance(self.body, HSplit):
|
504
513
|
self.body.children.append(
|
@@ -538,7 +547,7 @@ class OpenFileDialog(FileDialog):
|
|
538
547
|
self.file_browser.control.dir = path
|
539
548
|
elif path.is_file():
|
540
549
|
self.hide()
|
541
|
-
self.app.open_file(path, tab_class=self.tab_dd.value)
|
550
|
+
self.app.open_file(path, tab_class=self.tab_dd.value.tab_class)
|
542
551
|
return
|
543
552
|
else:
|
544
553
|
self.show(
|
@@ -551,7 +560,7 @@ class OpenFileDialog(FileDialog):
|
|
551
560
|
@add_cmd(menu_title="Open File…")
|
552
561
|
def _open_file() -> None:
|
553
562
|
"""Open a file."""
|
554
|
-
from euporie.core.current import get_app
|
563
|
+
from euporie.core.app.current import get_app
|
555
564
|
|
556
565
|
if dialog := get_app().dialogs.get("open-file"):
|
557
566
|
dialog.show()
|
@@ -560,7 +569,7 @@ class OpenFileDialog(FileDialog):
|
|
560
569
|
|
561
570
|
register_bindings(
|
562
571
|
{
|
563
|
-
"euporie.core.app.BaseApp": {
|
572
|
+
"euporie.core.app.app:BaseApp": {
|
564
573
|
"open-file": "c-o",
|
565
574
|
}
|
566
575
|
}
|
@@ -597,7 +606,7 @@ class SaveAsDialog(FileDialog):
|
|
597
606
|
)
|
598
607
|
def _save_as() -> None:
|
599
608
|
"""Save the current file at a new location."""
|
600
|
-
from euporie.core.current import get_app
|
609
|
+
from euporie.core.app.current import get_app
|
601
610
|
|
602
611
|
app = get_app()
|
603
612
|
if dialog := app.dialogs.get("save-as"):
|
@@ -607,7 +616,7 @@ class SaveAsDialog(FileDialog):
|
|
607
616
|
|
608
617
|
register_bindings(
|
609
618
|
{
|
610
|
-
"euporie.core.app.BaseApp": {
|
619
|
+
"euporie.core.app.app:BaseApp": {
|
611
620
|
"save-as": ("A-s"),
|
612
621
|
}
|
613
622
|
}
|
@@ -768,6 +777,8 @@ class ErrorDialog(Dialog):
|
|
768
777
|
|
769
778
|
def load(self, exception: Exception | None = None, when: str = "") -> None:
|
770
779
|
"""Load dialog body & buttons."""
|
780
|
+
import traceback
|
781
|
+
|
771
782
|
from euporie.core.margins import MarginContainer, ScrollbarMargin
|
772
783
|
from euporie.core.widgets.formatted_text_area import FormattedTextArea
|
773
784
|
from euporie.core.widgets.forms import Checkbox
|
@@ -793,7 +804,7 @@ class ErrorDialog(Dialog):
|
|
793
804
|
("bold", f" when {when}" if when else ""),
|
794
805
|
("bold", ":"),
|
795
806
|
("", "\n\n"),
|
796
|
-
("fg:ansired", exception
|
807
|
+
("fg:ansired", repr(exception)),
|
797
808
|
("", "\n"),
|
798
809
|
],
|
799
810
|
)
|
@@ -854,6 +865,8 @@ class UnsavedDialog(Dialog):
|
|
854
865
|
tab.save(cb=partial(tab.close, cb))
|
855
866
|
|
856
867
|
def no_cb() -> None:
|
868
|
+
from euporie.core.tabs.base import Tab
|
869
|
+
|
857
870
|
assert tab is not None
|
858
871
|
self.hide()
|
859
872
|
Tab.close(tab, cb)
|
@@ -898,7 +911,7 @@ class ShortcutsDialog(Dialog):
|
|
898
911
|
|
899
912
|
def format_key_info(self) -> StyleAndTextTuples:
|
900
913
|
"""Generate a table with the current key bindings."""
|
901
|
-
import
|
914
|
+
import pkgutil
|
902
915
|
from textwrap import dedent
|
903
916
|
|
904
917
|
from prompt_toolkit.formatted_text.base import to_formatted_text
|
@@ -915,9 +928,7 @@ class ShortcutsDialog(Dialog):
|
|
915
928
|
|
916
929
|
for group, bindings in BINDINGS.items():
|
917
930
|
if any(not get_cmd(cmd_name).hidden() for cmd_name in bindings):
|
918
|
-
|
919
|
-
mod = importlib.import_module(mod_name)
|
920
|
-
app_cls = getattr(mod, cls_name)
|
931
|
+
app_cls = pkgutil.resolve_name(group)
|
921
932
|
section_title = (
|
922
933
|
dedent(app_cls.__doc__).strip().split("\n")[0].rstrip(".")
|
923
934
|
)
|
@@ -960,7 +971,7 @@ class ShortcutsDialog(Dialog):
|
|
960
971
|
@add_cmd()
|
961
972
|
def _keyboard_shortcuts() -> None:
|
962
973
|
"""Display details of registered key-bindings in a dialog."""
|
963
|
-
from euporie.core.current import get_app
|
974
|
+
from euporie.core.app.current import get_app
|
964
975
|
|
965
976
|
if dialog := get_app().dialogs.get("shortcuts"):
|
966
977
|
dialog.toggle()
|
euporie/core/widgets/display.py
CHANGED
@@ -16,13 +16,10 @@ from prompt_toolkit.layout.controls import GetLinePrefixCallable, UIContent, UIC
|
|
16
16
|
from prompt_toolkit.mouse_events import MouseEvent, MouseEventType
|
17
17
|
from prompt_toolkit.utils import Event, to_str
|
18
18
|
|
19
|
+
from euporie.core.app.current import get_app
|
19
20
|
from euporie.core.commands import add_cmd
|
20
21
|
from euporie.core.convert.datum import Datum
|
21
|
-
from euporie.core.
|
22
|
-
from euporie.core.filters import (
|
23
|
-
display_has_focus,
|
24
|
-
scrollable,
|
25
|
-
)
|
22
|
+
from euporie.core.filters import display_has_focus, scrollable
|
26
23
|
from euporie.core.ft.utils import wrap
|
27
24
|
from euporie.core.graphics import GraphicProcessor
|
28
25
|
from euporie.core.key_binding.registry import (
|
@@ -34,7 +31,8 @@ from euporie.core.margins import MarginContainer, ScrollbarMargin
|
|
34
31
|
from euporie.core.utils import run_in_thread_with_context
|
35
32
|
|
36
33
|
if TYPE_CHECKING:
|
37
|
-
from
|
34
|
+
from collections.abc import Iterable
|
35
|
+
from typing import Any, Callable
|
38
36
|
|
39
37
|
from prompt_toolkit.filters import FilterOrBool
|
40
38
|
from prompt_toolkit.formatted_text import StyleAndTextTuples
|
@@ -89,7 +87,8 @@ class DisplayControl(UIControl):
|
|
89
87
|
self.height = 0
|
90
88
|
|
91
89
|
self.key_bindings = load_registered_bindings(
|
92
|
-
"euporie.core.widgets.display.DisplayControl"
|
90
|
+
"euporie.core.widgets.display.DisplayControl",
|
91
|
+
config=get_app().config,
|
93
92
|
)
|
94
93
|
|
95
94
|
self.rendered = Event(self)
|
@@ -159,7 +158,7 @@ class DisplayControl(UIControl):
|
|
159
158
|
extend=not self.dont_extend_width(),
|
160
159
|
)
|
161
160
|
if width and height:
|
162
|
-
key = Datum.add_size(datum, Size(height,
|
161
|
+
key = Datum.add_size(datum, Size(height, width))
|
163
162
|
ft = [(f"[Graphic_{key}]", ""), *ft]
|
164
163
|
lines = list(split_lines(ft))
|
165
164
|
if wrap_lines and width:
|
@@ -196,13 +195,14 @@ class DisplayControl(UIControl):
|
|
196
195
|
)
|
197
196
|
|
198
197
|
def render(self) -> None:
|
199
|
-
"""Render the
|
198
|
+
"""Render the content in a thread."""
|
200
199
|
datum = self.datum
|
201
200
|
wrap_lines = self.wrap_lines()
|
202
201
|
|
203
|
-
|
204
|
-
|
205
|
-
|
202
|
+
cols = self.preferred_width(self.width)
|
203
|
+
rows = self.preferred_height(
|
204
|
+
self.width, self.height, wrap_lines=wrap_lines, get_line_prefix=None
|
205
|
+
)
|
206
206
|
|
207
207
|
def _render() -> None:
|
208
208
|
cp = self.color_palette
|
@@ -226,12 +226,7 @@ class DisplayControl(UIControl):
|
|
226
226
|
|
227
227
|
def preferred_width(self, max_available_width: int) -> int | None:
|
228
228
|
"""Calculate and return the preferred width of the control."""
|
229
|
-
|
230
|
-
if max_cols:
|
231
|
-
return min(max_cols, max_available_width)
|
232
|
-
return self._max_line_width_cache[
|
233
|
-
self.datum, max_available_width, None, self.wrap_lines()
|
234
|
-
]
|
229
|
+
return max_available_width
|
235
230
|
|
236
231
|
def preferred_height(
|
237
232
|
self,
|
@@ -241,12 +236,18 @@ class DisplayControl(UIControl):
|
|
241
236
|
get_line_prefix: GetLinePrefixCallable | None,
|
242
237
|
) -> int | None:
|
243
238
|
"""Calculate and return the preferred height of the control."""
|
239
|
+
height = None
|
244
240
|
max_cols, aspect = self.datum.cell_size()
|
245
241
|
if aspect:
|
246
|
-
|
242
|
+
height = ceil(min(width, max_cols) * aspect)
|
247
243
|
cp = self.color_palette
|
248
244
|
self.lines = self._line_cache[
|
249
|
-
self.datum,
|
245
|
+
self.datum,
|
246
|
+
width,
|
247
|
+
height,
|
248
|
+
cp.fg.base_hex,
|
249
|
+
cp.bg.base_hex,
|
250
|
+
self.wrap_lines(),
|
250
251
|
]
|
251
252
|
return len(self.lines)
|
252
253
|
|
@@ -299,17 +300,19 @@ class DisplayControl(UIControl):
|
|
299
300
|
A :py:class:`UIContent` instance.
|
300
301
|
"""
|
301
302
|
# Trigger a re-render in the future if things have changed
|
303
|
+
render = False
|
302
304
|
if self.loading:
|
303
|
-
|
304
|
-
if width != self.width:
|
305
|
+
render = True
|
306
|
+
if width != self.width or height != self.height:
|
305
307
|
self.resizing = True
|
306
308
|
self.width = width
|
307
309
|
self.height = height
|
308
|
-
|
310
|
+
render = True
|
309
311
|
if (cp := get_app().color_palette) != self.color_palette:
|
310
312
|
self.color_palette = cp
|
313
|
+
render = True
|
314
|
+
if render:
|
311
315
|
self.render()
|
312
|
-
|
313
316
|
content = self._content_cache[
|
314
317
|
self.datum, width, height, self.loading, self.cursor_position, cp
|
315
318
|
]
|