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.
Files changed (131) hide show
  1. euporie/console/_commands.py +143 -0
  2. euporie/console/_settings.py +58 -0
  3. euporie/console/app.py +25 -71
  4. euporie/console/tabs/console.py +58 -62
  5. euporie/core/__init__.py +1 -1
  6. euporie/core/__main__.py +28 -11
  7. euporie/core/_settings.py +109 -0
  8. euporie/core/app/__init__.py +3 -0
  9. euporie/core/app/_commands.py +95 -0
  10. euporie/core/app/_settings.py +457 -0
  11. euporie/core/{app.py → app/app.py} +212 -576
  12. euporie/core/app/base.py +51 -0
  13. euporie/core/{current.py → app/current.py} +13 -4
  14. euporie/core/app/cursor.py +35 -0
  15. euporie/core/app/dummy.py +12 -0
  16. euporie/core/app/launch.py +28 -0
  17. euporie/core/bars/__init__.py +11 -0
  18. euporie/core/bars/command.py +205 -0
  19. euporie/core/bars/menu.py +258 -0
  20. euporie/core/{widgets → bars}/search.py +20 -16
  21. euporie/core/{widgets → bars}/status.py +6 -23
  22. euporie/core/clipboard.py +19 -80
  23. euporie/core/comm/base.py +8 -6
  24. euporie/core/comm/ipywidgets.py +16 -7
  25. euporie/core/comm/registry.py +2 -1
  26. euporie/core/commands.py +10 -20
  27. euporie/core/completion.py +3 -2
  28. euporie/core/config.py +368 -341
  29. euporie/core/convert/__init__.py +0 -30
  30. euporie/core/convert/datum.py +116 -53
  31. euporie/core/convert/formats/__init__.py +31 -0
  32. euporie/core/convert/formats/ansi.py +9 -23
  33. euporie/core/convert/formats/common.py +11 -23
  34. euporie/core/convert/formats/html.py +45 -40
  35. euporie/core/convert/formats/pil.py +1 -1
  36. euporie/core/convert/formats/png.py +3 -5
  37. euporie/core/convert/formats/sixel.py +3 -3
  38. euporie/core/convert/registry.py +4 -6
  39. euporie/core/convert/utils.py +41 -4
  40. euporie/core/diagnostics.py +2 -2
  41. euporie/core/filters.py +98 -40
  42. euporie/core/format.py +2 -3
  43. euporie/core/ft/ansi.py +1 -1
  44. euporie/core/ft/html.py +12 -21
  45. euporie/core/ft/table.py +1 -3
  46. euporie/core/ft/utils.py +4 -1
  47. euporie/core/graphics.py +386 -133
  48. euporie/core/history.py +2 -2
  49. euporie/core/inspection.py +3 -2
  50. euporie/core/io.py +207 -28
  51. euporie/core/kernel/__init__.py +1 -0
  52. euporie/core/{kernel.py → kernel/client.py} +45 -108
  53. euporie/core/kernel/manager.py +114 -0
  54. euporie/core/key_binding/bindings/__init__.py +1 -8
  55. euporie/core/key_binding/bindings/basic.py +47 -7
  56. euporie/core/key_binding/bindings/completion.py +3 -8
  57. euporie/core/key_binding/bindings/micro.py +1 -6
  58. euporie/core/key_binding/bindings/mouse.py +2 -2
  59. euporie/core/key_binding/bindings/terminal.py +193 -0
  60. euporie/core/key_binding/key_processor.py +43 -2
  61. euporie/core/key_binding/registry.py +2 -0
  62. euporie/core/key_binding/utils.py +22 -2
  63. euporie/core/keys.py +7156 -93
  64. euporie/core/layout/cache.py +3 -3
  65. euporie/core/layout/containers.py +48 -4
  66. euporie/core/layout/decor.py +2 -2
  67. euporie/core/layout/mouse.py +1 -1
  68. euporie/core/layout/print.py +2 -1
  69. euporie/core/layout/scroll.py +39 -34
  70. euporie/core/log.py +76 -64
  71. euporie/core/lsp.py +118 -24
  72. euporie/core/margins.py +1 -1
  73. euporie/core/path.py +62 -13
  74. euporie/core/renderer.py +58 -17
  75. euporie/core/style.py +57 -39
  76. euporie/core/suggest.py +103 -85
  77. euporie/core/tabs/__init__.py +32 -0
  78. euporie/core/tabs/_settings.py +113 -0
  79. euporie/core/tabs/base.py +80 -470
  80. euporie/core/tabs/kernel.py +419 -0
  81. euporie/core/tabs/notebook.py +24 -101
  82. euporie/core/utils.py +92 -15
  83. euporie/core/validation.py +1 -1
  84. euporie/core/widgets/_settings.py +188 -0
  85. euporie/core/widgets/cell.py +19 -50
  86. euporie/core/widgets/cell_outputs.py +25 -36
  87. euporie/core/widgets/decor.py +11 -41
  88. euporie/core/widgets/dialog.py +62 -27
  89. euporie/core/widgets/display.py +12 -15
  90. euporie/core/widgets/file_browser.py +2 -23
  91. euporie/core/widgets/forms.py +8 -5
  92. euporie/core/widgets/inputs.py +13 -70
  93. euporie/core/widgets/layout.py +2 -1
  94. euporie/core/widgets/logo.py +49 -0
  95. euporie/core/widgets/menu.py +10 -8
  96. euporie/core/widgets/pager.py +6 -10
  97. euporie/core/widgets/palette.py +6 -6
  98. euporie/hub/app.py +52 -35
  99. euporie/notebook/_commands.py +24 -0
  100. euporie/notebook/_settings.py +107 -0
  101. euporie/notebook/app.py +49 -171
  102. euporie/notebook/filters.py +1 -1
  103. euporie/notebook/tabs/__init__.py +46 -7
  104. euporie/notebook/tabs/_commands.py +714 -0
  105. euporie/notebook/tabs/_settings.py +32 -0
  106. euporie/notebook/tabs/display.py +4 -4
  107. euporie/notebook/tabs/edit.py +11 -44
  108. euporie/notebook/tabs/json.py +5 -5
  109. euporie/notebook/tabs/log.py +1 -18
  110. euporie/notebook/tabs/notebook.py +11 -660
  111. euporie/notebook/widgets/_commands.py +11 -0
  112. euporie/notebook/widgets/_settings.py +19 -0
  113. euporie/notebook/widgets/side_bar.py +14 -34
  114. euporie/preview/_settings.py +104 -0
  115. euporie/preview/app.py +6 -31
  116. euporie/preview/tabs/notebook.py +6 -72
  117. euporie/web/__init__.py +1 -0
  118. euporie/web/tabs/__init__.py +14 -0
  119. euporie/web/tabs/web.py +11 -6
  120. euporie/web/widgets/__init__.py +1 -0
  121. euporie/web/widgets/webview.py +5 -15
  122. {euporie-2.8.4.dist-info → euporie-2.8.6.dist-info}/METADATA +10 -8
  123. euporie-2.8.6.dist-info/RECORD +175 -0
  124. {euporie-2.8.4.dist-info → euporie-2.8.6.dist-info}/WHEEL +1 -1
  125. {euporie-2.8.4.dist-info → euporie-2.8.6.dist-info}/entry_points.txt +2 -2
  126. {euporie-2.8.4.dist-info → euporie-2.8.6.dist-info}/licenses/LICENSE +1 -1
  127. euporie/core/launch.py +0 -64
  128. euporie/core/terminal.py +0 -522
  129. euporie-2.8.4.dist-info/RECORD +0 -147
  130. {euporie-2.8.4.data → euporie-2.8.6.data}/data/share/applications/euporie-console.desktop +0 -0
  131. {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 classes."""
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.comm.registry import open_comm
15
+ from euporie.core.app.current import get_app
25
16
  from euporie.core.commands import add_cmd
26
- from euporie.core.completion import DeduplicateCompleter, KernelCompleter, LspCompleter
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, Sequence
26
+ from typing import Any, Callable
51
27
 
52
- from prompt_toolkit.auto_suggest import AutoSuggest
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.comm.base import Comm
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
- run_in_thread_with_context(self.save, path, _wrapped_cb)
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 notebook."""
144
- raise NotImplementedError
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=tab_has_focus)
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
- )