euporie 2.8.4__py3-none-any.whl → 2.8.6__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 +58 -62
- euporie/core/__init__.py +1 -1
- euporie/core/__main__.py +28 -11
- euporie/core/_settings.py +109 -0
- euporie/core/app/__init__.py +3 -0
- euporie/core/app/_commands.py +95 -0
- euporie/core/app/_settings.py +457 -0
- euporie/core/{app.py → app/app.py} +212 -576
- 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 +205 -0
- euporie/core/bars/menu.py +258 -0
- euporie/core/{widgets → bars}/search.py +20 -16
- euporie/core/{widgets → bars}/status.py +6 -23
- euporie/core/clipboard.py +19 -80
- euporie/core/comm/base.py +8 -6
- euporie/core/comm/ipywidgets.py +16 -7
- euporie/core/comm/registry.py +2 -1
- euporie/core/commands.py +10 -20
- euporie/core/completion.py +3 -2
- euporie/core/config.py +368 -341
- euporie/core/convert/__init__.py +0 -30
- euporie/core/convert/datum.py +116 -53
- euporie/core/convert/formats/__init__.py +31 -0
- euporie/core/convert/formats/ansi.py +9 -23
- 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 +4 -6
- euporie/core/convert/utils.py +41 -4
- euporie/core/diagnostics.py +2 -2
- euporie/core/filters.py +98 -40
- euporie/core/format.py +2 -3
- euporie/core/ft/ansi.py +1 -1
- euporie/core/ft/html.py +12 -21
- euporie/core/ft/table.py +1 -3
- euporie/core/ft/utils.py +4 -1
- euporie/core/graphics.py +386 -133
- 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} +45 -108
- euporie/core/kernel/manager.py +114 -0
- euporie/core/key_binding/bindings/__init__.py +1 -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 +1 -6
- euporie/core/key_binding/bindings/mouse.py +2 -2
- euporie/core/key_binding/bindings/terminal.py +193 -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 +3 -3
- euporie/core/layout/containers.py +48 -4
- euporie/core/layout/decor.py +2 -2
- euporie/core/layout/mouse.py +1 -1
- euporie/core/layout/print.py +2 -1
- euporie/core/layout/scroll.py +39 -34
- euporie/core/log.py +76 -64
- euporie/core/lsp.py +118 -24
- euporie/core/margins.py +1 -1
- euporie/core/path.py +62 -13
- euporie/core/renderer.py +58 -17
- euporie/core/style.py +57 -39
- euporie/core/suggest.py +103 -85
- euporie/core/tabs/__init__.py +32 -0
- euporie/core/tabs/_settings.py +113 -0
- euporie/core/tabs/base.py +80 -470
- euporie/core/tabs/kernel.py +419 -0
- euporie/core/tabs/notebook.py +24 -101
- euporie/core/utils.py +92 -15
- euporie/core/validation.py +1 -1
- euporie/core/widgets/_settings.py +188 -0
- euporie/core/widgets/cell.py +19 -50
- euporie/core/widgets/cell_outputs.py +25 -36
- euporie/core/widgets/decor.py +11 -41
- euporie/core/widgets/dialog.py +62 -27
- euporie/core/widgets/display.py +12 -15
- euporie/core/widgets/file_browser.py +2 -23
- euporie/core/widgets/forms.py +8 -5
- euporie/core/widgets/inputs.py +13 -70
- euporie/core/widgets/layout.py +2 -1
- euporie/core/widgets/logo.py +49 -0
- euporie/core/widgets/menu.py +10 -8
- euporie/core/widgets/pager.py +6 -10
- euporie/core/widgets/palette.py +6 -6
- euporie/hub/app.py +52 -35
- euporie/notebook/_commands.py +24 -0
- euporie/notebook/_settings.py +107 -0
- euporie/notebook/app.py +49 -171
- 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 +4 -4
- euporie/notebook/tabs/edit.py +11 -44
- euporie/notebook/tabs/json.py +5 -5
- euporie/notebook/tabs/log.py +1 -18
- euporie/notebook/tabs/notebook.py +11 -660
- 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 +6 -31
- euporie/preview/tabs/notebook.py +6 -72
- euporie/web/__init__.py +1 -0
- euporie/web/tabs/__init__.py +14 -0
- euporie/web/tabs/web.py +11 -6
- euporie/web/widgets/__init__.py +1 -0
- euporie/web/widgets/webview.py +5 -15
- {euporie-2.8.4.dist-info → euporie-2.8.6.dist-info}/METADATA +10 -8
- euporie-2.8.6.dist-info/RECORD +175 -0
- {euporie-2.8.4.dist-info → euporie-2.8.6.dist-info}/WHEEL +1 -1
- {euporie-2.8.4.dist-info → euporie-2.8.6.dist-info}/entry_points.txt +2 -2
- {euporie-2.8.4.dist-info → euporie-2.8.6.dist-info}/licenses/LICENSE +1 -1
- euporie/core/launch.py +0 -64
- euporie/core/terminal.py +0 -522
- euporie-2.8.4.dist-info/RECORD +0 -147
- {euporie-2.8.4.data → euporie-2.8.6.data}/data/share/applications/euporie-console.desktop +0 -0
- {euporie-2.8.4.data → euporie-2.8.6.data}/data/share/applications/euporie-notebook.desktop +0 -0
@@ -0,0 +1,419 @@
|
|
1
|
+
"""Contain kernel tab base class."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
import asyncio
|
6
|
+
import logging
|
7
|
+
from abc import ABCMeta
|
8
|
+
from collections import deque
|
9
|
+
from functools import partial
|
10
|
+
from typing import TYPE_CHECKING
|
11
|
+
from weakref import WeakKeyDictionary
|
12
|
+
|
13
|
+
from prompt_toolkit.auto_suggest import DummyAutoSuggest
|
14
|
+
from prompt_toolkit.completion.base import (
|
15
|
+
DynamicCompleter,
|
16
|
+
_MergedCompleter,
|
17
|
+
)
|
18
|
+
from prompt_toolkit.history import DummyHistory, InMemoryHistory
|
19
|
+
|
20
|
+
from euporie.core.app.current import get_app
|
21
|
+
from euporie.core.comm.registry import open_comm
|
22
|
+
from euporie.core.commands import add_cmd
|
23
|
+
from euporie.core.completion import DeduplicateCompleter, KernelCompleter, LspCompleter
|
24
|
+
from euporie.core.diagnostics import Report
|
25
|
+
from euporie.core.filters import kernel_tab_has_focus
|
26
|
+
from euporie.core.format import LspFormatter
|
27
|
+
from euporie.core.history import KernelHistory
|
28
|
+
from euporie.core.inspection import (
|
29
|
+
FirstInspector,
|
30
|
+
KernelInspector,
|
31
|
+
LspInspector,
|
32
|
+
)
|
33
|
+
from euporie.core.kernel.client import Kernel, MsgCallbacks
|
34
|
+
from euporie.core.suggest import HistoryAutoSuggest
|
35
|
+
from euporie.core.tabs.base import Tab
|
36
|
+
|
37
|
+
if TYPE_CHECKING:
|
38
|
+
from collections.abc import Sequence
|
39
|
+
from pathlib import Path
|
40
|
+
from typing import Any, Callable
|
41
|
+
|
42
|
+
from prompt_toolkit.auto_suggest import AutoSuggest
|
43
|
+
from prompt_toolkit.completion.base import Completer
|
44
|
+
from prompt_toolkit.history import History
|
45
|
+
|
46
|
+
from euporie.core.app.app import BaseApp
|
47
|
+
from euporie.core.comm.base import Comm
|
48
|
+
from euporie.core.format import Formatter
|
49
|
+
from euporie.core.inspection import Inspector
|
50
|
+
from euporie.core.lsp import LspClient
|
51
|
+
from euporie.core.widgets.inputs import KernelInput
|
52
|
+
|
53
|
+
log = logging.getLogger(__name__)
|
54
|
+
|
55
|
+
|
56
|
+
class KernelTab(Tab, metaclass=ABCMeta):
|
57
|
+
"""A Tab which connects to a kernel."""
|
58
|
+
|
59
|
+
kernel: Kernel
|
60
|
+
kernel_language: str
|
61
|
+
_metadata: dict[str, Any]
|
62
|
+
bg_init = False
|
63
|
+
|
64
|
+
default_callbacks: MsgCallbacks
|
65
|
+
allow_stdin: bool
|
66
|
+
|
67
|
+
def __init__(
|
68
|
+
self,
|
69
|
+
app: BaseApp,
|
70
|
+
path: Path | None = None,
|
71
|
+
kernel: Kernel | None = None,
|
72
|
+
comms: dict[str, Comm] | None = None,
|
73
|
+
use_kernel_history: bool = False,
|
74
|
+
connection_file: Path | None = None,
|
75
|
+
) -> None:
|
76
|
+
"""Create a new instance of a tab with a kernel."""
|
77
|
+
# Init tab
|
78
|
+
super().__init__(app, path)
|
79
|
+
|
80
|
+
self.lsps: list[LspClient] = []
|
81
|
+
self.history: History = DummyHistory()
|
82
|
+
self.inspectors: list[Inspector] = []
|
83
|
+
self.inspector = FirstInspector(lambda: self.inspectors)
|
84
|
+
self.suggester: AutoSuggest = DummyAutoSuggest()
|
85
|
+
self.completers: list[Completer] = []
|
86
|
+
self.completer = DeduplicateCompleter(
|
87
|
+
DynamicCompleter(lambda: _MergedCompleter(self.completers))
|
88
|
+
)
|
89
|
+
self.formatters: list[Formatter] = self.app.formatters
|
90
|
+
self.reports: WeakKeyDictionary[LspClient, Report] = WeakKeyDictionary()
|
91
|
+
|
92
|
+
# The client-side comm states
|
93
|
+
self.comms: dict[str, Comm] = {}
|
94
|
+
# The current kernel input
|
95
|
+
self._current_input: KernelInput | None = None
|
96
|
+
|
97
|
+
if self.bg_init:
|
98
|
+
# Load kernel in a background thread
|
99
|
+
app.create_background_task(
|
100
|
+
asyncio.to_thread(
|
101
|
+
self.init_kernel, kernel, comms, use_kernel_history, connection_file
|
102
|
+
)
|
103
|
+
)
|
104
|
+
else:
|
105
|
+
self.init_kernel(kernel, comms, use_kernel_history, connection_file)
|
106
|
+
|
107
|
+
async def load_lsps(self) -> None:
|
108
|
+
"""Load the LSP clients."""
|
109
|
+
path = self.path
|
110
|
+
|
111
|
+
# Load list of LSP clients for the tab's language
|
112
|
+
self.lsps.extend(self.app.get_language_lsps(self.language))
|
113
|
+
|
114
|
+
# Wait for all lsps to be initialized, and setup hooks as they become ready
|
115
|
+
async def _await_load(lsp: LspClient) -> LspClient:
|
116
|
+
await lsp.initialized.wait()
|
117
|
+
return lsp
|
118
|
+
|
119
|
+
for ready in asyncio.as_completed([_await_load(lsp) for lsp in self.lsps]):
|
120
|
+
lsp = await ready
|
121
|
+
# Apply open, save, and close hooks to the tab
|
122
|
+
change_handler = partial(lambda lsp, tab: self.lsp_change_handler(lsp), lsp)
|
123
|
+
close_handler = partial(lambda lsp, tab: self.lsp_close_handler(lsp), lsp)
|
124
|
+
before_save_handler = partial(
|
125
|
+
lambda lsp, tab: self.lsp_before_save_handler(lsp), lsp
|
126
|
+
)
|
127
|
+
after_save_handler = partial(
|
128
|
+
lambda lsp, tab: self.lsp_after_save_handler(lsp), lsp
|
129
|
+
)
|
130
|
+
|
131
|
+
self.on_close += close_handler
|
132
|
+
self.on_change += change_handler
|
133
|
+
self.before_save += before_save_handler
|
134
|
+
self.after_save += after_save_handler
|
135
|
+
|
136
|
+
# Listen for LSP diagnostics
|
137
|
+
lsp.on_diagnostics += self.lsp_update_diagnostics
|
138
|
+
|
139
|
+
# Add completer
|
140
|
+
completer = LspCompleter(lsp=lsp, path=path)
|
141
|
+
self.completers.append(completer)
|
142
|
+
|
143
|
+
# Add inspector
|
144
|
+
inspector = LspInspector(lsp, path)
|
145
|
+
self.inspectors.append(inspector)
|
146
|
+
|
147
|
+
# Add formatter
|
148
|
+
formatter = LspFormatter(lsp, path)
|
149
|
+
self.formatters.append(formatter)
|
150
|
+
|
151
|
+
# Remove hooks if the LSP exits
|
152
|
+
def lsp_unload(lsp: LspClient) -> None:
|
153
|
+
self.on_change -= change_handler # noqa: B023
|
154
|
+
self.before_save -= before_save_handler # noqa: B023
|
155
|
+
self.after_save -= after_save_handler # noqa: B023
|
156
|
+
self.on_close -= close_handler # noqa: B023
|
157
|
+
if completer in self.completers: # noqa: B023
|
158
|
+
self.completers.remove(completer) # noqa: B023
|
159
|
+
if inspector in self.completers: # noqa: B023
|
160
|
+
self.inspectors.remove(inspector) # noqa: B023
|
161
|
+
if formatter in self.completers: # noqa: B023
|
162
|
+
self.formatters.remove(formatter) # noqa: B023
|
163
|
+
if completer in self.completers: # noqa: B023
|
164
|
+
self.completers.remove(completer) # noqa: B023
|
165
|
+
if inspector in self.inspectors: # noqa: B023
|
166
|
+
self.inspectors.remove(inspector) # noqa: B023
|
167
|
+
if formatter in self.formatters: # noqa: B023
|
168
|
+
self.formatters.remove(formatter) # noqa: B023
|
169
|
+
|
170
|
+
lsp.on_exit += lsp_unload
|
171
|
+
|
172
|
+
# Remove the lsp exit handler if this tab closes
|
173
|
+
self.on_close += lambda tab: (
|
174
|
+
(lsp.on_exit.__isub__(lsp_unload) and None) or None # noqa: B023
|
175
|
+
) # Magical typing
|
176
|
+
|
177
|
+
# Tell the LSP we have an open file
|
178
|
+
self.lsp_open_handler(lsp)
|
179
|
+
|
180
|
+
def pre_init_kernel(self) -> None:
|
181
|
+
"""Run stuff before the kernel is loaded."""
|
182
|
+
|
183
|
+
def post_init_kernel(self) -> None:
|
184
|
+
"""Run stuff after the kernel is loaded."""
|
185
|
+
|
186
|
+
def init_kernel(
|
187
|
+
self,
|
188
|
+
kernel: Kernel | None = None,
|
189
|
+
comms: dict[str, Comm] | None = None,
|
190
|
+
use_kernel_history: bool = False,
|
191
|
+
connection_file: Path | None = None,
|
192
|
+
) -> None:
|
193
|
+
"""Set up the tab's kernel and related components."""
|
194
|
+
self.pre_init_kernel()
|
195
|
+
|
196
|
+
self.kernel_queue: deque[Callable] = deque()
|
197
|
+
|
198
|
+
if kernel:
|
199
|
+
self.kernel = kernel
|
200
|
+
self.kernel.default_callbacks = self.default_callbacks
|
201
|
+
else:
|
202
|
+
self.kernel = Kernel(
|
203
|
+
kernel_tab=self,
|
204
|
+
allow_stdin=self.allow_stdin,
|
205
|
+
default_callbacks=self.default_callbacks,
|
206
|
+
connection_file=connection_file,
|
207
|
+
)
|
208
|
+
self.comms = comms or {} # The client-side comm states
|
209
|
+
self.completers.append(KernelCompleter(self.kernel))
|
210
|
+
self.inspectors.append(KernelInspector(self.kernel))
|
211
|
+
self.use_kernel_history = use_kernel_history
|
212
|
+
self.history = (
|
213
|
+
KernelHistory(self.kernel) if use_kernel_history else InMemoryHistory()
|
214
|
+
)
|
215
|
+
self.suggester = HistoryAutoSuggest(self.history)
|
216
|
+
|
217
|
+
self.app.create_background_task(self.load_lsps())
|
218
|
+
|
219
|
+
self.post_init_kernel()
|
220
|
+
|
221
|
+
def close(self, cb: Callable | None = None) -> None:
|
222
|
+
"""Shut down kernel when tab is closed."""
|
223
|
+
if hasattr(self, "kernel"):
|
224
|
+
self.kernel.shutdown()
|
225
|
+
super().close(cb)
|
226
|
+
|
227
|
+
def interrupt_kernel(self) -> None:
|
228
|
+
"""Interrupt the current `Notebook`'s kernel."""
|
229
|
+
self.kernel.interrupt()
|
230
|
+
|
231
|
+
def restart_kernel(self, cb: Callable | None = None) -> None:
|
232
|
+
"""Restart the current `Notebook`'s kernel."""
|
233
|
+
|
234
|
+
def _cb(result: dict[str, Any]) -> None:
|
235
|
+
if callable(cb):
|
236
|
+
cb()
|
237
|
+
|
238
|
+
if confirm := self.app.dialogs.get("confirm"):
|
239
|
+
confirm.show(
|
240
|
+
message="Are you sure you want to restart the kernel?",
|
241
|
+
cb=partial(self.kernel.restart, cb=_cb),
|
242
|
+
)
|
243
|
+
else:
|
244
|
+
self.kernel.restart(cb=_cb)
|
245
|
+
|
246
|
+
def kernel_started(self, result: dict[str, Any] | None = None) -> None:
|
247
|
+
"""Task to run when the kernel has started."""
|
248
|
+
# Check kernel has not failed
|
249
|
+
if not self.kernel_name or self.kernel.missing:
|
250
|
+
if not self.kernel_name:
|
251
|
+
msg = "No kernel selected"
|
252
|
+
else:
|
253
|
+
msg = f"Kernel '{self.kernel_display_name}' not installed"
|
254
|
+
self.change_kernel(
|
255
|
+
msg=msg,
|
256
|
+
startup=True,
|
257
|
+
)
|
258
|
+
|
259
|
+
elif self.kernel.status == "error":
|
260
|
+
self.report_kernel_error(self.kernel.error)
|
261
|
+
|
262
|
+
else:
|
263
|
+
# Wait for an idle kernel
|
264
|
+
if self.kernel.status != "idle":
|
265
|
+
self.kernel.wait_for_status("idle")
|
266
|
+
|
267
|
+
# Load widget comm info
|
268
|
+
# self.kernel.comm_info(target_name="jupyter.widget")
|
269
|
+
|
270
|
+
# Load kernel info
|
271
|
+
self.kernel.info(set_kernel_info=self.set_kernel_info)
|
272
|
+
|
273
|
+
# Load kernel history
|
274
|
+
if self.use_kernel_history:
|
275
|
+
self.app.create_background_task(self.load_history())
|
276
|
+
|
277
|
+
# Run queued kernel tasks when the kernel is idle
|
278
|
+
log.debug("Running %d kernel tasks", len(self.kernel_queue))
|
279
|
+
while self.kernel_queue:
|
280
|
+
self.kernel_queue.popleft()()
|
281
|
+
|
282
|
+
self.app.invalidate()
|
283
|
+
|
284
|
+
def report_kernel_error(self, error: Exception | None) -> None:
|
285
|
+
"""Report a kernel error to the user."""
|
286
|
+
log.debug("Kernel error", exc_info=error)
|
287
|
+
|
288
|
+
async def load_history(self) -> None:
|
289
|
+
"""Load kernel history."""
|
290
|
+
try:
|
291
|
+
await self.history.load().__anext__()
|
292
|
+
except StopAsyncIteration:
|
293
|
+
pass
|
294
|
+
|
295
|
+
@property
|
296
|
+
def metadata(self) -> dict[str, Any]:
|
297
|
+
"""Return a dictionary to hold notebook / kernel metadata."""
|
298
|
+
return self._metadata
|
299
|
+
|
300
|
+
@property
|
301
|
+
def kernel_name(self) -> str:
|
302
|
+
"""Return the name of the kernel defined in the notebook JSON."""
|
303
|
+
return self.metadata.get("kernelspec", {}).get(
|
304
|
+
"name", self.app.config.kernel_name
|
305
|
+
)
|
306
|
+
|
307
|
+
@kernel_name.setter
|
308
|
+
def kernel_name(self, value: str) -> None:
|
309
|
+
"""Return the name of the kernel defined in the notebook JSON."""
|
310
|
+
self.metadata.setdefault("kernelspec", {})["name"] = value
|
311
|
+
|
312
|
+
@property
|
313
|
+
def language(self) -> str:
|
314
|
+
"""Return the name of the kernel defined in the notebook JSON."""
|
315
|
+
return self.metadata.get("kernelspec", {}).get("language")
|
316
|
+
|
317
|
+
@property
|
318
|
+
def kernel_display_name(self) -> str:
|
319
|
+
"""Return the display name of the kernel defined in the notebook JSON."""
|
320
|
+
return self.metadata.get("kernelspec", {}).get("display_name", self.kernel_name)
|
321
|
+
|
322
|
+
@property
|
323
|
+
def kernel_lang_file_ext(self) -> str:
|
324
|
+
"""Return the display name of the kernel defined in the notebook JSON."""
|
325
|
+
return self.metadata.get("language_info", {}).get("file_extension", ".py")
|
326
|
+
|
327
|
+
@property
|
328
|
+
def current_input(self) -> KernelInput:
|
329
|
+
"""Return the currently active kernel input, if any."""
|
330
|
+
from euporie.core.widgets.inputs import KernelInput
|
331
|
+
|
332
|
+
return self._current_input or KernelInput(self)
|
333
|
+
|
334
|
+
def set_kernel_info(self, info: dict) -> None:
|
335
|
+
"""Handle kernel info requests."""
|
336
|
+
self.metadata["language_info"] = info.get("language_info", {})
|
337
|
+
|
338
|
+
def change_kernel(self, msg: str | None = None, startup: bool = False) -> None:
|
339
|
+
"""Prompt the user to select a new kernel."""
|
340
|
+
kernel_specs = self.kernel.specs
|
341
|
+
|
342
|
+
# Warn the user if no kernels are installed
|
343
|
+
if not kernel_specs:
|
344
|
+
if startup and "no-kernels" in self.app.dialogs:
|
345
|
+
self.app.dialogs["no-kernels"].show()
|
346
|
+
return
|
347
|
+
|
348
|
+
# Automatically select the only kernel if there is only one
|
349
|
+
if startup and len(kernel_specs) == 1:
|
350
|
+
self.kernel.change(next(iter(kernel_specs)))
|
351
|
+
return
|
352
|
+
|
353
|
+
self.app.dialogs["change-kernel"].show(
|
354
|
+
tab=self, message=msg, kernel_specs=kernel_specs
|
355
|
+
)
|
356
|
+
|
357
|
+
def comm_open(self, content: dict, buffers: Sequence[bytes]) -> None:
|
358
|
+
"""Register a new kernel Comm object in the notebook."""
|
359
|
+
comm_id = str(content.get("comm_id"))
|
360
|
+
self.comms[comm_id] = open_comm(
|
361
|
+
comm_container=self, content=content, buffers=buffers
|
362
|
+
)
|
363
|
+
|
364
|
+
def comm_msg(self, content: dict, buffers: Sequence[bytes]) -> None:
|
365
|
+
"""Respond to a Comm message from the kernel."""
|
366
|
+
comm_id = str(content.get("comm_id"))
|
367
|
+
if comm := self.comms.get(comm_id):
|
368
|
+
comm.process_data(content.get("data", {}), buffers)
|
369
|
+
|
370
|
+
def comm_close(self, content: dict, buffers: Sequence[bytes]) -> None:
|
371
|
+
"""Close a notebook Comm."""
|
372
|
+
comm_id = content.get("comm_id")
|
373
|
+
if comm_id in self.comms:
|
374
|
+
del self.comms[comm_id]
|
375
|
+
|
376
|
+
def lsp_open_handler(self, lsp: LspClient) -> None:
|
377
|
+
"""Tell the LSP we opened a file."""
|
378
|
+
lsp.open_doc(
|
379
|
+
path=self.path, language=self.language, text=self.current_input.buffer.text
|
380
|
+
)
|
381
|
+
|
382
|
+
def lsp_change_handler(self, lsp: LspClient) -> None:
|
383
|
+
"""Tell the LSP server a file has changed."""
|
384
|
+
lsp.change_doc(
|
385
|
+
path=self.path,
|
386
|
+
language=self.language,
|
387
|
+
text=self.current_input.buffer.text,
|
388
|
+
)
|
389
|
+
|
390
|
+
def lsp_before_save_handler(self, lsp: LspClient) -> None:
|
391
|
+
"""Tell the the LSP we are about to save a document."""
|
392
|
+
lsp.will_save_doc(self.path)
|
393
|
+
|
394
|
+
def lsp_after_save_handler(self, lsp: LspClient) -> None:
|
395
|
+
"""Tell the the LSP we saved a document."""
|
396
|
+
lsp.save_doc(self.path, text=self.current_input.buffer.text)
|
397
|
+
|
398
|
+
def lsp_close_handler(self, lsp: LspClient) -> None:
|
399
|
+
"""Tell the LSP we opened a file."""
|
400
|
+
lsp.close_doc(path=self.path)
|
401
|
+
|
402
|
+
def lsp_update_diagnostics(self, lsp: LspClient) -> None:
|
403
|
+
"""Process a new diagnostic report from the LSP."""
|
404
|
+
if (diagnostics := lsp.reports.pop(self.path.as_uri(), None)) is not None:
|
405
|
+
self.reports[lsp] = Report.from_lsp(self.current_input.text, diagnostics)
|
406
|
+
self.app.invalidate()
|
407
|
+
|
408
|
+
def report(self) -> Report:
|
409
|
+
"""Return the current diagnostic reports."""
|
410
|
+
return Report.from_reports(*self.reports.values())
|
411
|
+
|
412
|
+
# ################################### Commands ####################################
|
413
|
+
|
414
|
+
@staticmethod
|
415
|
+
@add_cmd(filter=kernel_tab_has_focus)
|
416
|
+
def _change_kernel() -> None:
|
417
|
+
"""Change the notebook's kernel."""
|
418
|
+
if isinstance(kt := get_app().tab, KernelTab):
|
419
|
+
kt.change_kernel()
|
euporie/core/tabs/notebook.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
"""Contain the
|
1
|
+
"""Contain the base class for a notebook tabs."""
|
2
2
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
@@ -9,15 +9,14 @@ from functools import partial
|
|
9
9
|
from typing import TYPE_CHECKING
|
10
10
|
|
11
11
|
import nbformat
|
12
|
-
from prompt_toolkit.filters import Never
|
12
|
+
from prompt_toolkit.filters import Never
|
13
13
|
|
14
14
|
from euporie.core.comm.registry import open_comm
|
15
15
|
from euporie.core.commands import get_cmd
|
16
|
-
from euporie.core.
|
17
|
-
from euporie.core.kernel import MsgCallbacks
|
16
|
+
from euporie.core.io import edit_in_editor
|
17
|
+
from euporie.core.kernel.client import MsgCallbacks
|
18
18
|
from euporie.core.path import UntitledPath
|
19
|
-
from euporie.core.tabs.
|
20
|
-
from euporie.core.terminal import edit_in_editor
|
19
|
+
from euporie.core.tabs.kernel import KernelTab
|
21
20
|
from euporie.core.widgets.cell import Cell, get_cell_id
|
22
21
|
|
23
22
|
try:
|
@@ -34,9 +33,9 @@ if TYPE_CHECKING:
|
|
34
33
|
from prompt_toolkit.filters import Filter
|
35
34
|
from prompt_toolkit.layout.containers import AnyContainer
|
36
35
|
|
37
|
-
from euporie.core.app import BaseApp
|
36
|
+
from euporie.core.app.app import BaseApp
|
38
37
|
from euporie.core.comm.base import Comm
|
39
|
-
from euporie.core.kernel import Kernel
|
38
|
+
from euporie.core.kernel.client import Kernel
|
40
39
|
from euporie.core.lsp import LspClient
|
41
40
|
from euporie.core.widgets.inputs import KernelInput
|
42
41
|
|
@@ -107,11 +106,14 @@ class BaseNotebook(KernelTab, metaclass=ABCMeta):
|
|
107
106
|
|
108
107
|
def pre_init_kernel(self) -> None:
|
109
108
|
"""Run stuff before the kernel is loaded."""
|
109
|
+
super().pre_init_kernel()
|
110
110
|
# Load notebook file
|
111
111
|
self.load()
|
112
112
|
|
113
113
|
def post_init_kernel(self) -> None:
|
114
114
|
"""Load the notebook container after the kernel has been loaded."""
|
115
|
+
super().post_init_kernel()
|
116
|
+
|
115
117
|
# Replace the tab's container
|
116
118
|
prev = self.container
|
117
119
|
self.container = self.load_container()
|
@@ -273,18 +275,15 @@ class BaseNotebook(KernelTab, metaclass=ABCMeta):
|
|
273
275
|
else:
|
274
276
|
super().close(cb)
|
275
277
|
|
276
|
-
def
|
278
|
+
def write_file(self, path: Path) -> None:
|
277
279
|
"""Write the notebook's JSON to the current notebook's file.
|
278
280
|
|
279
281
|
Additionally save the widget state to the notebook metadata.
|
280
282
|
|
281
283
|
Args:
|
282
|
-
path: An
|
283
|
-
cb: A callback to run if after saving the notebook.
|
284
|
+
path: An path at which to save the file
|
284
285
|
|
285
286
|
"""
|
286
|
-
if path is not None:
|
287
|
-
self.path = path
|
288
287
|
if self.app.config.save_widget_state:
|
289
288
|
self.json.setdefault("metadata", {})["widgets"] = {
|
290
289
|
"application/vnd.jupyter.widget-state+json": {
|
@@ -297,56 +296,21 @@ class BaseNotebook(KernelTab, metaclass=ABCMeta):
|
|
297
296
|
},
|
298
297
|
}
|
299
298
|
}
|
300
|
-
|
301
|
-
if dialog := self.app.dialogs.get("save-as"):
|
302
|
-
dialog.show(tab=self, cb=cb)
|
303
|
-
else:
|
304
|
-
log.debug("Saving notebook..")
|
305
|
-
self.saving = True
|
306
|
-
self.app.invalidate()
|
307
|
-
|
308
|
-
# Save to a temp file, then replace the original
|
309
|
-
temp_path = self.path.parent / f".{self.path.stem}.tmp{self.path.suffix}"
|
310
|
-
log.debug("Using temporary file %s", temp_path.name)
|
299
|
+
with path.open("w") as open_file:
|
311
300
|
try:
|
312
|
-
|
313
|
-
except
|
314
|
-
get_cmd("save-as").run()
|
315
|
-
else:
|
301
|
+
write_nb(nb=nbformat.from_dict(self.json), fp=open_file)
|
302
|
+
except AssertionError:
|
316
303
|
try:
|
317
|
-
|
318
|
-
|
319
|
-
except AssertionError:
|
320
|
-
try:
|
321
|
-
# Jupytext requires a filename if we don't give it a format
|
322
|
-
write_nb(nb=nbformat.from_dict(self.json), fp=temp_path)
|
323
|
-
except Exception:
|
324
|
-
# Jupytext requires a format if the path has no extension
|
325
|
-
# We just use ipynb as the default format
|
326
|
-
write_nb(
|
327
|
-
nb=nbformat.from_dict(self.json),
|
328
|
-
fp=open_file,
|
329
|
-
fmt="ipynb",
|
330
|
-
)
|
304
|
+
# Jupytext requires a filename if we don't give it a format
|
305
|
+
write_nb(nb=nbformat.from_dict(self.json), fp=path)
|
331
306
|
except Exception:
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
except Exception:
|
340
|
-
if dialog := self.app.dialogs.get("save-as"):
|
341
|
-
dialog.show(tab=self, cb=cb)
|
342
|
-
else:
|
343
|
-
self.dirty = False
|
344
|
-
self.saving = False
|
345
|
-
self.app.invalidate()
|
346
|
-
log.debug("Notebook saved")
|
347
|
-
# Run the callback
|
348
|
-
if callable(cb):
|
349
|
-
cb()
|
307
|
+
# Jupytext requires a format if the path has no extension
|
308
|
+
# We just use ipynb as the default format
|
309
|
+
write_nb(
|
310
|
+
nb=nbformat.from_dict(self.json),
|
311
|
+
fp=open_file,
|
312
|
+
fmt="ipynb",
|
313
|
+
)
|
350
314
|
|
351
315
|
def run_cell(
|
352
316
|
self,
|
@@ -458,44 +422,3 @@ class BaseNotebook(KernelTab, metaclass=ABCMeta):
|
|
458
422
|
def lsp_update_diagnostics(self, lsp: LspClient) -> None:
|
459
423
|
"""Process a new diagnostic report from the LSP."""
|
460
424
|
# Do nothing, these are handled by cells
|
461
|
-
|
462
|
-
# ################################### Settings ####################################
|
463
|
-
|
464
|
-
add_setting(
|
465
|
-
name="save_widget_state",
|
466
|
-
flags=["--save-widget-state"],
|
467
|
-
type_=bool,
|
468
|
-
help_="Save a notebook's widget state in the notebook metadata",
|
469
|
-
default=True,
|
470
|
-
description="""
|
471
|
-
When set to ``True``, the state of any widgets in the current notebook will
|
472
|
-
be saves in the notebook's metadata. This enables widgets to be displayed
|
473
|
-
when the notebook is re-opened without having to re-run the notebook.
|
474
|
-
""",
|
475
|
-
)
|
476
|
-
|
477
|
-
add_setting(
|
478
|
-
name="max_notebook_width",
|
479
|
-
flags=["--max-notebook-width"],
|
480
|
-
type_=int,
|
481
|
-
help_="Maximum width of notebooks",
|
482
|
-
default=120,
|
483
|
-
schema={
|
484
|
-
"minimum": 1,
|
485
|
-
},
|
486
|
-
description="""
|
487
|
-
The maximum width at which to display a notebook.
|
488
|
-
""",
|
489
|
-
)
|
490
|
-
|
491
|
-
add_setting(
|
492
|
-
name="expand",
|
493
|
-
flags=["--expand"],
|
494
|
-
type_=bool,
|
495
|
-
help_="Use the full width to display notebooks",
|
496
|
-
default=False,
|
497
|
-
description="""
|
498
|
-
Whether the notebook page should expand to fill the available width
|
499
|
-
""",
|
500
|
-
cmd_filter=~buffer_has_focus,
|
501
|
-
)
|