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
euporie/core/tabs/base.py
CHANGED
@@ -1,65 +1,35 @@
|
|
1
|
-
"""Contain tab base
|
1
|
+
"""Contain tab base class."""
|
2
2
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
5
|
import asyncio
|
6
6
|
import logging
|
7
7
|
from abc import ABCMeta
|
8
|
-
from collections import deque
|
9
|
-
from functools import partial
|
10
8
|
from typing import TYPE_CHECKING, ClassVar
|
11
|
-
from weakref import WeakKeyDictionary
|
12
9
|
|
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
10
|
from prompt_toolkit.layout.containers import WindowAlign
|
20
11
|
from prompt_toolkit.layout.controls import FormattedTextControl
|
21
12
|
from prompt_toolkit.utils import Event
|
22
13
|
from upath import UPath
|
23
14
|
|
24
|
-
from euporie.core.
|
15
|
+
from euporie.core.app.current import get_app
|
25
16
|
from euporie.core.commands import add_cmd
|
26
|
-
from euporie.core.
|
27
|
-
from euporie.core.config import add_setting
|
28
|
-
from euporie.core.current import get_app
|
29
|
-
from euporie.core.diagnostics import Report
|
30
|
-
from euporie.core.filters import kernel_tab_has_focus, tab_has_focus
|
31
|
-
from euporie.core.format import LspFormatter
|
32
|
-
from euporie.core.history import KernelHistory
|
33
|
-
from euporie.core.inspection import (
|
34
|
-
FirstInspector,
|
35
|
-
KernelInspector,
|
36
|
-
LspInspector,
|
37
|
-
)
|
38
|
-
from euporie.core.kernel import Kernel, MsgCallbacks
|
17
|
+
from euporie.core.filters import tab_can_save, tab_has_focus
|
39
18
|
from euporie.core.key_binding.registry import (
|
40
19
|
register_bindings,
|
41
20
|
)
|
42
21
|
from euporie.core.layout.containers import Window
|
43
|
-
from euporie.core.path import parse_path
|
44
|
-
from euporie.core.suggest import HistoryAutoSuggest
|
45
|
-
from euporie.core.utils import run_in_thread_with_context
|
46
|
-
from euporie.core.widgets.inputs import KernelInput
|
22
|
+
from euporie.core.path import UntitledPath, parse_path
|
47
23
|
|
48
24
|
if TYPE_CHECKING:
|
49
25
|
from pathlib import Path
|
50
|
-
from typing import Any, Callable
|
26
|
+
from typing import Any, Callable
|
51
27
|
|
52
|
-
from prompt_toolkit.
|
53
|
-
from prompt_toolkit.completion.base import Completer
|
54
|
-
from prompt_toolkit.history import History
|
28
|
+
from prompt_toolkit.key_binding.key_processor import KeyPressEvent
|
55
29
|
from prompt_toolkit.layout.containers import AnyContainer
|
56
30
|
|
57
|
-
from euporie.core.app import BaseApp
|
58
|
-
from euporie.core.
|
59
|
-
from euporie.core.format import Formatter
|
60
|
-
from euporie.core.inspection import Inspector
|
61
|
-
from euporie.core.lsp import LspClient
|
62
|
-
from euporie.core.widgets.status import StatusBarFields
|
31
|
+
from euporie.core.app.app import BaseApp
|
32
|
+
from euporie.core.bars.status import StatusBarFields
|
63
33
|
|
64
34
|
log = logging.getLogger(__name__)
|
65
35
|
|
@@ -137,11 +107,77 @@ class Tab(metaclass=ABCMeta):
|
|
137
107
|
cb()
|
138
108
|
self.after_save.fire()
|
139
109
|
|
140
|
-
|
110
|
+
self.app.create_background_task(asyncio.to_thread(self.save, path, _wrapped_cb))
|
141
111
|
|
142
112
|
def save(self, path: Path | None = None, cb: Callable | None = None) -> None:
|
143
|
-
"""Save the current
|
144
|
-
|
113
|
+
"""Save the current file."""
|
114
|
+
if path is not None:
|
115
|
+
self.path = path
|
116
|
+
|
117
|
+
if (self.path is None or isinstance(self.path, UntitledPath)) and (
|
118
|
+
dialog := self.app.dialogs.get("save-as")
|
119
|
+
):
|
120
|
+
dialog.show(tab=self, cb=cb)
|
121
|
+
return
|
122
|
+
|
123
|
+
path = self.path
|
124
|
+
try:
|
125
|
+
# Ensure parent path exists
|
126
|
+
parent = path.parent
|
127
|
+
parent.mkdir(exist_ok=True, parents=True)
|
128
|
+
|
129
|
+
# Create backup if original file exists
|
130
|
+
backup_path: Path | None = None
|
131
|
+
if path.exists():
|
132
|
+
name = f"{path.name}.bak"
|
133
|
+
if not name.startswith("."):
|
134
|
+
name = f".{name}"
|
135
|
+
backup_path = parent / name
|
136
|
+
try:
|
137
|
+
import shutil
|
138
|
+
|
139
|
+
shutil.copy2(path, backup_path)
|
140
|
+
except Exception as e:
|
141
|
+
log.error("Failed to create backup: %s", e)
|
142
|
+
raise
|
143
|
+
|
144
|
+
# Write new content directly to original file
|
145
|
+
try:
|
146
|
+
self.write_file(path)
|
147
|
+
except Exception as e:
|
148
|
+
log.error("Failed to write file: %s", e)
|
149
|
+
# Restore from backup if it exists
|
150
|
+
if backup_path is not None:
|
151
|
+
log.info("Restoring backup")
|
152
|
+
backup_path.replace(path)
|
153
|
+
raise
|
154
|
+
|
155
|
+
self.dirty = False
|
156
|
+
self.saving = False
|
157
|
+
self.app.invalidate()
|
158
|
+
log.debug("File saved successfully")
|
159
|
+
|
160
|
+
# Run the callback
|
161
|
+
if callable(cb):
|
162
|
+
cb()
|
163
|
+
|
164
|
+
except Exception:
|
165
|
+
log.exception("An error occurred while saving the file")
|
166
|
+
if dialog := self.app.dialogs.get("save-as"):
|
167
|
+
dialog.show(tab=self, cb=cb)
|
168
|
+
|
169
|
+
def write_file(self, path: Path) -> None:
|
170
|
+
"""Write the tab's data to a path.
|
171
|
+
|
172
|
+
Not implement in the base tab.
|
173
|
+
|
174
|
+
Args:
|
175
|
+
path: An path at which to save the file
|
176
|
+
|
177
|
+
"""
|
178
|
+
raise NotImplementedError(
|
179
|
+
f"File saving not implement for `{self.__class__.__name__}` tab"
|
180
|
+
)
|
145
181
|
|
146
182
|
def __pt_status__(self) -> StatusBarFields | None:
|
147
183
|
"""Return a list of statusbar field values shown then this tab is active."""
|
@@ -171,12 +207,12 @@ class Tab(metaclass=ABCMeta):
|
|
171
207
|
Tab._refresh_tab()
|
172
208
|
|
173
209
|
@staticmethod
|
174
|
-
@add_cmd(filter=
|
175
|
-
def _save_file() -> None:
|
210
|
+
@add_cmd(filter=tab_can_save, aliases=["w"])
|
211
|
+
def _save_file(event: KeyPressEvent) -> None:
|
176
212
|
"""Save the current file."""
|
177
213
|
if (tab := get_app().tab) is not None:
|
178
214
|
try:
|
179
|
-
tab._save()
|
215
|
+
tab._save(UPath(event._arg) if event._arg else None)
|
180
216
|
except NotImplementedError:
|
181
217
|
pass
|
182
218
|
|
@@ -190,429 +226,3 @@ class Tab(metaclass=ABCMeta):
|
|
190
226
|
}
|
191
227
|
}
|
192
228
|
)
|
193
|
-
|
194
|
-
|
195
|
-
class KernelTab(Tab, metaclass=ABCMeta):
|
196
|
-
"""A Tab which connects to a kernel."""
|
197
|
-
|
198
|
-
kernel: Kernel
|
199
|
-
kernel_language: str
|
200
|
-
_metadata: dict[str, Any]
|
201
|
-
bg_init = True
|
202
|
-
|
203
|
-
default_callbacks: MsgCallbacks
|
204
|
-
allow_stdin: bool
|
205
|
-
|
206
|
-
def __init__(
|
207
|
-
self,
|
208
|
-
app: BaseApp,
|
209
|
-
path: Path | None = None,
|
210
|
-
kernel: Kernel | None = None,
|
211
|
-
comms: dict[str, Comm] | None = None,
|
212
|
-
use_kernel_history: bool = False,
|
213
|
-
connection_file: Path | None = None,
|
214
|
-
) -> None:
|
215
|
-
"""Create a new instance of a tab with a kernel."""
|
216
|
-
# Init tab
|
217
|
-
super().__init__(app, path)
|
218
|
-
|
219
|
-
self.lsps: list[LspClient] = []
|
220
|
-
self.history: History = DummyHistory()
|
221
|
-
self.inspectors: list[Inspector] = []
|
222
|
-
self.inspector = FirstInspector(lambda: self.inspectors)
|
223
|
-
self.suggester: AutoSuggest = DummyAutoSuggest()
|
224
|
-
self.completers: list[Completer] = []
|
225
|
-
self.completer = DeduplicateCompleter(
|
226
|
-
DynamicCompleter(lambda: _MergedCompleter(self.completers))
|
227
|
-
)
|
228
|
-
self.formatters: list[Formatter] = self.app.formatters
|
229
|
-
self.reports: WeakKeyDictionary[LspClient, Report] = WeakKeyDictionary()
|
230
|
-
|
231
|
-
# The client-side comm states
|
232
|
-
self.comms: dict[str, Comm] = {}
|
233
|
-
# The current kernel input
|
234
|
-
self._current_input: KernelInput | None = None
|
235
|
-
|
236
|
-
if self.bg_init:
|
237
|
-
# Load kernel in a background thread
|
238
|
-
run_in_thread_with_context(
|
239
|
-
partial(
|
240
|
-
self.init_kernel, kernel, comms, use_kernel_history, connection_file
|
241
|
-
)
|
242
|
-
)
|
243
|
-
else:
|
244
|
-
self.init_kernel(kernel, comms, use_kernel_history, connection_file)
|
245
|
-
|
246
|
-
async def load_lsps(self) -> None:
|
247
|
-
"""Load the LSP clients."""
|
248
|
-
path = self.path
|
249
|
-
|
250
|
-
# Load list of LSP clients for the tab's language
|
251
|
-
self.lsps.extend(self.app.get_language_lsps(self.language))
|
252
|
-
|
253
|
-
# Wait for all lsps to be initialized, and setup hooks as they become ready
|
254
|
-
async def _await_load(lsp: LspClient) -> LspClient:
|
255
|
-
await lsp.initialized.wait()
|
256
|
-
return lsp
|
257
|
-
|
258
|
-
for ready in asyncio.as_completed([_await_load(lsp) for lsp in self.lsps]):
|
259
|
-
lsp = await ready
|
260
|
-
# Apply open, save, and close hooks to the tab
|
261
|
-
change_handler = partial(lambda lsp, tab: self.lsp_change_handler(lsp), lsp)
|
262
|
-
close_handler = partial(lambda lsp, tab: self.lsp_close_handler(lsp), lsp)
|
263
|
-
before_save_handler = partial(
|
264
|
-
lambda lsp, tab: self.lsp_before_save_handler(lsp), lsp
|
265
|
-
)
|
266
|
-
after_save_handler = partial(
|
267
|
-
lambda lsp, tab: self.lsp_after_save_handler(lsp), lsp
|
268
|
-
)
|
269
|
-
|
270
|
-
self.on_close += close_handler
|
271
|
-
self.on_change += change_handler
|
272
|
-
self.before_save += before_save_handler
|
273
|
-
self.after_save += after_save_handler
|
274
|
-
|
275
|
-
# Listen for LSP diagnostics
|
276
|
-
lsp.on_diagnostics += self.lsp_update_diagnostics
|
277
|
-
|
278
|
-
# Add completer
|
279
|
-
completer = LspCompleter(lsp=lsp, path=path)
|
280
|
-
self.completers.append(completer)
|
281
|
-
|
282
|
-
# Add inspector
|
283
|
-
inspector = LspInspector(lsp, path)
|
284
|
-
self.inspectors.append(inspector)
|
285
|
-
|
286
|
-
# Add formatter
|
287
|
-
formatter = LspFormatter(lsp, path)
|
288
|
-
self.formatters.append(formatter)
|
289
|
-
|
290
|
-
# Remove hooks if the LSP exits
|
291
|
-
def lsp_unload(lsp: LspClient) -> None:
|
292
|
-
self.on_change -= change_handler # noqa: B023
|
293
|
-
self.before_save -= before_save_handler # noqa: B023
|
294
|
-
self.after_save -= after_save_handler # noqa: B023
|
295
|
-
self.on_close -= close_handler # noqa: B023
|
296
|
-
if completer in self.completers: # noqa: B023
|
297
|
-
self.completers.remove(completer) # noqa: B023
|
298
|
-
if inspector in self.completers: # noqa: B023
|
299
|
-
self.inspectors.remove(inspector) # noqa: B023
|
300
|
-
if formatter in self.completers: # noqa: B023
|
301
|
-
self.formatters.remove(formatter) # noqa: B023
|
302
|
-
if completer in self.completers: # noqa: B023
|
303
|
-
self.completers.remove(completer) # noqa: B023
|
304
|
-
if inspector in self.inspectors: # noqa: B023
|
305
|
-
self.inspectors.remove(inspector) # noqa: B023
|
306
|
-
if formatter in self.formatters: # noqa: B023
|
307
|
-
self.formatters.remove(formatter) # noqa: B023
|
308
|
-
|
309
|
-
lsp.on_exit += lsp_unload
|
310
|
-
|
311
|
-
# Remove the lsp exit handler if this tab closes
|
312
|
-
self.on_close += lambda tab: (
|
313
|
-
(lsp.on_exit.__isub__(lsp_unload) and None) or None # noqa: B023
|
314
|
-
) # Magical typing
|
315
|
-
|
316
|
-
# Tell the LSP we have an open file
|
317
|
-
self.lsp_open_handler(lsp)
|
318
|
-
|
319
|
-
def pre_init_kernel(self) -> None:
|
320
|
-
"""Run stuff before the kernel is loaded."""
|
321
|
-
|
322
|
-
def post_init_kernel(self) -> None:
|
323
|
-
"""Run stuff after the kernel is loaded."""
|
324
|
-
|
325
|
-
def init_kernel(
|
326
|
-
self,
|
327
|
-
kernel: Kernel | None = None,
|
328
|
-
comms: dict[str, Comm] | None = None,
|
329
|
-
use_kernel_history: bool = False,
|
330
|
-
connection_file: Path | None = None,
|
331
|
-
) -> None:
|
332
|
-
"""Set up the tab's kernel and related components."""
|
333
|
-
self.pre_init_kernel()
|
334
|
-
|
335
|
-
self.kernel_queue: deque[Callable] = deque()
|
336
|
-
|
337
|
-
if kernel:
|
338
|
-
self.kernel = kernel
|
339
|
-
self.kernel.default_callbacks = self.default_callbacks
|
340
|
-
else:
|
341
|
-
self.kernel = Kernel(
|
342
|
-
kernel_tab=self,
|
343
|
-
allow_stdin=self.allow_stdin,
|
344
|
-
default_callbacks=self.default_callbacks,
|
345
|
-
connection_file=connection_file,
|
346
|
-
)
|
347
|
-
self.comms = comms or {} # The client-side comm states
|
348
|
-
self.completers.append(KernelCompleter(self.kernel))
|
349
|
-
self.inspectors.append(KernelInspector(self.kernel))
|
350
|
-
self.use_kernel_history = use_kernel_history
|
351
|
-
self.history = (
|
352
|
-
KernelHistory(self.kernel) if use_kernel_history else InMemoryHistory()
|
353
|
-
)
|
354
|
-
self.suggester = HistoryAutoSuggest(self.history)
|
355
|
-
|
356
|
-
self.app.create_background_task(self.load_lsps())
|
357
|
-
|
358
|
-
self.post_init_kernel()
|
359
|
-
|
360
|
-
def close(self, cb: Callable | None = None) -> None:
|
361
|
-
"""Shut down kernel when tab is closed."""
|
362
|
-
if hasattr(self, "kernel"):
|
363
|
-
self.kernel.shutdown()
|
364
|
-
super().close(cb)
|
365
|
-
|
366
|
-
def interrupt_kernel(self) -> None:
|
367
|
-
"""Interrupt the current `Notebook`'s kernel."""
|
368
|
-
self.kernel.interrupt()
|
369
|
-
|
370
|
-
def restart_kernel(self, cb: Callable | None = None) -> None:
|
371
|
-
"""Restart the current `Notebook`'s kernel."""
|
372
|
-
|
373
|
-
def _cb(result: dict[str, Any]) -> None:
|
374
|
-
if callable(cb):
|
375
|
-
cb()
|
376
|
-
|
377
|
-
if confirm := self.app.dialogs.get("confirm"):
|
378
|
-
confirm.show(
|
379
|
-
message="Are you sure you want to restart the kernel?",
|
380
|
-
cb=partial(self.kernel.restart, cb=_cb),
|
381
|
-
)
|
382
|
-
else:
|
383
|
-
self.kernel.restart(cb=_cb)
|
384
|
-
|
385
|
-
def kernel_started(self, result: dict[str, Any] | None = None) -> None:
|
386
|
-
"""Task to run when the kernel has started."""
|
387
|
-
# Check kernel has not failed
|
388
|
-
if not self.kernel_name or self.kernel.missing:
|
389
|
-
if not self.kernel_name:
|
390
|
-
msg = "No kernel selected"
|
391
|
-
else:
|
392
|
-
msg = f"Kernel '{self.kernel_display_name}' not installed"
|
393
|
-
self.change_kernel(
|
394
|
-
msg=msg,
|
395
|
-
startup=True,
|
396
|
-
)
|
397
|
-
|
398
|
-
elif self.kernel.status == "error":
|
399
|
-
self.report_kernel_error(self.kernel.error)
|
400
|
-
|
401
|
-
else:
|
402
|
-
# Wait for an idle kernel
|
403
|
-
if self.kernel.status != "idle":
|
404
|
-
self.kernel.wait_for_status("idle")
|
405
|
-
|
406
|
-
# Load widget comm info
|
407
|
-
# self.kernel.comm_info(target_name="jupyter.widget")
|
408
|
-
|
409
|
-
# Load kernel info
|
410
|
-
self.kernel.info(set_kernel_info=self.set_kernel_info)
|
411
|
-
|
412
|
-
# Load kernel history
|
413
|
-
if self.use_kernel_history:
|
414
|
-
self.app.create_background_task(self.load_history())
|
415
|
-
|
416
|
-
# Run queued kernel tasks when the kernel is idle
|
417
|
-
log.debug("Running %d kernel tasks", len(self.kernel_queue))
|
418
|
-
while self.kernel_queue:
|
419
|
-
self.kernel_queue.popleft()()
|
420
|
-
|
421
|
-
self.app.invalidate()
|
422
|
-
|
423
|
-
def report_kernel_error(self, error: Exception | None) -> None:
|
424
|
-
"""Report a kernel error to the user."""
|
425
|
-
log.debug("Kernel error", exc_info=error)
|
426
|
-
|
427
|
-
async def load_history(self) -> None:
|
428
|
-
"""Load kernel history."""
|
429
|
-
try:
|
430
|
-
await self.history.load().__anext__()
|
431
|
-
except StopAsyncIteration:
|
432
|
-
pass
|
433
|
-
|
434
|
-
@property
|
435
|
-
def metadata(self) -> dict[str, Any]:
|
436
|
-
"""Return a dictionary to hold notebook / kernel metadata."""
|
437
|
-
return self._metadata
|
438
|
-
|
439
|
-
@property
|
440
|
-
def kernel_name(self) -> str:
|
441
|
-
"""Return the name of the kernel defined in the notebook JSON."""
|
442
|
-
return self.metadata.get("kernelspec", {}).get(
|
443
|
-
"name", self.app.config.kernel_name
|
444
|
-
)
|
445
|
-
|
446
|
-
@kernel_name.setter
|
447
|
-
def kernel_name(self, value: str) -> None:
|
448
|
-
"""Return the name of the kernel defined in the notebook JSON."""
|
449
|
-
self.metadata.setdefault("kernelspec", {})["name"] = value
|
450
|
-
|
451
|
-
@property
|
452
|
-
def language(self) -> str:
|
453
|
-
"""Return the name of the kernel defined in the notebook JSON."""
|
454
|
-
return self.metadata.get("kernelspec", {}).get("language")
|
455
|
-
|
456
|
-
@property
|
457
|
-
def kernel_display_name(self) -> str:
|
458
|
-
"""Return the display name of the kernel defined in the notebook JSON."""
|
459
|
-
return self.metadata.get("kernelspec", {}).get("display_name", self.kernel_name)
|
460
|
-
|
461
|
-
@property
|
462
|
-
def kernel_lang_file_ext(self) -> str:
|
463
|
-
"""Return the display name of the kernel defined in the notebook JSON."""
|
464
|
-
return self.metadata.get("language_info", {}).get("file_extension", ".py")
|
465
|
-
|
466
|
-
@property
|
467
|
-
def current_input(self) -> KernelInput:
|
468
|
-
"""Return the currently active kernel input, if any."""
|
469
|
-
return self._current_input or KernelInput(self)
|
470
|
-
|
471
|
-
def set_kernel_info(self, info: dict) -> None:
|
472
|
-
"""Handle kernel info requests."""
|
473
|
-
self.metadata["language_info"] = info.get("language_info", {})
|
474
|
-
|
475
|
-
def change_kernel(self, msg: str | None = None, startup: bool = False) -> None:
|
476
|
-
"""Prompt the user to select a new kernel."""
|
477
|
-
kernel_specs = self.kernel.specs
|
478
|
-
|
479
|
-
# Warn the user if no kernels are installed
|
480
|
-
if not kernel_specs:
|
481
|
-
if startup and "no-kernels" in self.app.dialogs:
|
482
|
-
self.app.dialogs["no-kernels"].show()
|
483
|
-
return
|
484
|
-
|
485
|
-
# Automatically select the only kernel if there is only one
|
486
|
-
if startup and len(kernel_specs) == 1:
|
487
|
-
self.kernel.change(next(iter(kernel_specs)))
|
488
|
-
return
|
489
|
-
|
490
|
-
self.app.dialogs["change-kernel"].show(
|
491
|
-
tab=self, message=msg, kernel_specs=kernel_specs
|
492
|
-
)
|
493
|
-
|
494
|
-
def comm_open(self, content: dict, buffers: Sequence[bytes]) -> None:
|
495
|
-
"""Register a new kernel Comm object in the notebook."""
|
496
|
-
comm_id = str(content.get("comm_id"))
|
497
|
-
self.comms[comm_id] = open_comm(
|
498
|
-
comm_container=self, content=content, buffers=buffers
|
499
|
-
)
|
500
|
-
|
501
|
-
def comm_msg(self, content: dict, buffers: Sequence[bytes]) -> None:
|
502
|
-
"""Respond to a Comm message from the kernel."""
|
503
|
-
comm_id = str(content.get("comm_id"))
|
504
|
-
if comm := self.comms.get(comm_id):
|
505
|
-
comm.process_data(content.get("data", {}), buffers)
|
506
|
-
|
507
|
-
def comm_close(self, content: dict, buffers: Sequence[bytes]) -> None:
|
508
|
-
"""Close a notebook Comm."""
|
509
|
-
comm_id = content.get("comm_id")
|
510
|
-
if comm_id in self.comms:
|
511
|
-
del self.comms[comm_id]
|
512
|
-
|
513
|
-
def lsp_open_handler(self, lsp: LspClient) -> None:
|
514
|
-
"""Tell the LSP we opened a file."""
|
515
|
-
lsp.open_doc(
|
516
|
-
path=self.path, language=self.language, text=self.current_input.buffer.text
|
517
|
-
)
|
518
|
-
|
519
|
-
def lsp_change_handler(self, lsp: LspClient) -> None:
|
520
|
-
"""Tell the LSP server a file has changed."""
|
521
|
-
lsp.change_doc(
|
522
|
-
path=self.path,
|
523
|
-
language=self.language,
|
524
|
-
text=self.current_input.buffer.text,
|
525
|
-
)
|
526
|
-
|
527
|
-
def lsp_before_save_handler(self, lsp: LspClient) -> None:
|
528
|
-
"""Tell the the LSP we are about to save a document."""
|
529
|
-
lsp.will_save_doc(self.path)
|
530
|
-
|
531
|
-
def lsp_after_save_handler(self, lsp: LspClient) -> None:
|
532
|
-
"""Tell the the LSP we saved a document."""
|
533
|
-
lsp.save_doc(self.path, text=self.current_input.buffer.text)
|
534
|
-
|
535
|
-
def lsp_close_handler(self, lsp: LspClient) -> None:
|
536
|
-
"""Tell the LSP we opened a file."""
|
537
|
-
lsp.close_doc(path=self.path)
|
538
|
-
|
539
|
-
def lsp_update_diagnostics(self, lsp: LspClient) -> None:
|
540
|
-
"""Process a new diagnostic report from the LSP."""
|
541
|
-
if (diagnostics := lsp.reports.pop(self.path.as_uri(), None)) is not None:
|
542
|
-
self.reports[lsp] = Report.from_lsp(self.current_input.text, diagnostics)
|
543
|
-
self.app.invalidate()
|
544
|
-
|
545
|
-
def report(self) -> Report:
|
546
|
-
"""Return the current diagnostic reports."""
|
547
|
-
return Report.from_reports(*self.reports.values())
|
548
|
-
|
549
|
-
# ################################### Commands ####################################
|
550
|
-
|
551
|
-
@staticmethod
|
552
|
-
@add_cmd(filter=kernel_tab_has_focus)
|
553
|
-
def _change_kernel() -> None:
|
554
|
-
"""Change the notebook's kernel."""
|
555
|
-
if isinstance(kt := get_app().tab, KernelTab):
|
556
|
-
kt.change_kernel()
|
557
|
-
|
558
|
-
# ################################### Settings ####################################
|
559
|
-
|
560
|
-
add_setting(
|
561
|
-
name="kernel_name",
|
562
|
-
flags=["--kernel-name", "--kernel"],
|
563
|
-
type_=str,
|
564
|
-
help_="The name of the kernel to start by default",
|
565
|
-
default="python3",
|
566
|
-
description="""
|
567
|
-
The name of the kernel selected automatically by the console app or in new
|
568
|
-
notebooks. If set to an empty string, the user will be asked which kernel
|
569
|
-
to launch.
|
570
|
-
""",
|
571
|
-
)
|
572
|
-
|
573
|
-
add_setting(
|
574
|
-
name="record_cell_timing",
|
575
|
-
title="cell timing recording",
|
576
|
-
flags=["--record-cell-timing"],
|
577
|
-
type_=bool,
|
578
|
-
help_="Should timing data be recorded in cell metadata.",
|
579
|
-
default=False,
|
580
|
-
schema={
|
581
|
-
"type": "boolean",
|
582
|
-
},
|
583
|
-
description="""
|
584
|
-
When set, execution timing data will be recorded in cell metadata.
|
585
|
-
""",
|
586
|
-
)
|
587
|
-
|
588
|
-
add_setting(
|
589
|
-
name="show_remote_inputs",
|
590
|
-
flags=["--show-remote-inputs"],
|
591
|
-
type_=bool,
|
592
|
-
help_="Display inputs sent to the kernel by other clients",
|
593
|
-
default=True,
|
594
|
-
description="""
|
595
|
-
If set to `True`, all code input sent to the kernel by any client will be
|
596
|
-
displayed.
|
597
|
-
|
598
|
-
If set to `False`, only inputs sent to the kernel by the current instance
|
599
|
-
of euporie will be displayed, and all other inputs will be ignored.
|
600
|
-
|
601
|
-
""",
|
602
|
-
)
|
603
|
-
|
604
|
-
add_setting(
|
605
|
-
name="show_remote_outputs",
|
606
|
-
flags=["--show-remote-outputs"],
|
607
|
-
type_=bool,
|
608
|
-
help_="Display kernel outputs triggered by other clients",
|
609
|
-
default=True,
|
610
|
-
description="""
|
611
|
-
If set to `False`, only outputs generated by code input from the current
|
612
|
-
instance of euporie will be displayed, and all other outputs will be
|
613
|
-
ignored.
|
614
|
-
|
615
|
-
If set to `True`, all outputs generated by the kernel will be
|
616
|
-
displayed.
|
617
|
-
""",
|
618
|
-
)
|