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/widgets/tree.py
CHANGED
@@ -2,108 +2,118 @@
|
|
2
2
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
|
-
from
|
5
|
+
from functools import partial
|
6
|
+
from typing import TYPE_CHECKING
|
6
7
|
|
7
|
-
from prompt_toolkit.
|
8
|
-
from prompt_toolkit.layout.containers import ConditionalContainer
|
8
|
+
from prompt_toolkit.layout.containers import Window
|
9
9
|
from prompt_toolkit.layout.controls import FormattedTextControl
|
10
10
|
from prompt_toolkit.mouse_events import MouseButton, MouseEvent, MouseEventType
|
11
11
|
|
12
|
-
from euporie.core.layout.containers import HSplit, VSplit, Window
|
13
|
-
|
14
12
|
if TYPE_CHECKING:
|
15
13
|
from typing import Any
|
16
14
|
|
17
15
|
from prompt_toolkit.formatted_text import StyleAndTextTuples
|
18
16
|
from prompt_toolkit.key_binding.key_bindings import NotImplementedOrNone
|
19
|
-
from prompt_toolkit.layout.containers import AnyContainer
|
20
17
|
|
21
18
|
|
22
19
|
class JsonView:
|
23
20
|
"""A JSON-view container."""
|
24
21
|
|
25
22
|
def __init__(
|
26
|
-
self, data: Any, title: str | None = None, expanded: bool =
|
23
|
+
self, data: Any, title: str | None = None, expanded: bool = False
|
27
24
|
) -> None:
|
28
25
|
"""Create a new instance."""
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
self.
|
34
|
-
|
35
|
-
self.children = []
|
36
|
-
self.value_style = ""
|
37
|
-
self.value = ""
|
38
|
-
|
39
|
-
if isinstance(data, list):
|
40
|
-
data = dict(enumerate(data))
|
41
|
-
self.value = f"[] {len(data)} items"
|
42
|
-
self.value_style = "class:pygments.comment"
|
43
|
-
if isinstance(data, dict):
|
44
|
-
self.value = f"{{}} {len(data)} items"
|
45
|
-
self.value_style = "class:pygments.comment"
|
46
|
-
self.children = [
|
47
|
-
JsonView(data=value, title=key, expanded=expanded)
|
48
|
-
for key, value in data.items()
|
49
|
-
]
|
50
|
-
else:
|
51
|
-
self.value = f"{data!r}"
|
52
|
-
self.value_style = {
|
53
|
-
str: "class:pygments.literal.string",
|
54
|
-
int: "class:pygments.literal.number",
|
55
|
-
float: "class:pygments.literal.number",
|
56
|
-
bool: "class:pygments.keyword.constant",
|
57
|
-
}.get(type(data), "")
|
58
|
-
|
59
|
-
self.container = HSplit(
|
60
|
-
[
|
61
|
-
Window(
|
62
|
-
FormattedTextControl(self.format_title),
|
63
|
-
dont_extend_height=True,
|
64
|
-
),
|
65
|
-
ConditionalContainer(
|
66
|
-
VSplit(
|
67
|
-
[
|
68
|
-
Window(
|
69
|
-
width=2,
|
70
|
-
char=" ",
|
71
|
-
style="",
|
72
|
-
),
|
73
|
-
HSplit(self.children),
|
74
|
-
]
|
75
|
-
),
|
76
|
-
filter=Condition(lambda: self.expanded and bool(self.children)),
|
77
|
-
),
|
78
|
-
],
|
26
|
+
self.data = data
|
27
|
+
self.title = "root" if title is None else title
|
28
|
+
self.start_expanded = expanded
|
29
|
+
self._toggled_paths: set[tuple[str, ...]] = set()
|
30
|
+
self.container = Window(
|
31
|
+
FormattedTextControl(self._get_formatted_text, focusable=True),
|
79
32
|
style="class:tree",
|
80
33
|
)
|
81
34
|
|
82
|
-
def
|
83
|
-
"""
|
35
|
+
def _get_value_style(self, value: Any) -> str:
|
36
|
+
"""Return the style for a given value type."""
|
37
|
+
return {
|
38
|
+
str: "class:pygments.literal.string",
|
39
|
+
int: "class:pygments.literal.number",
|
40
|
+
float: "class:pygments.literal.number",
|
41
|
+
bool: "class:pygments.keyword.constant",
|
42
|
+
}.get(type(value), "")
|
43
|
+
|
44
|
+
def _format_value(self, value: Any) -> tuple[str, str]:
|
45
|
+
"""Return the formatted value and its style."""
|
46
|
+
if isinstance(value, (list, dict)):
|
47
|
+
length = len(value)
|
48
|
+
if isinstance(value, list):
|
49
|
+
return f" [] {length} items", "class:pygments.comment"
|
50
|
+
return f" {{}} {length} items", "class:pygments.comment"
|
51
|
+
return repr(value), self._get_value_style(value)
|
52
|
+
|
53
|
+
def _get_formatted_text(self) -> StyleAndTextTuples:
|
54
|
+
"""Generate the complete tree view as formatted text."""
|
55
|
+
result: StyleAndTextTuples = []
|
56
|
+
toggled_paths = self._toggled_paths
|
57
|
+
start_expanded = self.start_expanded
|
58
|
+
toggle = self._toggle
|
59
|
+
format_value = self._format_value
|
60
|
+
|
61
|
+
def format_node(
|
62
|
+
data: Any, path: tuple[str, ...], indent: int, key: str
|
63
|
+
) -> None:
|
64
|
+
is_expanded = (path in toggled_paths) ^ start_expanded
|
65
|
+
has_children = isinstance(data, (dict, list))
|
66
|
+
mouse_handler = partial(toggle, path=path)
|
67
|
+
|
68
|
+
value, style = format_value(data)
|
69
|
+
row: StyleAndTextTuples = [
|
70
|
+
# Add indentation
|
71
|
+
("", " " * indent),
|
72
|
+
# Add toggle symbol
|
73
|
+
(
|
74
|
+
("class:pygments.operator", "▼" if is_expanded else "▶")
|
75
|
+
if has_children
|
76
|
+
else ("", " ")
|
77
|
+
),
|
78
|
+
("", " "),
|
79
|
+
("class:pygments.keyword", str(key)),
|
80
|
+
("class:pygments.punctuation", ": "),
|
81
|
+
(style, value),
|
82
|
+
("", "\n"),
|
83
|
+
]
|
84
|
+
|
85
|
+
# Apply mouse_handler to rows with children
|
86
|
+
if has_children:
|
87
|
+
row = [(style, text, mouse_handler) for (style, text, *_) in row]
|
88
|
+
|
89
|
+
result.extend(row)
|
90
|
+
|
91
|
+
if is_expanded and has_children:
|
92
|
+
if isinstance(data, list):
|
93
|
+
data = {str(i): v for i, v in enumerate(data)}
|
94
|
+
|
95
|
+
for k, v in data.items():
|
96
|
+
new_path = (*path, str(k)) if path else (str(k),)
|
97
|
+
format_node(v, new_path, indent + 1, k)
|
98
|
+
|
99
|
+
format_node(self.data, (), 0, self.title)
|
100
|
+
return result
|
101
|
+
|
102
|
+
def _toggle(
|
103
|
+
self, mouse_event: MouseEvent, path: tuple[str, ...]
|
104
|
+
) -> NotImplementedOrNone:
|
105
|
+
"""Toggle the expansion state of a node."""
|
84
106
|
if (
|
85
107
|
mouse_event.button == MouseButton.LEFT
|
86
108
|
and mouse_event.event_type == MouseEventType.MOUSE_UP
|
87
109
|
):
|
88
|
-
|
110
|
+
if path in self._toggled_paths:
|
111
|
+
self._toggled_paths.remove(path)
|
112
|
+
else:
|
113
|
+
self._toggled_paths.add(path)
|
89
114
|
return None
|
90
115
|
return NotImplemented
|
91
116
|
|
92
|
-
def
|
93
|
-
"""Return the tree node toggle and title."""
|
94
|
-
return cast(
|
95
|
-
"StyleAndTextTuples",
|
96
|
-
[
|
97
|
-
("class:pygments.operator", "▼" if self.expanded else "▶", self.toggle)
|
98
|
-
if self.children
|
99
|
-
else ("", " ", self.toggle),
|
100
|
-
("", " ", self.toggle),
|
101
|
-
("class:pygments.keyword", f"{self.title}", self.toggle),
|
102
|
-
("class:pygments.punctuation", ": ", self.toggle),
|
103
|
-
(self.value_style, self.value, self.toggle),
|
104
|
-
],
|
105
|
-
)
|
106
|
-
|
107
|
-
def __pt_container__(self) -> AnyContainer:
|
117
|
+
def __pt_container__(self) -> Window:
|
108
118
|
"""Return the tree-view container's content."""
|
109
119
|
return self.container
|
euporie/notebook/app.py
CHANGED
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
5
5
|
import logging
|
6
6
|
from functools import partial
|
7
7
|
from typing import TYPE_CHECKING, cast
|
8
|
+
from weakref import WeakKeyDictionary
|
8
9
|
|
9
10
|
from prompt_toolkit.filters import Condition
|
10
11
|
from prompt_toolkit.formatted_text.base import to_formatted_text
|
@@ -89,6 +90,7 @@ class NotebookApp(BaseApp):
|
|
89
90
|
super().__init__(**kwargs)
|
90
91
|
self.bindings_to_load.append("euporie.notebook.app.NotebookApp")
|
91
92
|
|
93
|
+
self._tab_bar_tabs: dict[int, WeakKeyDictionary[Tab, TabBarTab]] = {}
|
92
94
|
self.on_tabs_change += self.set_tab_container
|
93
95
|
|
94
96
|
# Register config hooks
|
@@ -136,21 +138,36 @@ class NotebookApp(BaseApp):
|
|
136
138
|
|
137
139
|
def set_tab_container(self, app: BaseApp | None = None) -> None:
|
138
140
|
"""Set the container to use to display opened tabs."""
|
141
|
+
tab_mode = TabMode(self.config.tab_mode)
|
139
142
|
if not self.tabs:
|
140
143
|
self._tab_container = Pattern(
|
141
144
|
self.config.background_character,
|
142
145
|
self.config.background_pattern,
|
143
146
|
)
|
144
|
-
elif
|
147
|
+
elif tab_mode == TabMode.TILE_HORIZONTALLY:
|
148
|
+
children = []
|
149
|
+
for tab in self.tabs:
|
150
|
+
|
151
|
+
def _get_tab_container(tab: Tab = tab) -> Tab:
|
152
|
+
return tab
|
153
|
+
|
154
|
+
children.append(DynamicContainer(_get_tab_container))
|
145
155
|
self._tab_container = HSplit(
|
146
|
-
children=
|
156
|
+
children=children,
|
147
157
|
padding=1,
|
148
158
|
padding_style="class:tab-padding",
|
149
159
|
padding_char="─",
|
150
160
|
)
|
151
|
-
elif
|
161
|
+
elif tab_mode == TabMode.TILE_VERTICALLY:
|
162
|
+
children = []
|
163
|
+
for tab in self.tabs:
|
164
|
+
|
165
|
+
def _get_tab_container(tab: Tab = tab) -> Tab:
|
166
|
+
return tab
|
167
|
+
|
168
|
+
children.append(DynamicContainer(_get_tab_container))
|
152
169
|
self._tab_container = VSplit(
|
153
|
-
children=
|
170
|
+
children=children,
|
154
171
|
padding=1,
|
155
172
|
padding_style="class:tab-padding",
|
156
173
|
padding_char="│",
|
@@ -161,9 +178,7 @@ class NotebookApp(BaseApp):
|
|
161
178
|
ConditionalContainer(
|
162
179
|
Window(
|
163
180
|
TabBarControl(
|
164
|
-
tabs=self.tab_bar_tabs,
|
165
|
-
active=lambda: self._tab_idx,
|
166
|
-
closeable=True,
|
181
|
+
tabs=self.tab_bar_tabs, active=lambda: self._tab_idx
|
167
182
|
),
|
168
183
|
height=2,
|
169
184
|
style="class:app-tab-bar",
|
@@ -281,14 +296,18 @@ class NotebookApp(BaseApp):
|
|
281
296
|
|
282
297
|
def tab_bar_tabs(self) -> list[TabBarTab]:
|
283
298
|
"""Return a list of the current tabs for the tab-bar."""
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
299
|
+
result = []
|
300
|
+
for i, tab in enumerate(self.tabs):
|
301
|
+
index_dict = self._tab_bar_tabs.setdefault(i, WeakKeyDictionary())
|
302
|
+
if tab not in index_dict:
|
303
|
+
index_dict[tab] = TabBarTab(
|
304
|
+
title=lambda tab=tab: tab.title, # type: ignore [misc]
|
305
|
+
on_activate=partial(setattr, self, "tab_idx", i),
|
306
|
+
on_close=partial(self.close_tab, tab),
|
307
|
+
closeable=True,
|
308
|
+
)
|
309
|
+
result.append(self._tab_bar_tabs[i][tab])
|
310
|
+
return result
|
292
311
|
|
293
312
|
def _handle_exception(
|
294
313
|
self, loop: AbstractEventLoop, context: dict[str, Any]
|
@@ -474,9 +493,9 @@ class NotebookApp(BaseApp):
|
|
474
493
|
children=[
|
475
494
|
get_cmd("toggle-enable-language-servers").menu,
|
476
495
|
separator,
|
496
|
+
self.config.menus.autosuggest,
|
477
497
|
get_cmd("toggle-autoformat").menu,
|
478
498
|
get_cmd("toggle-autocomplete").menu,
|
479
|
-
get_cmd("toggle-autosuggest").menu,
|
480
499
|
get_cmd("toggle-autoinspect").menu,
|
481
500
|
],
|
482
501
|
description="Turn code assistance tools on or off",
|
euporie/notebook/tabs/display.py
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
|
+
import asyncio
|
5
6
|
import logging
|
6
7
|
from typing import TYPE_CHECKING, ClassVar
|
7
8
|
|
@@ -12,7 +13,6 @@ from euporie.core.convert.datum import Datum
|
|
12
13
|
from euporie.core.convert.mime import MIME_FORMATS, get_format
|
13
14
|
from euporie.core.margins import MarginContainer, ScrollbarMargin
|
14
15
|
from euporie.core.tabs.base import Tab
|
15
|
-
from euporie.core.utils import run_in_thread_with_context
|
16
16
|
from euporie.core.widgets.display import Display
|
17
17
|
|
18
18
|
if TYPE_CHECKING:
|
@@ -44,7 +44,7 @@ class DisplayTab(Tab):
|
|
44
44
|
self.app.layout.focus(self.container)
|
45
45
|
self.app.invalidate()
|
46
46
|
|
47
|
-
|
47
|
+
app.create_background_task(asyncio.to_thread(_load))
|
48
48
|
|
49
49
|
def __pt_status__(self) -> StatusBarFields | None:
|
50
50
|
"""Return a list of statusbar field values shown then this tab is active."""
|
euporie/notebook/tabs/edit.py
CHANGED
@@ -3,16 +3,16 @@
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
5
|
import logging
|
6
|
+
from functools import partial
|
6
7
|
from typing import TYPE_CHECKING, ClassVar
|
7
8
|
|
8
9
|
from prompt_toolkit.layout.containers import HSplit
|
9
10
|
from prompt_toolkit.layout.dimension import Dimension
|
10
11
|
|
11
12
|
from euporie.core.filters import insert_mode, replace_mode
|
12
|
-
from euporie.core.kernel.
|
13
|
+
from euporie.core.kernel.jupyter import JupyterKernel, MsgCallbacks
|
13
14
|
from euporie.core.key_binding.registry import load_registered_bindings
|
14
15
|
from euporie.core.lexers import detect_lexer
|
15
|
-
from euporie.core.path import UntitledPath
|
16
16
|
from euporie.core.tabs.kernel import KernelTab
|
17
17
|
from euporie.core.widgets.inputs import KernelInput
|
18
18
|
|
@@ -41,7 +41,7 @@ class EditorTab(KernelTab):
|
|
41
41
|
self,
|
42
42
|
app: BaseApp,
|
43
43
|
path: Path | None = None,
|
44
|
-
kernel:
|
44
|
+
kernel: JupyterKernel | None = None,
|
45
45
|
comms: dict[str, Comm] | None = None,
|
46
46
|
use_kernel_history: bool = False,
|
47
47
|
) -> None:
|
@@ -93,7 +93,7 @@ class EditorTab(KernelTab):
|
|
93
93
|
if self.dirty and (unsaved := self.app.dialogs.get("unsaved")):
|
94
94
|
unsaved.show(
|
95
95
|
tab=self,
|
96
|
-
cb=cb,
|
96
|
+
cb=partial(super().close, cb),
|
97
97
|
)
|
98
98
|
else:
|
99
99
|
super().close(cb)
|
@@ -154,46 +154,11 @@ class EditorTab(KernelTab):
|
|
154
154
|
),
|
155
155
|
)
|
156
156
|
|
157
|
-
def
|
158
|
-
"""
|
159
|
-
if path is not None:
|
160
|
-
self.path = path
|
157
|
+
def write_file(self, path: Path) -> None:
|
158
|
+
"""Write the file's text data to a path.
|
161
159
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
self.saving = True
|
168
|
-
self.app.invalidate()
|
169
|
-
# Ensure parent path exists
|
170
|
-
parent = self.path.parent
|
171
|
-
parent.mkdir(exist_ok=True, parents=True)
|
172
|
-
# Save to a temp file, then replace the original
|
173
|
-
temp_path = parent / f".{self.path.stem}.tmp{self.path.suffix}"
|
174
|
-
log.debug("Using temporary file %s", temp_path.name)
|
175
|
-
try:
|
176
|
-
open_file = temp_path.open("w")
|
177
|
-
except NotImplementedError:
|
178
|
-
if dialog := self.app.dialogs.get("save-as"):
|
179
|
-
dialog.show(tab=self, cb=cb)
|
180
|
-
else:
|
181
|
-
try:
|
182
|
-
open_file.write(self.input_box.buffer.text)
|
183
|
-
except Exception:
|
184
|
-
if dialog := self.app.dialogs.get("save-as"):
|
185
|
-
dialog.show(tab=self, cb=cb)
|
186
|
-
else:
|
187
|
-
try:
|
188
|
-
temp_path.rename(self.path)
|
189
|
-
except Exception:
|
190
|
-
if dialog := self.app.dialogs.get("save-as"):
|
191
|
-
dialog.show(tab=self, cb=cb)
|
192
|
-
else:
|
193
|
-
self.dirty = False
|
194
|
-
self.saving = False
|
195
|
-
self.app.invalidate()
|
196
|
-
log.debug("File saved")
|
197
|
-
# Run the callback
|
198
|
-
if callable(cb):
|
199
|
-
cb()
|
160
|
+
Args:
|
161
|
+
path: An path at which to save the file
|
162
|
+
|
163
|
+
"""
|
164
|
+
path.write_text(self.input_box.buffer.text)
|
euporie/notebook/tabs/json.py
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
|
+
import asyncio
|
5
6
|
import json
|
6
7
|
import logging
|
7
8
|
from typing import TYPE_CHECKING, ClassVar
|
@@ -9,8 +10,9 @@ from typing import TYPE_CHECKING, ClassVar
|
|
9
10
|
from prompt_toolkit.layout.containers import VSplit
|
10
11
|
from prompt_toolkit.layout.dimension import Dimension
|
11
12
|
|
13
|
+
from euporie.core.layout.scroll import ScrollingContainer
|
14
|
+
from euporie.core.margins import MarginContainer, ScrollbarMargin
|
12
15
|
from euporie.core.tabs.base import Tab
|
13
|
-
from euporie.core.utils import run_in_thread_with_context
|
14
16
|
from euporie.core.widgets.tree import JsonView
|
15
17
|
|
16
18
|
if TYPE_CHECKING:
|
@@ -43,7 +45,7 @@ class JsonTab(Tab):
|
|
43
45
|
self.app.layout.focus(self.container)
|
44
46
|
self.app.invalidate()
|
45
47
|
|
46
|
-
|
48
|
+
app.create_background_task(asyncio.to_thread(_load))
|
47
49
|
|
48
50
|
def __pt_status__(self) -> StatusBarFields | None:
|
49
51
|
"""Return a list of statusbar field values shown then this tab is active."""
|
@@ -65,8 +67,10 @@ class JsonTab(Tab):
|
|
65
67
|
|
66
68
|
return VSplit(
|
67
69
|
[
|
68
|
-
|
69
|
-
|
70
|
+
scroll := ScrollingContainer(
|
71
|
+
children=[JsonView(data, title=self.path.name, expanded=True)]
|
72
|
+
),
|
73
|
+
MarginContainer(ScrollbarMargin(), target=scroll),
|
70
74
|
],
|
71
75
|
width=Dimension(weight=1),
|
72
76
|
height=Dimension(weight=1),
|
@@ -48,7 +48,7 @@ if TYPE_CHECKING:
|
|
48
48
|
from euporie.core.app.app import BaseApp
|
49
49
|
from euporie.core.bars.status import StatusBarFields
|
50
50
|
from euporie.core.comm.base import Comm
|
51
|
-
from euporie.core.kernel.
|
51
|
+
from euporie.core.kernel.base import BaseKernel
|
52
52
|
|
53
53
|
log = logging.getLogger(__name__)
|
54
54
|
|
@@ -77,7 +77,7 @@ class Notebook(BaseNotebook):
|
|
77
77
|
self,
|
78
78
|
app: BaseApp,
|
79
79
|
path: Path | None = None,
|
80
|
-
kernel:
|
80
|
+
kernel: BaseKernel | None = None,
|
81
81
|
comms: dict[str, Comm] | None = None,
|
82
82
|
use_kernel_history: bool = True,
|
83
83
|
json: dict[str, Any] | None = None,
|
@@ -97,7 +97,6 @@ class Notebook(BaseNotebook):
|
|
97
97
|
self.multiple_cells_selected = multiple_cells_selected
|
98
98
|
self.clipboard: list[Cell] = []
|
99
99
|
self.undo_buffer: deque[tuple[int, list[Cell]]] = deque(maxlen=10)
|
100
|
-
|
101
100
|
self.default_callbacks["set_next_input"] = self.set_next_input
|
102
101
|
|
103
102
|
# Tab stuff
|
@@ -117,7 +116,11 @@ class Notebook(BaseNotebook):
|
|
117
116
|
|
118
117
|
def _kernel_name() -> StyleAndTextTuples:
|
119
118
|
ft: StyleAndTextTuples = [
|
120
|
-
(
|
119
|
+
(
|
120
|
+
"",
|
121
|
+
self.kernel_display_name or "No Kernel",
|
122
|
+
self._statusbar_kernel_handler,
|
123
|
+
)
|
121
124
|
]
|
122
125
|
return ft
|
123
126
|
|
@@ -164,13 +167,28 @@ class Notebook(BaseNotebook):
|
|
164
167
|
self.app.dialogs["error"].show(exception=error, when="starting the kernel")
|
165
168
|
|
166
169
|
def load_container(self) -> AnyContainer:
|
167
|
-
"""
|
170
|
+
"""Trigger loading of the main notebook container."""
|
171
|
+
|
172
|
+
async def _load() -> None:
|
173
|
+
# Load notebook file
|
174
|
+
self.load()
|
175
|
+
# Load an focus container
|
176
|
+
prev = self.container
|
177
|
+
self.container = self._load_container()
|
178
|
+
self.loaded = True
|
179
|
+
# Update the focus if the old container had focus
|
180
|
+
if self.app.layout.has_focus(prev):
|
181
|
+
self.focus()
|
182
|
+
|
183
|
+
self.app.create_background_task(_load())
|
184
|
+
|
185
|
+
return self.container
|
186
|
+
|
187
|
+
def _load_container(self) -> AnyContainer:
|
188
|
+
"""Actually load the main notebook container."""
|
168
189
|
self.page = ScrollingContainer(
|
169
190
|
self.rendered_cells, width=self.app.config.max_notebook_width
|
170
191
|
)
|
171
|
-
# Ensure all cells get initialized ASAP to prevent race conditions
|
172
|
-
# creating multiple version of cells across threads
|
173
|
-
self.page.all_children()
|
174
192
|
|
175
193
|
expand = Condition(lambda: self.app.config.expand)
|
176
194
|
|
euporie/preview/tabs/notebook.py
CHANGED
@@ -27,7 +27,7 @@ if TYPE_CHECKING:
|
|
27
27
|
|
28
28
|
from euporie.core.app.app import BaseApp
|
29
29
|
from euporie.core.comm.base import Comm
|
30
|
-
from euporie.core.kernel.
|
30
|
+
from euporie.core.kernel.base import BaseKernel
|
31
31
|
|
32
32
|
log = logging.getLogger(__name__)
|
33
33
|
|
@@ -54,17 +54,6 @@ class PreviewNotebook(BaseNotebook):
|
|
54
54
|
"""Filter cells before kernel is loaded."""
|
55
55
|
super().pre_init_kernel()
|
56
56
|
|
57
|
-
# Filter the cells to be shown
|
58
|
-
n_cells = len(self.json["cells"]) - 1
|
59
|
-
start: int | None = None
|
60
|
-
stop: int | None = None
|
61
|
-
if self.app.config.cell_start is not None:
|
62
|
-
start = min(max(self.app.config.cell_start, -n_cells), n_cells)
|
63
|
-
if self.app.config.cell_stop is not None:
|
64
|
-
stop = min(max(self.app.config.cell_stop, -n_cells), n_cells)
|
65
|
-
log.debug("Showing cells %s to %s", start, stop)
|
66
|
-
self.json["cells"] = self.json["cells"][start:stop]
|
67
|
-
|
68
57
|
def post_init_kernel(self) -> None:
|
69
58
|
"""Optionally start kernel after it is loaded."""
|
70
59
|
super().post_init_kernel()
|
@@ -78,7 +67,7 @@ class PreviewNotebook(BaseNotebook):
|
|
78
67
|
|
79
68
|
def init_kernel(
|
80
69
|
self,
|
81
|
-
kernel:
|
70
|
+
kernel: BaseKernel | None = None,
|
82
71
|
comms: dict[str, Comm] | None = None,
|
83
72
|
use_kernel_history: bool = False,
|
84
73
|
connection_file: Path | None = None,
|
@@ -162,6 +151,21 @@ class PreviewNotebook(BaseNotebook):
|
|
162
151
|
|
163
152
|
def load_container(self) -> AnyContainer:
|
164
153
|
"""Load the notebook's main container."""
|
154
|
+
# Load file
|
155
|
+
self.load()
|
156
|
+
|
157
|
+
# Filter the cells to be shown
|
158
|
+
n_cells = len(self.json["cells"]) - 1
|
159
|
+
start: int | None = None
|
160
|
+
stop: int | None = None
|
161
|
+
if self.app.config.cell_start is not None:
|
162
|
+
start = min(max(self.app.config.cell_start, -n_cells), n_cells)
|
163
|
+
if self.app.config.cell_stop is not None:
|
164
|
+
stop = min(max(self.app.config.cell_stop, -n_cells), n_cells)
|
165
|
+
log.debug("Showing cells %s to %s", start, stop)
|
166
|
+
self.json["cells"] = self.json["cells"][start:stop]
|
167
|
+
|
168
|
+
# Generate container
|
165
169
|
no_expand = ~self.app.config.filters.expand
|
166
170
|
return PrintingContainer(
|
167
171
|
[
|
euporie/web/__init__.py
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
"""A euporie web viewer component."""
|
@@ -0,0 +1,14 @@
|
|
1
|
+
"""Tabs for use in euporie notebook editor."""
|
2
|
+
|
3
|
+
from euporie.core.tabs import _TAB_REGISTRY, TabRegistryEntry
|
4
|
+
|
5
|
+
_TAB_REGISTRY.extend(
|
6
|
+
[
|
7
|
+
TabRegistryEntry(
|
8
|
+
path="euporie.web.tabs.web:WebTab",
|
9
|
+
name="Web Viewer",
|
10
|
+
mime_types={"text/html", "text/markdown"},
|
11
|
+
weight=2,
|
12
|
+
),
|
13
|
+
]
|
14
|
+
)
|