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/tabs/kernel.py
CHANGED
@@ -6,11 +6,11 @@ import asyncio
|
|
6
6
|
import logging
|
7
7
|
from abc import ABCMeta
|
8
8
|
from collections import deque
|
9
|
-
from functools import partial
|
9
|
+
from functools import lru_cache, partial
|
10
10
|
from typing import TYPE_CHECKING
|
11
11
|
from weakref import WeakKeyDictionary
|
12
12
|
|
13
|
-
from prompt_toolkit.auto_suggest import DummyAutoSuggest
|
13
|
+
from prompt_toolkit.auto_suggest import DummyAutoSuggest, DynamicAutoSuggest
|
14
14
|
from prompt_toolkit.completion.base import (
|
15
15
|
DynamicCompleter,
|
16
16
|
_MergedCompleter,
|
@@ -30,10 +30,9 @@ from euporie.core.inspection import (
|
|
30
30
|
KernelInspector,
|
31
31
|
LspInspector,
|
32
32
|
)
|
33
|
-
from euporie.core.kernel
|
34
|
-
from euporie.core.
|
33
|
+
from euporie.core.kernel import list_kernels
|
34
|
+
from euporie.core.kernel.base import NoKernel
|
35
35
|
from euporie.core.tabs.base import Tab
|
36
|
-
from euporie.core.utils import run_in_thread_with_context
|
37
36
|
|
38
37
|
if TYPE_CHECKING:
|
39
38
|
from collections.abc import Sequence
|
@@ -48,16 +47,33 @@ if TYPE_CHECKING:
|
|
48
47
|
from euporie.core.comm.base import Comm
|
49
48
|
from euporie.core.format import Formatter
|
50
49
|
from euporie.core.inspection import Inspector
|
50
|
+
from euporie.core.kernel.base import BaseKernel, KernelFactory, MsgCallbacks
|
51
51
|
from euporie.core.lsp import LspClient
|
52
52
|
from euporie.core.widgets.inputs import KernelInput
|
53
53
|
|
54
54
|
log = logging.getLogger(__name__)
|
55
55
|
|
56
56
|
|
57
|
+
@lru_cache
|
58
|
+
def autosuggest_factory(kind: str, history: History) -> AutoSuggest:
|
59
|
+
"""Generate autosuggesters."""
|
60
|
+
if kind == "smart":
|
61
|
+
from euporie.core.suggest import SmartHistoryAutoSuggest
|
62
|
+
|
63
|
+
return SmartHistoryAutoSuggest(history)
|
64
|
+
elif kind == "simple":
|
65
|
+
from euporie.core.suggest import SimpleHistoryAutoSuggest
|
66
|
+
|
67
|
+
return SimpleHistoryAutoSuggest(history)
|
68
|
+
else:
|
69
|
+
from prompt_toolkit.auto_suggest import DummyAutoSuggest
|
70
|
+
|
71
|
+
return DummyAutoSuggest()
|
72
|
+
|
73
|
+
|
57
74
|
class KernelTab(Tab, metaclass=ABCMeta):
|
58
75
|
"""A Tab which connects to a kernel."""
|
59
76
|
|
60
|
-
kernel: Kernel
|
61
77
|
kernel_language: str
|
62
78
|
_metadata: dict[str, Any]
|
63
79
|
bg_init = False
|
@@ -69,7 +85,7 @@ class KernelTab(Tab, metaclass=ABCMeta):
|
|
69
85
|
self,
|
70
86
|
app: BaseApp,
|
71
87
|
path: Path | None = None,
|
72
|
-
kernel:
|
88
|
+
kernel: BaseKernel | None = None,
|
73
89
|
comms: dict[str, Comm] | None = None,
|
74
90
|
use_kernel_history: bool = False,
|
75
91
|
connection_file: Path | None = None,
|
@@ -78,6 +94,7 @@ class KernelTab(Tab, metaclass=ABCMeta):
|
|
78
94
|
# Init tab
|
79
95
|
super().__init__(app, path)
|
80
96
|
|
97
|
+
self.kernel: BaseKernel = NoKernel(self)
|
81
98
|
self.lsps: list[LspClient] = []
|
82
99
|
self.history: History = DummyHistory()
|
83
100
|
self.inspectors: list[Inspector] = []
|
@@ -97,8 +114,8 @@ class KernelTab(Tab, metaclass=ABCMeta):
|
|
97
114
|
|
98
115
|
if self.bg_init:
|
99
116
|
# Load kernel in a background thread
|
100
|
-
|
101
|
-
|
117
|
+
app.create_background_task(
|
118
|
+
asyncio.to_thread(
|
102
119
|
self.init_kernel, kernel, comms, use_kernel_history, connection_file
|
103
120
|
)
|
104
121
|
)
|
@@ -183,10 +200,12 @@ class KernelTab(Tab, metaclass=ABCMeta):
|
|
183
200
|
|
184
201
|
def post_init_kernel(self) -> None:
|
185
202
|
"""Run stuff after the kernel is loaded."""
|
203
|
+
if not isinstance(self.kernel, NoKernel):
|
204
|
+
self.metadata["kernelspec"] = self.kernel.spec
|
186
205
|
|
187
206
|
def init_kernel(
|
188
207
|
self,
|
189
|
-
kernel:
|
208
|
+
kernel: BaseKernel | None = None,
|
190
209
|
comms: dict[str, Comm] | None = None,
|
191
210
|
use_kernel_history: bool = False,
|
192
211
|
connection_file: Path | None = None,
|
@@ -200,20 +219,47 @@ class KernelTab(Tab, metaclass=ABCMeta):
|
|
200
219
|
self.kernel = kernel
|
201
220
|
self.kernel.default_callbacks = self.default_callbacks
|
202
221
|
else:
|
203
|
-
|
222
|
+
from euporie.core.kernel import list_kernels
|
223
|
+
|
224
|
+
kernel_infos = list_kernels()
|
225
|
+
kernel_name = self.kernel_name or self.app.config.kernel_name
|
226
|
+
for info in kernel_infos:
|
227
|
+
if info.name == kernel_name:
|
228
|
+
factory = info.factory
|
229
|
+
break
|
230
|
+
else:
|
231
|
+
msg = (
|
232
|
+
f"Kernel '{self.kernel_display_name}' not found"
|
233
|
+
if self.kernel_name
|
234
|
+
else "No kernel selected"
|
235
|
+
)
|
236
|
+
self.change_kernel(msg=msg, startup=True)
|
237
|
+
return
|
238
|
+
self.kernel = factory(
|
204
239
|
kernel_tab=self,
|
205
240
|
allow_stdin=self.allow_stdin,
|
206
241
|
default_callbacks=self.default_callbacks,
|
207
|
-
|
242
|
+
**(
|
243
|
+
{"connection_file": connection_file}
|
244
|
+
if connection_file is not None
|
245
|
+
else {}
|
246
|
+
),
|
208
247
|
)
|
248
|
+
|
209
249
|
self.comms = comms or {} # The client-side comm states
|
210
|
-
self.completers.append(KernelCompleter(self.kernel))
|
211
|
-
self.inspectors.append(KernelInspector(self.kernel))
|
250
|
+
self.completers.append(KernelCompleter(lambda: self.kernel))
|
251
|
+
self.inspectors.append(KernelInspector(lambda: self.kernel))
|
212
252
|
self.use_kernel_history = use_kernel_history
|
213
253
|
self.history = (
|
214
|
-
KernelHistory(self.kernel)
|
254
|
+
KernelHistory(lambda: self.kernel)
|
255
|
+
if use_kernel_history
|
256
|
+
else InMemoryHistory()
|
215
257
|
)
|
216
|
-
|
258
|
+
|
259
|
+
def _get_suggester() -> AutoSuggest | None:
|
260
|
+
return autosuggest_factory(self.app.config.autosuggest, self.history)
|
261
|
+
|
262
|
+
self.suggester = DynamicAutoSuggest(_get_suggester)
|
217
263
|
|
218
264
|
self.app.create_background_task(self.load_lsps())
|
219
265
|
|
@@ -221,7 +267,7 @@ class KernelTab(Tab, metaclass=ABCMeta):
|
|
221
267
|
|
222
268
|
def close(self, cb: Callable | None = None) -> None:
|
223
269
|
"""Shut down kernel when tab is closed."""
|
224
|
-
if
|
270
|
+
if self.kernel is not None:
|
225
271
|
self.kernel.shutdown()
|
226
272
|
super().close(cb)
|
227
273
|
|
@@ -233,6 +279,7 @@ class KernelTab(Tab, metaclass=ABCMeta):
|
|
233
279
|
"""Restart the current `Notebook`'s kernel."""
|
234
280
|
|
235
281
|
def _cb(result: dict[str, Any]) -> None:
|
282
|
+
self.kernel_started()
|
236
283
|
if callable(cb):
|
237
284
|
cb()
|
238
285
|
|
@@ -246,18 +293,8 @@ class KernelTab(Tab, metaclass=ABCMeta):
|
|
246
293
|
|
247
294
|
def kernel_started(self, result: dict[str, Any] | None = None) -> None:
|
248
295
|
"""Task to run when the kernel has started."""
|
249
|
-
#
|
250
|
-
if
|
251
|
-
if not self.kernel_name:
|
252
|
-
msg = "No kernel selected"
|
253
|
-
else:
|
254
|
-
msg = f"Kernel '{self.kernel_display_name}' not installed"
|
255
|
-
self.change_kernel(
|
256
|
-
msg=msg,
|
257
|
-
startup=True,
|
258
|
-
)
|
259
|
-
|
260
|
-
elif self.kernel.status == "error":
|
296
|
+
# Set kernel spec in metadata
|
297
|
+
if self.kernel.status == "error":
|
261
298
|
self.report_kernel_error(self.kernel.error)
|
262
299
|
|
263
300
|
else:
|
@@ -301,9 +338,7 @@ class KernelTab(Tab, metaclass=ABCMeta):
|
|
301
338
|
@property
|
302
339
|
def kernel_name(self) -> str:
|
303
340
|
"""Return the name of the kernel defined in the notebook JSON."""
|
304
|
-
return self.metadata.get("kernelspec", {}).get(
|
305
|
-
"name", self.app.config.kernel_name
|
306
|
-
)
|
341
|
+
return self.metadata.get("kernelspec", {}).get("name", "")
|
307
342
|
|
308
343
|
@kernel_name.setter
|
309
344
|
def kernel_name(self, value: str) -> None:
|
@@ -338,22 +373,32 @@ class KernelTab(Tab, metaclass=ABCMeta):
|
|
338
373
|
|
339
374
|
def change_kernel(self, msg: str | None = None, startup: bool = False) -> None:
|
340
375
|
"""Prompt the user to select a new kernel."""
|
341
|
-
|
376
|
+
kernel_infos = list_kernels()
|
342
377
|
|
343
378
|
# Warn the user if no kernels are installed
|
344
|
-
if not
|
379
|
+
if not kernel_infos:
|
345
380
|
if startup and "no-kernels" in self.app.dialogs:
|
346
381
|
self.app.dialogs["no-kernels"].show()
|
347
382
|
return
|
348
383
|
|
349
384
|
# Automatically select the only kernel if there is only one
|
350
|
-
if startup and len(
|
351
|
-
self.
|
385
|
+
if startup and len(kernel_infos) == 1:
|
386
|
+
self.switch_kernel(next(iter(kernel_infos)).factory)
|
352
387
|
return
|
353
388
|
|
354
|
-
|
355
|
-
|
389
|
+
# Prompt user to select a kernel
|
390
|
+
self.app.dialogs["change-kernel"].show(tab=self, message=msg)
|
391
|
+
|
392
|
+
def switch_kernel(self, factory: KernelFactory) -> None:
|
393
|
+
"""Shut down the current kernel and change to another."""
|
394
|
+
if (old_kernel := self.kernel) is not None:
|
395
|
+
old_kernel.shutdown(wait=True)
|
396
|
+
kernel = factory(
|
397
|
+
kernel_tab=self,
|
398
|
+
default_callbacks=self.default_callbacks,
|
399
|
+
allow_stdin=self.allow_stdin,
|
356
400
|
)
|
401
|
+
self.init_kernel(kernel)
|
357
402
|
|
358
403
|
def comm_open(self, content: dict, buffers: Sequence[bytes]) -> None:
|
359
404
|
"""Register a new kernel Comm object in the notebook."""
|
euporie/core/tabs/notebook.py
CHANGED
@@ -12,10 +12,8 @@ import nbformat
|
|
12
12
|
from prompt_toolkit.filters import Never
|
13
13
|
|
14
14
|
from euporie.core.comm.registry import open_comm
|
15
|
-
from euporie.core.commands import get_cmd
|
16
15
|
from euporie.core.io import edit_in_editor
|
17
|
-
from euporie.core.kernel.
|
18
|
-
from euporie.core.path import UntitledPath
|
16
|
+
from euporie.core.kernel.base import MsgCallbacks
|
19
17
|
from euporie.core.tabs.kernel import KernelTab
|
20
18
|
from euporie.core.widgets.cell import Cell, get_cell_id
|
21
19
|
|
@@ -27,15 +25,17 @@ except ModuleNotFoundError:
|
|
27
25
|
from nbformat import write as write_nb
|
28
26
|
|
29
27
|
if TYPE_CHECKING:
|
28
|
+
from collections.abc import Sequence
|
30
29
|
from pathlib import Path
|
31
30
|
from typing import Any, Callable
|
32
31
|
|
33
32
|
from prompt_toolkit.filters import Filter
|
34
33
|
from prompt_toolkit.layout.containers import AnyContainer
|
34
|
+
from prompt_toolkit.layout.controls import BufferControl
|
35
35
|
|
36
36
|
from euporie.core.app.app import BaseApp
|
37
37
|
from euporie.core.comm.base import Comm
|
38
|
-
from euporie.core.kernel.
|
38
|
+
from euporie.core.kernel.base import BaseKernel
|
39
39
|
from euporie.core.lsp import LspClient
|
40
40
|
from euporie.core.widgets.inputs import KernelInput
|
41
41
|
|
@@ -52,7 +52,7 @@ class BaseNotebook(KernelTab, metaclass=ABCMeta):
|
|
52
52
|
self,
|
53
53
|
app: BaseApp,
|
54
54
|
path: Path | None = None,
|
55
|
-
kernel:
|
55
|
+
kernel: BaseKernel | None = None,
|
56
56
|
comms: dict[str, Comm] | None = None,
|
57
57
|
use_kernel_history: bool = False,
|
58
58
|
json: dict[str, Any] | None = None,
|
@@ -92,6 +92,9 @@ class BaseNotebook(KernelTab, metaclass=ABCMeta):
|
|
92
92
|
app, path, kernel=kernel, comms=comms, use_kernel_history=use_kernel_history
|
93
93
|
)
|
94
94
|
|
95
|
+
# Load notebook file
|
96
|
+
self.container = self.load_container()
|
97
|
+
|
95
98
|
# Tab stuff
|
96
99
|
|
97
100
|
def reset(self) -> None:
|
@@ -104,24 +107,10 @@ class BaseNotebook(KernelTab, metaclass=ABCMeta):
|
|
104
107
|
|
105
108
|
# KernelTab stuff
|
106
109
|
|
107
|
-
def pre_init_kernel(self) -> None:
|
108
|
-
"""Run stuff before the kernel is loaded."""
|
109
|
-
super().pre_init_kernel()
|
110
|
-
# Load notebook file
|
111
|
-
self.load()
|
112
|
-
|
113
110
|
def post_init_kernel(self) -> None:
|
114
111
|
"""Load the notebook container after the kernel has been loaded."""
|
115
112
|
super().post_init_kernel()
|
116
113
|
|
117
|
-
# Replace the tab's container
|
118
|
-
prev = self.container
|
119
|
-
self.container = self.load_container()
|
120
|
-
self.loaded = True
|
121
|
-
# Update the focus if the old container had focus
|
122
|
-
if self.app.layout.has_focus(prev):
|
123
|
-
self.focus()
|
124
|
-
|
125
114
|
# Load widgets
|
126
115
|
self.load_widgets_from_metadata()
|
127
116
|
|
@@ -179,8 +168,7 @@ class BaseNotebook(KernelTab, metaclass=ABCMeta):
|
|
179
168
|
|
180
169
|
@abstractmethod
|
181
170
|
def load_container(self) -> AnyContainer:
|
182
|
-
"""
|
183
|
-
...
|
171
|
+
"""Absract method for loading the notebook's main container."""
|
184
172
|
|
185
173
|
@abstractproperty
|
186
174
|
def cell(self) -> Cell:
|
@@ -270,23 +258,20 @@ class BaseNotebook(KernelTab, metaclass=ABCMeta):
|
|
270
258
|
if self.dirty and (unsaved := self.app.dialogs.get("unsaved")):
|
271
259
|
unsaved.show(
|
272
260
|
tab=self,
|
273
|
-
cb=cb,
|
261
|
+
cb=partial(super().close, cb),
|
274
262
|
)
|
275
263
|
else:
|
276
264
|
super().close(cb)
|
277
265
|
|
278
|
-
def
|
266
|
+
def write_file(self, path: Path) -> None:
|
279
267
|
"""Write the notebook's JSON to the current notebook's file.
|
280
268
|
|
281
269
|
Additionally save the widget state to the notebook metadata.
|
282
270
|
|
283
271
|
Args:
|
284
|
-
path: An
|
285
|
-
cb: A callback to run if after saving the notebook.
|
272
|
+
path: An path at which to save the file
|
286
273
|
|
287
274
|
"""
|
288
|
-
if path is not None:
|
289
|
-
self.path = path
|
290
275
|
if self.app.config.save_widget_state:
|
291
276
|
self.json.setdefault("metadata", {})["widgets"] = {
|
292
277
|
"application/vnd.jupyter.widget-state+json": {
|
@@ -299,58 +284,21 @@ class BaseNotebook(KernelTab, metaclass=ABCMeta):
|
|
299
284
|
},
|
300
285
|
}
|
301
286
|
}
|
302
|
-
|
303
|
-
if dialog := self.app.dialogs.get("save-as"):
|
304
|
-
dialog.show(tab=self, cb=cb)
|
305
|
-
else:
|
306
|
-
log.debug("Saving notebook..")
|
307
|
-
self.saving = True
|
308
|
-
self.app.invalidate()
|
309
|
-
# Ensure parent path exists
|
310
|
-
parent = self.path.parent
|
311
|
-
parent.mkdir(exist_ok=True, parents=True)
|
312
|
-
# Save to a temp file, then replace the original
|
313
|
-
temp_path = parent / f".{self.path.stem}.tmp{self.path.suffix}"
|
314
|
-
log.debug("Using temporary file %s", temp_path.name)
|
287
|
+
with path.open("w") as open_file:
|
315
288
|
try:
|
316
|
-
|
317
|
-
except
|
318
|
-
get_cmd("save-as").run()
|
319
|
-
else:
|
289
|
+
write_nb(nb=nbformat.from_dict(self.json), fp=open_file)
|
290
|
+
except AssertionError:
|
320
291
|
try:
|
321
|
-
|
322
|
-
|
323
|
-
except AssertionError:
|
324
|
-
try:
|
325
|
-
# Jupytext requires a filename if we don't give it a format
|
326
|
-
write_nb(nb=nbformat.from_dict(self.json), fp=temp_path)
|
327
|
-
except Exception:
|
328
|
-
# Jupytext requires a format if the path has no extension
|
329
|
-
# We just use ipynb as the default format
|
330
|
-
write_nb(
|
331
|
-
nb=nbformat.from_dict(self.json),
|
332
|
-
fp=open_file,
|
333
|
-
fmt="ipynb",
|
334
|
-
)
|
292
|
+
# Jupytext requires a filename if we don't give it a format
|
293
|
+
write_nb(nb=nbformat.from_dict(self.json), fp=path)
|
335
294
|
except Exception:
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
except Exception:
|
344
|
-
if dialog := self.app.dialogs.get("save-as"):
|
345
|
-
dialog.show(tab=self, cb=cb)
|
346
|
-
else:
|
347
|
-
self.dirty = False
|
348
|
-
self.saving = False
|
349
|
-
self.app.invalidate()
|
350
|
-
log.debug("Notebook saved")
|
351
|
-
# Run the callback
|
352
|
-
if callable(cb):
|
353
|
-
cb()
|
295
|
+
# Jupytext requires a format if the path has no extension
|
296
|
+
# We just use ipynb as the default format
|
297
|
+
write_nb(
|
298
|
+
nb=nbformat.from_dict(self.json),
|
299
|
+
fp=open_file,
|
300
|
+
fmt="ipynb",
|
301
|
+
)
|
354
302
|
|
355
303
|
def run_cell(
|
356
304
|
self,
|
@@ -462,3 +410,7 @@ class BaseNotebook(KernelTab, metaclass=ABCMeta):
|
|
462
410
|
def lsp_update_diagnostics(self, lsp: LspClient) -> None:
|
463
411
|
"""Process a new diagnostic report from the LSP."""
|
464
412
|
# Do nothing, these are handled by cells
|
413
|
+
|
414
|
+
def __pt_searchables__(self) -> Sequence[BufferControl]:
|
415
|
+
"""Return list of cell input buffer controls for searching."""
|
416
|
+
return [cell.input_box.control for cell in self.rendered_cells()]
|
euporie/core/utils.py
CHANGED
@@ -1,12 +1,10 @@
|
|
1
|
-
"""
|
1
|
+
"""Miscellaneous utility classes."""
|
2
2
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
|
-
import contextvars
|
6
5
|
from collections.abc import Sequence
|
7
6
|
from functools import cache
|
8
7
|
from itertools import chain
|
9
|
-
from threading import Thread
|
10
8
|
from typing import TYPE_CHECKING, TypeVar, overload
|
11
9
|
|
12
10
|
from prompt_toolkit.mouse_events import MouseButton, MouseEventType
|
@@ -14,7 +12,7 @@ from prompt_toolkit.mouse_events import MouseButton, MouseEventType
|
|
14
12
|
if TYPE_CHECKING:
|
15
13
|
from collections.abc import Iterable
|
16
14
|
from types import ModuleType
|
17
|
-
from typing import
|
15
|
+
from typing import Callable
|
18
16
|
|
19
17
|
from prompt_toolkit.key_binding.key_bindings import NotImplementedOrNone
|
20
18
|
from prompt_toolkit.layout.mouse_handlers import MouseHandler
|
@@ -78,21 +76,6 @@ def on_click(func: Callable) -> MouseHandler:
|
|
78
76
|
return _mouse_handler
|
79
77
|
|
80
78
|
|
81
|
-
def run_in_thread_with_context(
|
82
|
-
func: Callable, *args: Any, daemon: bool = True, **kwargs: Any
|
83
|
-
) -> None:
|
84
|
-
"""Run a function in an thread, but make sure it uses the same contextvars.
|
85
|
-
|
86
|
-
This is required so that the function will see the right application.
|
87
|
-
"""
|
88
|
-
Thread(
|
89
|
-
target=contextvars.copy_context().run,
|
90
|
-
args=(func, *args),
|
91
|
-
kwargs=kwargs,
|
92
|
-
daemon=daemon,
|
93
|
-
).start()
|
94
|
-
|
95
|
-
|
96
79
|
@cache
|
97
80
|
def root_module(name: str) -> ModuleType:
|
98
81
|
"""Find and load the root module of a given module name by traversing up the module hierarchy.
|
euporie/core/validation.py
CHANGED
@@ -9,29 +9,29 @@ from prompt_toolkit.validation import ValidationError, Validator
|
|
9
9
|
if TYPE_CHECKING:
|
10
10
|
from prompt_toolkit.document import Document
|
11
11
|
|
12
|
-
from euporie.core.kernel.
|
12
|
+
from euporie.core.kernel.base import BaseKernel
|
13
13
|
|
14
14
|
|
15
15
|
class KernelValidator(Validator):
|
16
16
|
"""Validate kernel input using a kernel code completeness call."""
|
17
17
|
|
18
|
-
def __init__(self, kernel:
|
18
|
+
def __init__(self, kernel: BaseKernel) -> None:
|
19
19
|
"""Initialize the validator."""
|
20
20
|
self.kernel = kernel
|
21
21
|
|
22
22
|
def validate(self, document: Document) -> None:
|
23
23
|
"""Validate the input synchronously."""
|
24
|
-
completeness_status = self.kernel.is_complete(
|
25
|
-
|
26
|
-
)
|
24
|
+
completeness_status = self.kernel.is_complete(source=document.text).get(
|
25
|
+
"status", "unknown"
|
26
|
+
)
|
27
27
|
if completeness_status == "incomplete":
|
28
28
|
raise ValidationError
|
29
29
|
|
30
30
|
async def validate_async(self, document: Document) -> None:
|
31
31
|
"""Return a `Future` which is set when the validation is ready."""
|
32
|
-
completeness_status = (
|
33
|
-
|
34
|
-
)
|
32
|
+
completeness_status = (
|
33
|
+
await self.kernel.is_complete_async(source=document.text)
|
34
|
+
).get("status", "unknown")
|
35
35
|
if completeness_status == "incomplete":
|
36
36
|
raise ValidationError
|
37
37
|
return
|
@@ -22,6 +22,22 @@ add_setting(
|
|
22
22
|
cmd_filter=~buffer_has_focus,
|
23
23
|
)
|
24
24
|
|
25
|
+
add_setting(
|
26
|
+
name="text_output_limit",
|
27
|
+
group="euporie.core.widgets.cell_outputs",
|
28
|
+
flags=["--text-output-limit"],
|
29
|
+
type_=int,
|
30
|
+
help_="Limit the amount of cell text output",
|
31
|
+
default=1_000_000,
|
32
|
+
schema={
|
33
|
+
"minimum": 0,
|
34
|
+
},
|
35
|
+
description="""
|
36
|
+
Limit the number of text characters in interactive cell text output to this value.
|
37
|
+
Use ``0`` to allow any amount of characters.
|
38
|
+
""",
|
39
|
+
)
|
40
|
+
|
25
41
|
# euporie,core.widgets.file_browser:FileBrowser
|
26
42
|
|
27
43
|
add_setting(
|
@@ -86,9 +102,10 @@ add_setting(
|
|
86
102
|
name="autosuggest",
|
87
103
|
group="euporie.core.widgets.inputs",
|
88
104
|
flags=["--autosuggest"],
|
89
|
-
type_=
|
105
|
+
type_=str,
|
106
|
+
choices=["smart", "simple", "none"],
|
90
107
|
help_="Provide line completion suggestions",
|
91
|
-
default=
|
108
|
+
default="smart",
|
92
109
|
description="""
|
93
110
|
Whether to automatically suggestion line content while typing in code cells.
|
94
111
|
""",
|
euporie/core/widgets/cell.py
CHANGED
@@ -6,7 +6,7 @@ import asyncio
|
|
6
6
|
import logging
|
7
7
|
import os
|
8
8
|
import weakref
|
9
|
-
from functools import partial
|
9
|
+
from functools import lru_cache, partial
|
10
10
|
from pathlib import Path
|
11
11
|
from typing import TYPE_CHECKING, cast
|
12
12
|
from weakref import WeakKeyDictionary
|
@@ -50,6 +50,7 @@ if TYPE_CHECKING:
|
|
50
50
|
from prompt_toolkit.completion.base import Completer
|
51
51
|
from prompt_toolkit.formatted_text.base import StyleAndTextTuples
|
52
52
|
|
53
|
+
from euporie.core.border import GridStyle
|
53
54
|
from euporie.core.format import Formatter
|
54
55
|
from euporie.core.inspection import Inspector
|
55
56
|
from euporie.core.lsp import LspClient
|
@@ -79,6 +80,18 @@ def get_cell_id(cell_json: dict) -> str:
|
|
79
80
|
return cell_id
|
80
81
|
|
81
82
|
|
83
|
+
@lru_cache(maxsize=32)
|
84
|
+
def _get_border_style(
|
85
|
+
selected: bool, focused: bool, show_borders: bool, multi_selected: bool
|
86
|
+
) -> GridStyle:
|
87
|
+
"""Get the border style grid based on cell state."""
|
88
|
+
if not (show_borders or selected):
|
89
|
+
return NoLine.grid
|
90
|
+
if focused and multi_selected:
|
91
|
+
return ThickLine.outer
|
92
|
+
return ThinLine.outer
|
93
|
+
|
94
|
+
|
82
95
|
class Cell:
|
83
96
|
"""A kernel_tab cell element.
|
84
97
|
|
@@ -86,8 +99,6 @@ class Cell:
|
|
86
99
|
focused.
|
87
100
|
"""
|
88
101
|
|
89
|
-
input_box: KernelInput
|
90
|
-
|
91
102
|
def __init__(
|
92
103
|
self, index: int, json: dict, kernel_tab: BaseNotebook, is_new: bool = False
|
93
104
|
) -> None:
|
@@ -158,6 +169,14 @@ class Cell:
|
|
158
169
|
"""Update cell json when the input buffer has been edited."""
|
159
170
|
weak_self._set_input(buf.text)
|
160
171
|
weak_self.kernel_tab.dirty = True
|
172
|
+
weak_self.on_change()
|
173
|
+
# Re-render markdown cells when edited outside of edit mode
|
174
|
+
if (
|
175
|
+
weak_self.cell_type == "markdown"
|
176
|
+
and not weak_self.kernel_tab.in_edit_mode()
|
177
|
+
):
|
178
|
+
weak_self.output_area.json = weak_self.output_json
|
179
|
+
weak_self.refresh()
|
161
180
|
|
162
181
|
def on_cursor_position_changed(buf: Buffer) -> None:
|
163
182
|
"""Respond to cursor movements."""
|
@@ -172,7 +191,7 @@ class Cell:
|
|
172
191
|
# Now we generate the main container used to represent a kernel_tab cell
|
173
192
|
|
174
193
|
source_hidden = Condition(
|
175
|
-
lambda: weak_self.json
|
194
|
+
lambda: weak_self.json.get("metadata", {})
|
176
195
|
.get("jupyter", {})
|
177
196
|
.get("source_hidden", False)
|
178
197
|
)
|
@@ -199,42 +218,22 @@ class Cell:
|
|
199
218
|
)
|
200
219
|
self.input_box.buffer.name = self.cell_type
|
201
220
|
|
202
|
-
self.input_box.buffer.on_text_changed += lambda buf: weak_self.on_change()
|
203
|
-
|
204
221
|
def border_char(name: str) -> Callable[..., str]:
|
205
222
|
"""Return a function which returns the cell border character to display."""
|
206
223
|
|
207
224
|
def _inner() -> str:
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
grid = ThinLine.outer
|
225
|
+
if not weak_self:
|
226
|
+
return " "
|
227
|
+
grid = _get_border_style(
|
228
|
+
weak_self.selected,
|
229
|
+
weak_self.focused,
|
230
|
+
weak_self.kernel_tab.app.config.show_cell_borders,
|
231
|
+
multiple_cells_selected(),
|
232
|
+
)
|
217
233
|
return getattr(grid, name.upper())
|
218
234
|
|
219
235
|
return _inner
|
220
236
|
|
221
|
-
# @lru_cache(maxsize=None)
|
222
|
-
# def _cell_border_char(
|
223
|
-
# name: str,
|
224
|
-
# show_cell_borders: bool,
|
225
|
-
# focused: bool,
|
226
|
-
# selected: bool,
|
227
|
-
# multiple_cells_selected: bool,
|
228
|
-
# ) -> str:
|
229
|
-
# if show_cell_borders or selected:
|
230
|
-
# if focused and multiple_cells_selected:
|
231
|
-
# grid = ThickLine.outer
|
232
|
-
# else:
|
233
|
-
# grid = ThinLine.outer
|
234
|
-
# else:
|
235
|
-
# grid = NoLine.grid
|
236
|
-
# return getattr(grid, name.upper())
|
237
|
-
|
238
237
|
self.control = Window(
|
239
238
|
FormattedTextControl(
|
240
239
|
border_char("TOP_LEFT"),
|
@@ -764,6 +763,7 @@ class Cell:
|
|
764
763
|
def set_execution_count(self, n: int) -> None:
|
765
764
|
"""Set the execution count of the cell."""
|
766
765
|
self.json["execution_count"] = n
|
766
|
+
self.refresh()
|
767
767
|
|
768
768
|
def add_output(self, output_json: dict[str, Any], own: bool) -> None:
|
769
769
|
"""Add a new output to the cell."""
|