euporie 2.8.6__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/app/_commands.py +4 -21
- euporie/core/app/app.py +13 -7
- euporie/core/bars/command.py +9 -6
- 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 -8
- euporie/core/completion.py +14 -6
- euporie/core/convert/datum.py +7 -7
- euporie/core/data_structures.py +20 -1
- euporie/core/filters.py +8 -0
- euporie/core/ft/html.py +47 -40
- euporie/core/graphics.py +11 -3
- 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 +45 -148
- euporie/core/log.py +1 -1
- euporie/core/style.py +2 -1
- euporie/core/suggest.py +155 -74
- euporie/core/tabs/__init__.py +10 -0
- euporie/core/tabs/_commands.py +76 -0
- euporie/core/tabs/_settings.py +16 -0
- euporie/core/tabs/base.py +22 -8
- euporie/core/tabs/kernel.py +81 -35
- euporie/core/tabs/notebook.py +14 -22
- euporie/core/utils.py +1 -1
- euporie/core/validation.py +8 -8
- euporie/core/widgets/_settings.py +19 -2
- euporie/core/widgets/cell.py +31 -31
- euporie/core/widgets/cell_outputs.py +10 -1
- euporie/core/widgets/dialog.py +30 -75
- 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 +55 -15
- euporie/core/widgets/palette.py +3 -1
- euporie/core/widgets/tree.py +86 -76
- euporie/notebook/app.py +35 -16
- euporie/notebook/tabs/edit.py +4 -4
- euporie/notebook/tabs/json.py +6 -2
- euporie/notebook/tabs/notebook.py +26 -8
- euporie/preview/tabs/notebook.py +17 -13
- euporie/web/tabs/web.py +22 -3
- euporie/web/widgets/webview.py +3 -0
- {euporie-2.8.6.dist-info → euporie-2.8.7.dist-info}/METADATA +1 -1
- {euporie-2.8.6.dist-info → euporie-2.8.7.dist-info}/RECORD +64 -61
- {euporie-2.8.6.dist-info → euporie-2.8.7.dist-info}/entry_points.txt +1 -1
- {euporie-2.8.6.dist-info → euporie-2.8.7.dist-info}/licenses/LICENSE +1 -1
- {euporie-2.8.6.data → euporie-2.8.7.data}/data/share/applications/euporie-console.desktop +0 -0
- {euporie-2.8.6.data → euporie-2.8.7.data}/data/share/applications/euporie-notebook.desktop +0 -0
- {euporie-2.8.6.dist-info → euporie-2.8.7.dist-info}/WHEEL +0 -0
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
|
|
euporie/core/graphics.py
CHANGED
@@ -722,6 +722,12 @@ class KittyUnicodeGraphicControl(BaseKittyGraphicControl):
|
|
722
722
|
# Calculate the size and cropping bbox at which we want to display the graphic
|
723
723
|
cols = floor(d_cols * ratio)
|
724
724
|
rows = ceil(cols * d_aspect)
|
725
|
+
d_bbox = DiInt(
|
726
|
+
top=self.bbox.top,
|
727
|
+
right=max(0, cols - (total_available_width - self.bbox.right)),
|
728
|
+
bottom=max(0, rows - (total_available_height - self.bbox.bottom)),
|
729
|
+
left=self.bbox.left,
|
730
|
+
)
|
725
731
|
if not self.loaded:
|
726
732
|
self.load(cols=cols, rows=rows, bbox=DiInt(0, 0, 0, 0))
|
727
733
|
|
@@ -745,11 +751,13 @@ class KittyUnicodeGraphicControl(BaseKittyGraphicControl):
|
|
745
751
|
ft: StyleAndTextTuples = []
|
746
752
|
|
747
753
|
# Generate placeholder grid
|
748
|
-
|
749
|
-
|
754
|
+
row_start = d_bbox.top
|
755
|
+
row_stop = rows - d_bbox.bottom
|
756
|
+
col_start = d_bbox.left
|
757
|
+
col_stop = cols - d_bbox.right
|
750
758
|
placeholder = self.PLACEHOLDER
|
751
759
|
diacritics = self.DIACRITICS
|
752
|
-
for row in range(
|
760
|
+
for row in range(row_start, row_stop):
|
753
761
|
for col in range(col_start, col_stop):
|
754
762
|
ft.extend(
|
755
763
|
[
|
euporie/core/history.py
CHANGED
@@ -9,8 +9,9 @@ from prompt_toolkit.history import History
|
|
9
9
|
|
10
10
|
if TYPE_CHECKING:
|
11
11
|
from collections.abc import AsyncGenerator, Iterable
|
12
|
+
from typing import Callable
|
12
13
|
|
13
|
-
from euporie.core.kernel.
|
14
|
+
from euporie.core.kernel.base import BaseKernel
|
14
15
|
|
15
16
|
log = logging.getLogger(__name__)
|
16
17
|
|
@@ -18,15 +19,24 @@ log = logging.getLogger(__name__)
|
|
18
19
|
class KernelHistory(History):
|
19
20
|
"""Load the kernel's command history."""
|
20
21
|
|
21
|
-
def __init__(
|
22
|
+
def __init__(
|
23
|
+
self, kernel: BaseKernel | Callable[[], BaseKernel], n: int = 1000
|
24
|
+
) -> None:
|
22
25
|
"""Create a new instance of the kernel history loader."""
|
23
26
|
super().__init__()
|
24
|
-
self.
|
27
|
+
self._kernel = kernel
|
25
28
|
# How many items to load
|
26
29
|
self.n = n
|
27
30
|
self.n_loaded = 0
|
28
31
|
self.loading = False
|
29
32
|
|
33
|
+
@property
|
34
|
+
def kernel(self) -> BaseKernel:
|
35
|
+
"""Return the current kernel."""
|
36
|
+
if callable(self._kernel):
|
37
|
+
return self._kernel()
|
38
|
+
return self._kernel
|
39
|
+
|
30
40
|
async def load(self) -> AsyncGenerator[str, None]:
|
31
41
|
"""Load the history and yield all entries, most recent history first.
|
32
42
|
|
@@ -39,9 +49,9 @@ class KernelHistory(History):
|
|
39
49
|
Yields:
|
40
50
|
Each history string
|
41
51
|
"""
|
42
|
-
if not self.loading and not self._loaded and self.kernel
|
52
|
+
if not self.loading and not self._loaded and self.kernel:
|
43
53
|
self.loading = True
|
44
|
-
items = await self.kernel.
|
54
|
+
items = await self.kernel.history_async(n=self.n, hist_access_type="tail")
|
45
55
|
if items:
|
46
56
|
self._loaded_strings = [item[2] for item in reversed(items)]
|
47
57
|
# Remove sequential duplicates
|
euporie/core/inspection.py
CHANGED
@@ -13,7 +13,7 @@ if TYPE_CHECKING:
|
|
13
13
|
|
14
14
|
from prompt_toolkit.document import Document
|
15
15
|
|
16
|
-
from euporie.core.kernel.
|
16
|
+
from euporie.core.kernel.base import BaseKernel
|
17
17
|
from euporie.core.lsp import LspClient
|
18
18
|
|
19
19
|
|
@@ -27,22 +27,29 @@ class Inspector(metaclass=ABCMeta):
|
|
27
27
|
|
28
28
|
|
29
29
|
class KernelInspector(Inspector):
|
30
|
-
"""Inspector which retrieves contextual help from a
|
30
|
+
"""Inspector which retrieves contextual help from a kernel."""
|
31
31
|
|
32
|
-
def __init__(self, kernel:
|
33
|
-
"""Initialize a new inspector which queries a
|
34
|
-
self.
|
32
|
+
def __init__(self, kernel: BaseKernel | Callable[[], BaseKernel]) -> None:
|
33
|
+
"""Initialize a new inspector which queries a kernel."""
|
34
|
+
self._kernel = kernel
|
35
|
+
|
36
|
+
@property
|
37
|
+
def kernel(self) -> BaseKernel:
|
38
|
+
"""Return the current kernel."""
|
39
|
+
if callable(self._kernel):
|
40
|
+
return self._kernel()
|
41
|
+
return self._kernel
|
35
42
|
|
36
43
|
async def get_context(self, document: Document, auto: bool) -> dict[str, Any]:
|
37
44
|
"""Request contextual help from the kernel."""
|
38
|
-
return await self.kernel.
|
45
|
+
return await self.kernel.inspect_async(document.text, document.cursor_position)
|
39
46
|
|
40
47
|
|
41
48
|
class LspInspector(Inspector):
|
42
49
|
"""Inspector which retrieves contextual help from a Language Server."""
|
43
50
|
|
44
51
|
def __init__(self, lsp: LspClient, path: Path) -> None:
|
45
|
-
"""Initialize a new inspector which queries a
|
52
|
+
"""Initialize a new inspector which queries a kernel."""
|
46
53
|
self.lsp = lsp
|
47
54
|
self.path = path
|
48
55
|
|
@@ -56,12 +63,12 @@ class LspInspector(Inspector):
|
|
56
63
|
|
57
64
|
|
58
65
|
class FirstInspector(Inspector):
|
59
|
-
"""Return results of the first inspector to
|
66
|
+
"""Return results of the first inspector to respond."""
|
60
67
|
|
61
68
|
def __init__(
|
62
69
|
self, inspectors: Sequence[Inspector] | Callable[[], Sequence[Inspector]]
|
63
70
|
) -> None:
|
64
|
-
"""Initialize a new inspector which queries a
|
71
|
+
"""Initialize a new inspector which queries a kernel."""
|
65
72
|
self.inspectors = inspectors
|
66
73
|
|
67
74
|
async def get_context(self, document: Document, auto: bool) -> dict[str, Any]:
|
euporie/core/kernel/__init__.py
CHANGED
@@ -1 +1,53 @@
|
|
1
|
-
"""Concerns the interaction with
|
1
|
+
"""Concerns the interaction with kernels."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
from importlib.util import find_spec
|
6
|
+
from pkgutil import resolve_name
|
7
|
+
from typing import TYPE_CHECKING
|
8
|
+
|
9
|
+
if TYPE_CHECKING:
|
10
|
+
from typing import Any, Literal
|
11
|
+
|
12
|
+
from euporie.core.kernel.base import BaseKernel, KernelInfo, MsgCallbacks
|
13
|
+
from euporie.core.tabs.kernel import KernelTab
|
14
|
+
|
15
|
+
KERNEL_REGISTRY = {
|
16
|
+
"local": "euporie.core.kernel.local:LocalPythonKernel",
|
17
|
+
}
|
18
|
+
if find_spec("jupyter_client"):
|
19
|
+
KERNEL_REGISTRY["jupyter"] = "euporie.core.kernel.jupyter:JupyterKernel"
|
20
|
+
|
21
|
+
|
22
|
+
def list_kernels() -> list[KernelInfo]:
|
23
|
+
"""Get specifications for all available kernel types.
|
24
|
+
|
25
|
+
Returns:
|
26
|
+
A dictionary mapping kernel type names to their specifications.
|
27
|
+
"""
|
28
|
+
return [
|
29
|
+
variant
|
30
|
+
for type_path in KERNEL_REGISTRY.values()
|
31
|
+
for variant in resolve_name(type_path).variants()
|
32
|
+
]
|
33
|
+
|
34
|
+
|
35
|
+
def create_kernel(
|
36
|
+
type_name: Literal["jupyter", "local"],
|
37
|
+
kernel_tab: KernelTab,
|
38
|
+
default_callbacks: MsgCallbacks | None = None,
|
39
|
+
allow_stdin: bool = False,
|
40
|
+
**kwargs: Any,
|
41
|
+
) -> BaseKernel:
|
42
|
+
"""Create and return appropriate kernel instance."""
|
43
|
+
type_path = KERNEL_REGISTRY.get(type_name)
|
44
|
+
if type_path is not None:
|
45
|
+
type_class = resolve_name(type_path)
|
46
|
+
return type_class(
|
47
|
+
kernel_tab=kernel_tab,
|
48
|
+
default_callbacks=default_callbacks,
|
49
|
+
allow_stdin=allow_stdin,
|
50
|
+
**kwargs,
|
51
|
+
)
|
52
|
+
else:
|
53
|
+
raise ValueError(f"Unknown kernel type: {type_name}")
|