euporie 2.8.5__py3-none-any.whl → 2.8.7__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/app.py +2 -0
- euporie/console/tabs/console.py +27 -17
- euporie/core/__init__.py +2 -2
- euporie/core/__main__.py +2 -2
- euporie/core/_settings.py +7 -2
- euporie/core/app/_commands.py +20 -12
- euporie/core/app/_settings.py +34 -4
- euporie/core/app/app.py +31 -18
- euporie/core/bars/command.py +53 -27
- euporie/core/bars/search.py +43 -2
- euporie/core/border.py +7 -2
- euporie/core/comm/base.py +2 -2
- euporie/core/comm/ipywidgets.py +3 -3
- euporie/core/commands.py +44 -24
- euporie/core/completion.py +14 -6
- euporie/core/convert/datum.py +7 -7
- euporie/core/data_structures.py +20 -1
- euporie/core/filters.py +40 -9
- euporie/core/format.py +2 -3
- euporie/core/ft/html.py +47 -40
- euporie/core/graphics.py +199 -31
- euporie/core/history.py +15 -5
- euporie/core/inspection.py +16 -9
- euporie/core/kernel/__init__.py +53 -1
- euporie/core/kernel/base.py +571 -0
- euporie/core/kernel/{client.py → jupyter.py} +173 -430
- euporie/core/kernel/{manager.py → jupyter_manager.py} +4 -3
- euporie/core/kernel/local.py +694 -0
- euporie/core/key_binding/bindings/basic.py +6 -3
- euporie/core/keys.py +26 -25
- euporie/core/layout/cache.py +31 -7
- euporie/core/layout/containers.py +88 -13
- euporie/core/layout/scroll.py +69 -170
- euporie/core/log.py +2 -5
- euporie/core/path.py +61 -13
- euporie/core/style.py +2 -1
- euporie/core/suggest.py +155 -74
- euporie/core/tabs/__init__.py +12 -4
- euporie/core/tabs/_commands.py +76 -0
- euporie/core/tabs/_settings.py +16 -0
- euporie/core/tabs/base.py +89 -9
- euporie/core/tabs/kernel.py +83 -38
- euporie/core/tabs/notebook.py +28 -76
- euporie/core/utils.py +2 -19
- euporie/core/validation.py +8 -8
- euporie/core/widgets/_settings.py +19 -2
- euporie/core/widgets/cell.py +32 -32
- euporie/core/widgets/cell_outputs.py +10 -1
- euporie/core/widgets/dialog.py +60 -76
- euporie/core/widgets/display.py +2 -2
- euporie/core/widgets/forms.py +71 -59
- euporie/core/widgets/inputs.py +7 -4
- euporie/core/widgets/layout.py +281 -93
- euporie/core/widgets/menu.py +56 -16
- euporie/core/widgets/palette.py +3 -1
- euporie/core/widgets/tree.py +86 -76
- euporie/notebook/app.py +35 -16
- euporie/notebook/tabs/display.py +2 -2
- euporie/notebook/tabs/edit.py +11 -46
- euporie/notebook/tabs/json.py +8 -4
- euporie/notebook/tabs/notebook.py +26 -8
- euporie/preview/tabs/notebook.py +17 -13
- euporie/web/__init__.py +1 -0
- euporie/web/tabs/__init__.py +14 -0
- euporie/web/tabs/web.py +30 -5
- euporie/web/widgets/__init__.py +1 -0
- euporie/web/widgets/webview.py +5 -4
- {euporie-2.8.5.dist-info → euporie-2.8.7.dist-info}/METADATA +4 -2
- {euporie-2.8.5.dist-info → euporie-2.8.7.dist-info}/RECORD +74 -68
- {euporie-2.8.5.dist-info → euporie-2.8.7.dist-info}/entry_points.txt +1 -1
- {euporie-2.8.5.dist-info → euporie-2.8.7.dist-info}/licenses/LICENSE +1 -1
- {euporie-2.8.5.data → euporie-2.8.7.data}/data/share/applications/euporie-console.desktop +0 -0
- {euporie-2.8.5.data → euporie-2.8.7.data}/data/share/applications/euporie-notebook.desktop +0 -0
- {euporie-2.8.5.dist-info → euporie-2.8.7.dist-info}/WHEEL +0 -0
euporie/core/comm/ipywidgets.py
CHANGED
@@ -19,7 +19,7 @@ from prompt_toolkit.layout.processors import BeforeInput
|
|
19
19
|
|
20
20
|
from euporie.core.comm.base import Comm, CommView
|
21
21
|
from euporie.core.data_structures import DiBool
|
22
|
-
from euporie.core.kernel.
|
22
|
+
from euporie.core.kernel.jupyter import MsgCallbacks
|
23
23
|
from euporie.core.layout.decor import FocusedStyle
|
24
24
|
from euporie.core.widgets.forms import (
|
25
25
|
Button,
|
@@ -518,7 +518,7 @@ class TextBoxIpyWidgetComm(IpyWidgetComm, metaclass=ABCMeta):
|
|
518
518
|
container,
|
519
519
|
setters={
|
520
520
|
"value": lambda x: setattr(text.buffer, "text", str(x)),
|
521
|
-
"rows": partial(setattr, text.
|
521
|
+
"rows": partial(setattr, text.window, "height"),
|
522
522
|
"placeholder": partial(setattr, text, "placeholder"),
|
523
523
|
"description_allow_html": partial(setattr, labelled_widget, "html"),
|
524
524
|
},
|
@@ -1403,7 +1403,7 @@ class ColorPickerModel(TextBoxIpyWidgetComm):
|
|
1403
1403
|
container,
|
1404
1404
|
setters={
|
1405
1405
|
"value": lambda x: setattr(text.buffer, "text", str(x)),
|
1406
|
-
"rows": partial(setattr, text.
|
1406
|
+
"rows": partial(setattr, text.window, "height"),
|
1407
1407
|
"placeholder": partial(setattr, text, "placeholder"),
|
1408
1408
|
},
|
1409
1409
|
)
|
euporie/core/commands.py
CHANGED
@@ -41,6 +41,36 @@ if TYPE_CHECKING:
|
|
41
41
|
log = logging.getLogger(__name__)
|
42
42
|
|
43
43
|
|
44
|
+
def parse_args(arg: str) -> list[Any]:
|
45
|
+
"""Parse a command argument string into a list of values.
|
46
|
+
|
47
|
+
Args:
|
48
|
+
arg: The argument string to parse
|
49
|
+
|
50
|
+
Returns:
|
51
|
+
A list of parsed values, with strings for items that couldn't be evaluated
|
52
|
+
"""
|
53
|
+
if not arg:
|
54
|
+
return []
|
55
|
+
|
56
|
+
import ast
|
57
|
+
|
58
|
+
result = []
|
59
|
+
for item in arg.split():
|
60
|
+
try:
|
61
|
+
# Safely evaluate string as a Python literal
|
62
|
+
new_value = ast.literal_eval(item)
|
63
|
+
except (ValueError, SyntaxError):
|
64
|
+
# Keep as string if evaluation fails
|
65
|
+
result.append(item)
|
66
|
+
else:
|
67
|
+
if type(new_value) is str:
|
68
|
+
result.append(item)
|
69
|
+
else:
|
70
|
+
result.append(new_value)
|
71
|
+
return result
|
72
|
+
|
73
|
+
|
44
74
|
class Command:
|
45
75
|
"""Wrap a function so it can be used as a key-binding or a menu item."""
|
46
76
|
|
@@ -110,18 +140,19 @@ class Command:
|
|
110
140
|
|
111
141
|
self.keys: list[tuple[str | Keys, ...]] = []
|
112
142
|
|
113
|
-
def run(self, arg: str
|
143
|
+
def run(self, arg: str = "") -> None:
|
114
144
|
"""Run the command's handler."""
|
115
145
|
if self.filter():
|
116
146
|
app = get_app()
|
117
147
|
result = self.key_handler(
|
118
148
|
KeyPressEvent(
|
119
149
|
key_processor_ref=weakref.ref(app.key_processor),
|
120
|
-
arg=
|
150
|
+
arg=None,
|
121
151
|
key_sequence=[],
|
122
152
|
previous_key_sequence=[],
|
123
153
|
is_repeat=False,
|
124
154
|
),
|
155
|
+
*parse_args(arg),
|
125
156
|
)
|
126
157
|
if isawaitable(result):
|
127
158
|
|
@@ -141,14 +172,19 @@ class Command:
|
|
141
172
|
handler = self.handler
|
142
173
|
sig = signature(handler)
|
143
174
|
|
144
|
-
if sig.parameters:
|
145
|
-
# The handler already accepts a `KeyPressEvent` argument
|
175
|
+
if sig.parameters and next(iter(sig.parameters.keys())) == "event":
|
176
|
+
# The handler already accepts a `KeyPressEvent` argument named "event"
|
177
|
+
# as the first parameter
|
146
178
|
return cast("KeyHandlerCallable", handler)
|
147
179
|
|
180
|
+
# Otherwise we need to wrap in a function which accepts a KeyPressEvent as the
|
181
|
+
# first parameter
|
148
182
|
if iscoroutinefunction(handler):
|
149
183
|
|
150
|
-
async def _key_handler_async(
|
151
|
-
|
184
|
+
async def _key_handler_async(
|
185
|
+
event: KeyPressEvent, *args: Any
|
186
|
+
) -> NotImplementedOrNone:
|
187
|
+
result = cast("CommandHandlerNoArgs", handler)(*args)
|
152
188
|
assert isawaitable(result)
|
153
189
|
return await result
|
154
190
|
|
@@ -156,8 +192,8 @@ class Command:
|
|
156
192
|
|
157
193
|
else:
|
158
194
|
|
159
|
-
def _key_handler(event: KeyPressEvent) -> NotImplementedOrNone:
|
160
|
-
return cast("CommandHandlerNoArgs", handler)()
|
195
|
+
def _key_handler(event: KeyPressEvent, *args: Any) -> NotImplementedOrNone:
|
196
|
+
return cast("CommandHandlerNoArgs", handler)(*args)
|
161
197
|
|
162
198
|
return _key_handler
|
163
199
|
|
@@ -191,22 +227,6 @@ class Command:
|
|
191
227
|
return format_keys([self.keys[0]])[0]
|
192
228
|
return ""
|
193
229
|
|
194
|
-
@property
|
195
|
-
def menu_handler(self) -> Callable[[], None]:
|
196
|
-
"""Return a menu handler for the command."""
|
197
|
-
handler = self.handler
|
198
|
-
if isawaitable(handler):
|
199
|
-
|
200
|
-
def _menu_handler() -> None:
|
201
|
-
task = cast("CommandHandlerNoArgs", handler)()
|
202
|
-
task = cast("Coroutine[Any, Any, None]", task)
|
203
|
-
if task is not None:
|
204
|
-
get_app().create_background_task(task)
|
205
|
-
|
206
|
-
return _menu_handler
|
207
|
-
else:
|
208
|
-
return cast("Callable[[], None]", handler)
|
209
|
-
|
210
230
|
@property
|
211
231
|
def menu(self) -> MenuItem:
|
212
232
|
"""Return a menu item for the command."""
|
euporie/core/completion.py
CHANGED
@@ -10,26 +10,34 @@ from prompt_toolkit.completion.base import CompleteEvent, Completer, Completion
|
|
10
10
|
if TYPE_CHECKING:
|
11
11
|
from collections.abc import AsyncGenerator, Iterable
|
12
12
|
from pathlib import Path
|
13
|
+
from typing import Callable
|
13
14
|
|
14
15
|
from prompt_toolkit.document import Document
|
15
16
|
|
16
|
-
from euporie.core.kernel.
|
17
|
+
from euporie.core.kernel.base import BaseKernel
|
17
18
|
from euporie.core.lsp import LspClient
|
18
19
|
|
19
20
|
log = logging.getLogger(__name__)
|
20
21
|
|
21
22
|
|
22
23
|
class KernelCompleter(Completer):
|
23
|
-
"""A prompt_toolkit completer which provides completions from a
|
24
|
+
"""A prompt_toolkit completer which provides completions from a kernel."""
|
24
25
|
|
25
|
-
def __init__(self, kernel:
|
26
|
+
def __init__(self, kernel: BaseKernel | Callable[[], BaseKernel]) -> None:
|
26
27
|
"""Instantiate the completer for a given notebook.
|
27
28
|
|
28
29
|
Args:
|
29
30
|
kernel: A `Notebook` instance
|
30
31
|
|
31
32
|
"""
|
32
|
-
self.
|
33
|
+
self._kernel = kernel
|
34
|
+
|
35
|
+
@property
|
36
|
+
def kernel(self) -> BaseKernel:
|
37
|
+
"""Return the current kernel."""
|
38
|
+
if callable(self._kernel):
|
39
|
+
return self._kernel()
|
40
|
+
return self._kernel
|
33
41
|
|
34
42
|
def get_completions(
|
35
43
|
self, document: Document, complete_event: CompleteEvent
|
@@ -42,8 +50,8 @@ class KernelCompleter(Completer):
|
|
42
50
|
self, document: Document, complete_event: CompleteEvent
|
43
51
|
) -> AsyncGenerator[Completion, None]:
|
44
52
|
"""Retrieve completions from a :class:`Kernel`."""
|
45
|
-
for kwargs in await self.kernel.
|
46
|
-
|
53
|
+
for kwargs in await self.kernel.complete_async(
|
54
|
+
source=document.text,
|
47
55
|
cursor_pos=document.cursor_position,
|
48
56
|
):
|
49
57
|
if completion_type := kwargs.get("display_meta"):
|
euporie/core/convert/datum.py
CHANGED
@@ -249,13 +249,13 @@ class Datum(Generic[T], metaclass=_MetaDatum):
|
|
249
249
|
self._queue[key_conv] = event = asyncio.Event()
|
250
250
|
|
251
251
|
routes = _CONVERTOR_ROUTE_CACHE[(self.format, to)]
|
252
|
-
log.debug(
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
)
|
252
|
+
# log.debug(
|
253
|
+
# "Converting %s->'%s'@%s using routes: %s",
|
254
|
+
# self,
|
255
|
+
# to,
|
256
|
+
# (cols, rows),
|
257
|
+
# routes,
|
258
|
+
# )
|
259
259
|
output: T | None = None
|
260
260
|
if routes:
|
261
261
|
datum = self
|
euporie/core/data_structures.py
CHANGED
@@ -3,7 +3,9 @@
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
5
|
from functools import lru_cache
|
6
|
-
from typing import NamedTuple
|
6
|
+
from typing import NamedTuple, TypeVar, overload
|
7
|
+
|
8
|
+
_T = TypeVar("_T")
|
7
9
|
|
8
10
|
|
9
11
|
class DiBool(NamedTuple):
|
@@ -28,6 +30,23 @@ class DiInt(NamedTuple):
|
|
28
30
|
bottom: int = 0
|
29
31
|
left: int = 0
|
30
32
|
|
33
|
+
@overload
|
34
|
+
def __add__(self, other: tuple[int, ...], /) -> DiInt: ...
|
35
|
+
@overload
|
36
|
+
def __add__(self, other: tuple[_T, ...], /) -> DiInt: ...
|
37
|
+
|
38
|
+
def __add__(self, other):
|
39
|
+
"""Add two DiInt instances together."""
|
40
|
+
if not isinstance(other, DiInt):
|
41
|
+
return NotImplemented
|
42
|
+
|
43
|
+
return DiInt(
|
44
|
+
top=self.top + other.top,
|
45
|
+
right=self.right + other.right,
|
46
|
+
bottom=self.bottom + other.bottom,
|
47
|
+
left=self.left + other.left,
|
48
|
+
)
|
49
|
+
|
31
50
|
@classmethod
|
32
51
|
def from_value(cls, value: int) -> DiInt:
|
33
52
|
"""Construct an instance from a single value."""
|
euporie/core/filters.py
CHANGED
@@ -64,6 +64,14 @@ def cursor_in_leading_ws() -> bool:
|
|
64
64
|
return (not before) or before.isspace()
|
65
65
|
|
66
66
|
|
67
|
+
@Condition
|
68
|
+
def cursor_at_end_of_line() -> bool:
|
69
|
+
"""Determine if the cursor of the current buffer is in leading whitespace."""
|
70
|
+
from prompt_toolkit.application.current import get_app
|
71
|
+
|
72
|
+
return get_app().current_buffer.document.is_cursor_at_the_end_of_line
|
73
|
+
|
74
|
+
|
67
75
|
@Condition
|
68
76
|
def has_suggestion() -> bool:
|
69
77
|
"""Determine if the current buffer can display a suggestion."""
|
@@ -133,6 +141,38 @@ def tab_has_focus() -> bool:
|
|
133
141
|
return get_app().tab is not None
|
134
142
|
|
135
143
|
|
144
|
+
@Condition
|
145
|
+
def kernel_tab_has_focus() -> bool:
|
146
|
+
"""Determine if there is a focused kernel tab."""
|
147
|
+
from euporie.core.app.current import get_app
|
148
|
+
from euporie.core.tabs.kernel import KernelTab
|
149
|
+
|
150
|
+
return isinstance(get_app().tab, KernelTab)
|
151
|
+
|
152
|
+
|
153
|
+
@cache
|
154
|
+
def tab_type_has_focus(tab_class_path: str) -> Condition:
|
155
|
+
"""Determine if the focused tab is of a particular type."""
|
156
|
+
from pkgutil import resolve_name
|
157
|
+
|
158
|
+
from euporie.core.app.current import get_app
|
159
|
+
|
160
|
+
tab_class = cache(resolve_name)
|
161
|
+
|
162
|
+
return Condition(lambda: isinstance(get_app().tab, tab_class(tab_class_path)))
|
163
|
+
|
164
|
+
|
165
|
+
@Condition
|
166
|
+
def tab_can_save() -> bool:
|
167
|
+
"""Determine if the current tab can save it's contents."""
|
168
|
+
from euporie.core.app.current import get_app
|
169
|
+
from euporie.core.tabs.base import Tab
|
170
|
+
|
171
|
+
return (
|
172
|
+
tab := get_app().tab
|
173
|
+
) is not None and tab.__class__.write_file != Tab.write_file
|
174
|
+
|
175
|
+
|
136
176
|
@Condition
|
137
177
|
def pager_has_focus() -> bool:
|
138
178
|
"""Determine if there is a currently focused notebook."""
|
@@ -321,15 +361,6 @@ def multiple_cells_selected() -> bool:
|
|
321
361
|
return False
|
322
362
|
|
323
363
|
|
324
|
-
@Condition
|
325
|
-
def kernel_tab_has_focus() -> bool:
|
326
|
-
"""Determine if there is a focused kernel tab."""
|
327
|
-
from euporie.core.app.current import get_app
|
328
|
-
from euporie.core.tabs.kernel import KernelTab
|
329
|
-
|
330
|
-
return isinstance(get_app().tab, KernelTab)
|
331
|
-
|
332
|
-
|
333
364
|
def scrollable(window: Window) -> Filter:
|
334
365
|
"""Return a filter which indicates if a window is scrollable."""
|
335
366
|
return Condition(
|
euporie/core/format.py
CHANGED
@@ -110,11 +110,10 @@ class LspFormatter(Formatter):
|
|
110
110
|
range_ = change.get("range", {})
|
111
111
|
start = range_.get("start", {})
|
112
112
|
start_line = start.get("line", 0)
|
113
|
-
start_char = start.get("
|
113
|
+
start_char = start.get("character", 0)
|
114
114
|
end = range_.get("end", {})
|
115
115
|
end_line = end.get("line", 0)
|
116
|
-
end_char = end.get("
|
117
|
-
|
116
|
+
end_char = end.get("character", 0)
|
118
117
|
segment = range_to_slice(
|
119
118
|
start_line, start_char, end_line, end_char, text
|
120
119
|
)
|
euporie/core/ft/html.py
CHANGED
@@ -39,6 +39,7 @@ from euporie.core.border import (
|
|
39
39
|
LowerLeftHalfDottedLine,
|
40
40
|
LowerLeftHalfLine,
|
41
41
|
NoLine,
|
42
|
+
RoundedLine,
|
42
43
|
ThickDoubleDashedLine,
|
43
44
|
ThickLine,
|
44
45
|
ThickQuadrupleDashedLine,
|
@@ -558,7 +559,7 @@ def css_dimension(
|
|
558
559
|
digits = ""
|
559
560
|
i = 0
|
560
561
|
try:
|
561
|
-
while (c := value[i]) in "0123456789.":
|
562
|
+
while (c := value[i]) in "-0123456789.":
|
562
563
|
digits += c
|
563
564
|
i += 1
|
564
565
|
except IndexError:
|
@@ -924,19 +925,15 @@ class Theme(Mapping):
|
|
924
925
|
available_height: int,
|
925
926
|
) -> None:
|
926
927
|
"""Set the space available to the element for rendering."""
|
927
|
-
if self.theme["position"]
|
928
|
+
if self.theme["position"] in {"fixed"}:
|
928
929
|
# Space is given by position
|
929
|
-
position = self.position
|
930
930
|
dom = self.element.dom
|
931
931
|
assert dom.width is not None
|
932
932
|
assert dom.height is not None
|
933
|
+
position = self.position
|
933
934
|
self.available_width = (dom.width - position.right) - position.left
|
934
935
|
self.available_height = (dom.height - position.bottom) - position.top
|
935
936
|
|
936
|
-
# elif parent_theme := self.parent_theme:
|
937
|
-
# self.available_width = parent_theme.content_width
|
938
|
-
# self.available_height = parent_theme.content_height
|
939
|
-
|
940
937
|
else:
|
941
938
|
self.available_width = available_width
|
942
939
|
self.available_height = available_height
|
@@ -1527,6 +1524,7 @@ class Theme(Mapping):
|
|
1527
1524
|
# Replace the margin on the parent
|
1528
1525
|
if (
|
1529
1526
|
(first_child := element.first_child_element)
|
1527
|
+
and first_child.theme.in_flow
|
1530
1528
|
and first_child.prev_node_in_flow is None
|
1531
1529
|
and not self.border_visibility.top
|
1532
1530
|
and not self.padding.top
|
@@ -1541,6 +1539,7 @@ class Theme(Mapping):
|
|
1541
1539
|
values["top"] = max(child_theme.base_margin.top, values["top"])
|
1542
1540
|
if (
|
1543
1541
|
(last_child := element.last_child_element)
|
1542
|
+
and last_child.theme.in_flow
|
1544
1543
|
and last_child.next_node_in_flow is None
|
1545
1544
|
and not self.padding.bottom
|
1546
1545
|
and not self.border_visibility.bottom
|
@@ -1671,6 +1670,10 @@ class Theme(Mapping):
|
|
1671
1670
|
NoLine,
|
1672
1671
|
)
|
1673
1672
|
|
1673
|
+
# TODO - parse border_radius properly and check for corner radii
|
1674
|
+
if output[direction] == ThinLine and self.theme.get("border_radius"):
|
1675
|
+
output[direction] = RoundedLine
|
1676
|
+
|
1674
1677
|
return DiLineStyle(**output)
|
1675
1678
|
|
1676
1679
|
@cached_property
|
@@ -1857,7 +1860,11 @@ class Theme(Mapping):
|
|
1857
1860
|
"""The position of an element with a relative, absolute or fixed position."""
|
1858
1861
|
# TODO - calculate position based on top, left, bottom,right, width, height
|
1859
1862
|
soup_theme = self.element.dom.soup.theme
|
1860
|
-
|
1863
|
+
position = DiInt(0, 0, 0, 0)
|
1864
|
+
# if self.parent_theme is not None:
|
1865
|
+
# position += self.parent_theme.position
|
1866
|
+
position += self.base_margin
|
1867
|
+
position += DiInt(
|
1861
1868
|
top=round(
|
1862
1869
|
css_dimension(
|
1863
1870
|
self.theme["top"],
|
@@ -1891,6 +1898,7 @@ class Theme(Mapping):
|
|
1891
1898
|
or 0
|
1892
1899
|
),
|
1893
1900
|
)
|
1901
|
+
return position
|
1894
1902
|
|
1895
1903
|
@cached_property
|
1896
1904
|
def anchors(self) -> DiBool:
|
@@ -1912,10 +1920,7 @@ class Theme(Mapping):
|
|
1912
1920
|
and not self.preformatted
|
1913
1921
|
and not element.text
|
1914
1922
|
)
|
1915
|
-
or (
|
1916
|
-
self.theme["position"] == "absolute"
|
1917
|
-
and try_eval(self.theme["opacity"]) == 0
|
1918
|
-
)
|
1923
|
+
or (self.theme["position"] == "absolute" and self.hidden)
|
1919
1924
|
)
|
1920
1925
|
|
1921
1926
|
@cached_property
|
@@ -3746,19 +3751,20 @@ class HTML:
|
|
3746
3751
|
) -> StyleAndTextTuples:
|
3747
3752
|
"""Render a Node."""
|
3748
3753
|
# Update the element theme with the available space
|
3749
|
-
element.theme
|
3754
|
+
theme = element.theme
|
3755
|
+
theme.update_space(available_width, available_height)
|
3750
3756
|
|
3751
3757
|
# Render the contents
|
3752
|
-
if
|
3758
|
+
if theme.d_table:
|
3753
3759
|
render_func = self.render_table_content
|
3754
3760
|
|
3755
|
-
elif
|
3761
|
+
elif theme.d_list_item:
|
3756
3762
|
render_func = self.render_list_item_content
|
3757
3763
|
|
3758
|
-
elif
|
3764
|
+
elif theme.d_grid:
|
3759
3765
|
render_func = self.render_grid_content
|
3760
3766
|
|
3761
|
-
elif
|
3767
|
+
elif theme.latex:
|
3762
3768
|
render_func = self.render_latex_content
|
3763
3769
|
|
3764
3770
|
else:
|
@@ -4471,8 +4477,6 @@ class HTML:
|
|
4471
4477
|
float_lines_right: list[StyleAndTextTuples] = []
|
4472
4478
|
float_width_right = 0
|
4473
4479
|
|
4474
|
-
content_width = parent_theme.content_width
|
4475
|
-
|
4476
4480
|
new_line: StyleAndTextTuples = []
|
4477
4481
|
|
4478
4482
|
def flush() -> None:
|
@@ -4607,7 +4611,7 @@ class HTML:
|
|
4607
4611
|
# from each active float
|
4608
4612
|
if (
|
4609
4613
|
new_line
|
4610
|
-
and (
|
4614
|
+
and (available_width - float_width_left - float_width_right)
|
4611
4615
|
- left
|
4612
4616
|
- token_width
|
4613
4617
|
< 0
|
@@ -4633,7 +4637,7 @@ class HTML:
|
|
4633
4637
|
fillvalue=empty,
|
4634
4638
|
):
|
4635
4639
|
line_width = (
|
4636
|
-
|
4640
|
+
available_width
|
4637
4641
|
- fragment_list_width(ft_left)
|
4638
4642
|
- fragment_list_width(ft_right)
|
4639
4643
|
)
|
@@ -4724,7 +4728,7 @@ class HTML:
|
|
4724
4728
|
fragment_list_width(float_lines_left[0]) if float_lines_left else 0
|
4725
4729
|
)
|
4726
4730
|
line_width = (
|
4727
|
-
|
4731
|
+
available_width
|
4728
4732
|
- fragment_list_width(ft_left)
|
4729
4733
|
- fragment_list_width(ft_right)
|
4730
4734
|
)
|
@@ -4844,7 +4848,7 @@ class HTML:
|
|
4844
4848
|
placeholder="",
|
4845
4849
|
)
|
4846
4850
|
|
4847
|
-
#
|
4851
|
+
# Fill space around block elements so they fill the content width
|
4848
4852
|
if ft and ((fill and d_blocky and not theme.d_table) or d_inline_block):
|
4849
4853
|
pad_width = None
|
4850
4854
|
if d_blocky:
|
@@ -4929,7 +4933,6 @@ class HTML:
|
|
4929
4933
|
parent_style = parent_theme.style if parent_theme else ""
|
4930
4934
|
|
4931
4935
|
# Render the margin
|
4932
|
-
# if d_blocky and (alignment := theme.block_align) != FormattedTextAlign.LEFT:
|
4933
4936
|
if (alignment := theme.block_align) != FormattedTextAlign.LEFT:
|
4934
4937
|
# Center block contents if margin_left and margin_right are "auto"
|
4935
4938
|
ft = align(
|
@@ -4949,22 +4952,26 @@ class HTML:
|
|
4949
4952
|
padding_style=parent_style,
|
4950
4953
|
)
|
4951
4954
|
|
4952
|
-
# Apply mouse handler to
|
4953
|
-
if (
|
4954
|
-
|
4955
|
-
|
4956
|
-
and
|
4957
|
-
|
4958
|
-
|
4959
|
-
|
4960
|
-
|
4961
|
-
|
4962
|
-
|
4963
|
-
[
|
4964
|
-
|
4965
|
-
|
4966
|
-
|
4967
|
-
|
4955
|
+
# Apply mouse handler to elements with href, title, alt
|
4956
|
+
if callable(handler := self.mouse_handler):
|
4957
|
+
attrs = element.attrs
|
4958
|
+
# Inline elements inherit from parents
|
4959
|
+
if d_inline and (parent := element.parent):
|
4960
|
+
p_attrs = parent.attrs
|
4961
|
+
attrs.setdefault("href", p_attrs.get("href"))
|
4962
|
+
attrs.setdefault("title", p_attrs.get("title"))
|
4963
|
+
attrs.setdefault("alt", p_attrs.get("alt"))
|
4964
|
+
# Resolve link paths
|
4965
|
+
if href := attrs.get("href"):
|
4966
|
+
attrs["_link_path"] = self.base.joinuri(href)
|
4967
|
+
if {"title", "alt", "href"} & set(attrs):
|
4968
|
+
ft = cast(
|
4969
|
+
"StyleAndTextTuples",
|
4970
|
+
[
|
4971
|
+
(style, text, *(rest or [partial(handler, element)]))
|
4972
|
+
for style, text, *rest in ft
|
4973
|
+
],
|
4974
|
+
)
|
4968
4975
|
|
4969
4976
|
return ft
|
4970
4977
|
|