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/utils.py CHANGED
@@ -2,15 +2,17 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import contextvars
5
+ from collections.abc import Sequence
6
+ from functools import cache
6
7
  from itertools import chain
7
- from threading import Thread
8
- from typing import TYPE_CHECKING, Sequence, TypeVar, overload
8
+ from typing import TYPE_CHECKING, TypeVar, overload
9
9
 
10
10
  from prompt_toolkit.mouse_events import MouseButton, MouseEventType
11
11
 
12
12
  if TYPE_CHECKING:
13
- from typing import Any, Callable, Iterable
13
+ from collections.abc import Iterable
14
+ from types import ModuleType
15
+ from typing import Callable
14
16
 
15
17
  from prompt_toolkit.key_binding.key_bindings import NotImplementedOrNone
16
18
  from prompt_toolkit.layout.mouse_handlers import MouseHandler
@@ -74,16 +76,91 @@ def on_click(func: Callable) -> MouseHandler:
74
76
  return _mouse_handler
75
77
 
76
78
 
77
- def run_in_thread_with_context(
78
- func: Callable, *args: Any, daemon: bool = True, **kwargs: Any
79
- ) -> None:
80
- """Run a function in an thread, but make sure it uses the same contextvars.
79
+ @cache
80
+ def root_module(name: str) -> ModuleType:
81
+ """Find and load the root module of a given module name by traversing up the module hierarchy.
81
82
 
82
- This is required so that the function will see the right application.
83
+ This function walks up the module hierarchy until it finds the topmost parent module
84
+ that has a valid location. It uses Python's importlib machinery to inspect module
85
+ specifications and load modules.
86
+
87
+ Args:
88
+ name: The name of the module to find the root for (e.g., 'package.subpackage.module')
89
+
90
+ Returns:
91
+ The loaded root module object
92
+
93
+ Example:
94
+ >>> root = root_module("django.contrib.admin")
95
+ >>> print(root.__name__)
96
+ 'django'
97
+
98
+ Note:
99
+ The function is cached using lru_cache to improve performance for repeated lookups.
100
+ The function handles both regular packages and frozen modules.
101
+ """
102
+ from importlib.util import find_spec, module_from_spec
103
+
104
+ if spec := find_spec(name):
105
+ while True:
106
+ if spec.name == spec.parent:
107
+ try:
108
+ parent = find_spec("..", spec.parent)
109
+ except ImportError:
110
+ break
111
+ elif spec.parent is not None:
112
+ parent = find_spec(spec.parent)
113
+ if (spec and not spec.parent) or (parent and not parent.has_location):
114
+ break
115
+ if parent:
116
+ spec = parent
117
+ if spec.loader:
118
+ module = module_from_spec(spec)
119
+ spec.loader.exec_module(module)
120
+ return module
121
+ raise ModuleNotFoundError(name=name)
122
+
123
+
124
+ @cache
125
+ def import_submodules(root: ModuleType, names: tuple[str]) -> list[ModuleType]:
126
+ """Import all submodules with a specific name within a root module's package hierarchy.
127
+
128
+ This function walks through all packages under the given root module and imports
129
+ any submodules that match the specified name. It handles various module types
130
+ including regular packages, single file modules, and frozen modules.
131
+
132
+ Args:
133
+ root: The root module object to search within
134
+ names: The specific submodule name to search for
135
+
136
+ Returns:
137
+ A list of imported module objects matching the specified name
138
+
139
+ Example:
140
+ >>> root = import_module("django")
141
+ >>> admin_modules = import_submodules(root, "admin")
142
+ >>> print([m.__name__ for m in admin_modules])
143
+ ['django.contrib.admin', 'django.contrib.gis.admin']
144
+
145
+ Note:
146
+ - The function is cached using lru_cache to improve performance for repeated imports
147
+ - For packages, it searches through __path__
148
+ - For single file modules, it uses __file__
149
+ - For frozen modules, it uses the module specification's origin
83
150
  """
84
- Thread(
85
- target=contextvars.copy_context().run,
86
- args=(func, *args),
87
- kwargs=kwargs,
88
- daemon=daemon,
89
- ).start()
151
+ from importlib import import_module
152
+ from pkgutil import walk_packages
153
+
154
+ if hasattr(root, "__path__"):
155
+ path = root.__path__
156
+ elif hasattr(root, "__file__"):
157
+ path = [root.__file__] if root.__file__ else []
158
+ else:
159
+ # For frozen modules, we need to create a special path
160
+ spec = root.__spec__
161
+ path = [spec.origin] if spec and spec.origin else []
162
+ return [
163
+ import_module(module_name)
164
+ for _loader, module_name, _is_pkg in walk_packages(path, f"{root.__name__}.")
165
+ if module_name.rpartition(".")[2] in names
166
+ ]
@@ -9,7 +9,7 @@ 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 import Kernel
12
+ from euporie.core.kernel.client import Kernel
13
13
 
14
14
 
15
15
  class KernelValidator(Validator):
@@ -0,0 +1,188 @@
1
+ """Defines widget settings."""
2
+
3
+ from prompt_toolkit.filters import buffer_has_focus
4
+
5
+ from euporie.core.app.current import get_app
6
+ from euporie.core.config import add_setting
7
+
8
+ # euporie.core.widgets.cell_outputs:CellOutputArea
9
+
10
+ add_setting(
11
+ name="wrap_cell_outputs",
12
+ group="euporie.core.widgets.cell_outputs",
13
+ title="wrap cell outputs",
14
+ flags=["--wrap-cell-outputs"],
15
+ type_=bool,
16
+ help_="Wrap cell output text.",
17
+ default=False,
18
+ schema={"type": "boolean"},
19
+ description="""
20
+ Whether text-based cell outputs should be wrapped.
21
+ """,
22
+ cmd_filter=~buffer_has_focus,
23
+ )
24
+
25
+ # euporie,core.widgets.file_browser:FileBrowser
26
+
27
+ add_setting(
28
+ name="show_file_icons",
29
+ group="euporie.core.widgets.file_browser",
30
+ flags=["--show-file-icons"],
31
+ type_=bool,
32
+ title="File icons",
33
+ help_="Show file icons in the file manager",
34
+ default=False,
35
+ schema={
36
+ "type": "boolean",
37
+ },
38
+ description="""
39
+ Whether file icons should be shown in the file manager.
40
+
41
+ These icons exist in the unicode private use area, and may require custom
42
+ fonts such as ``awesome-terminal-fonts`` or ``nerdfonts`` to be installed.
43
+ """,
44
+ )
45
+
46
+ # euporie.core.widgets.inputs:KernelInput
47
+
48
+ add_setting(
49
+ name="line_numbers",
50
+ group="euporie.core.widgets.inputs",
51
+ flags=["--line-numbers"],
52
+ type_=bool,
53
+ help_="Show or hide line numbers",
54
+ default=True,
55
+ description="""
56
+ Whether line numbers are shown by default.
57
+ """,
58
+ hooks=[lambda x: get_app().refresh()],
59
+ )
60
+
61
+ add_setting(
62
+ name="autoformat",
63
+ group="euporie.core.widgets.inputs",
64
+ flags=["--autoformat"],
65
+ type_=bool,
66
+ help_="Automatically re-format code cells when run",
67
+ default=False,
68
+ description="""
69
+ Whether to automatically reformat code cells before they are run.
70
+ """,
71
+ )
72
+
73
+ add_setting(
74
+ name="autocomplete",
75
+ group="euporie.core.widgets.inputs",
76
+ flags=["--autocomplete"],
77
+ type_=bool,
78
+ help_="Provide completions suggestions automatically",
79
+ default=False,
80
+ description="""
81
+ Whether to automatically suggestion completions while typing in code cells.
82
+ """,
83
+ )
84
+
85
+ add_setting(
86
+ name="autosuggest",
87
+ group="euporie.core.widgets.inputs",
88
+ flags=["--autosuggest"],
89
+ type_=bool,
90
+ help_="Provide line completion suggestions",
91
+ default=True,
92
+ description="""
93
+ Whether to automatically suggestion line content while typing in code cells.
94
+ """,
95
+ )
96
+
97
+ add_setting(
98
+ name="autoinspect",
99
+ group="euporie.core.widgets.inputs",
100
+ flags=["--autoinspect"],
101
+ type_=bool,
102
+ help_="Display contextual help automatically",
103
+ default=False,
104
+ description="""
105
+ Whether to automatically display contextual help when navigating through code cells.
106
+ """,
107
+ )
108
+
109
+
110
+ # euporie.core.bars.status:StatusBar
111
+
112
+ add_setting(
113
+ name="show_status_bar",
114
+ group="euporie.core.bars.status",
115
+ flags=["--show-status-bar"],
116
+ type_=bool,
117
+ title="status bar",
118
+ help_="Show the status bar",
119
+ default=True,
120
+ schema={
121
+ "type": "boolean",
122
+ },
123
+ description="""
124
+ Whether the status bar should be shown at the bottom of the screen.
125
+ """,
126
+ )
127
+
128
+ # euporie.core.widgets.decor:
129
+
130
+ add_setting(
131
+ name="show_shadows",
132
+ group="euporie.core.widgets.decor",
133
+ flags=["--show-shadows"],
134
+ type_=bool,
135
+ help_="Show or hide shadows under menus and dialogs",
136
+ default=True,
137
+ description="""
138
+ Sets whether shadows are shown under dialogs and popup-menus.
139
+ """,
140
+ )
141
+
142
+ # euporie.core.widgets.cell
143
+
144
+ add_setting(
145
+ name="show_cell_borders",
146
+ group="euporie.core.widgets.cell",
147
+ title="cell borders",
148
+ flags=["--show-cell-borders"],
149
+ type_=bool,
150
+ help_="Show or hide cell borders.",
151
+ default=False,
152
+ schema={
153
+ "type": "boolean",
154
+ },
155
+ description="""
156
+ Whether cell borders should be drawn for unselected cells.
157
+ """,
158
+ )
159
+
160
+ add_setting(
161
+ name="external_editor",
162
+ group="euporie.core.widgets.cell",
163
+ flags=["--external-editor"],
164
+ type_=str,
165
+ help_="Set the external editor to use.",
166
+ default=None,
167
+ description="""
168
+ A command to run when editing cells externally. The following strings in
169
+ the command will be replaced with values which locate the cell being
170
+ edited:
171
+
172
+ * ``{top}``
173
+ * ``{left}``
174
+ * ``{bottom}``
175
+ * ``{right}``
176
+ * ``{width}``
177
+ * ``{height}``
178
+
179
+ This is useful if you run euporie inside a tmux session, and wish to launch
180
+ your editor in a pop-up pane. This can be achieved by setting this parameter
181
+ to something like the following:
182
+
183
+ .. code-block::
184
+
185
+ "tmux display-popup -x {left} -y {bottom} -w {width} -h {height} -B -E micro"
186
+
187
+ """,
188
+ )
@@ -27,10 +27,9 @@ from prompt_toolkit.layout.controls import FormattedTextControl
27
27
  from prompt_toolkit.layout.dimension import Dimension
28
28
  from prompt_toolkit.utils import Event
29
29
 
30
+ from euporie.core.app.current import get_app
30
31
  from euporie.core.border import NoLine, ThickLine, ThinLine
31
32
  from euporie.core.completion import DeduplicateCompleter, LspCompleter
32
- from euporie.core.config import add_setting
33
- from euporie.core.current import get_app
34
33
  from euporie.core.diagnostics import Report
35
34
  from euporie.core.filters import multiple_cells_selected
36
35
  from euporie.core.format import LspFormatter
@@ -173,7 +172,7 @@ class Cell:
173
172
  # Now we generate the main container used to represent a kernel_tab cell
174
173
 
175
174
  source_hidden = Condition(
176
- lambda: weak_self.json["metadata"]
175
+ lambda: weak_self.json.get("metadata", {})
177
176
  .get("jupyter", {})
178
177
  .get("source_hidden", False)
179
178
  )
@@ -219,6 +218,23 @@ class Cell:
219
218
 
220
219
  return _inner
221
220
 
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
+
222
238
  self.control = Window(
223
239
  FormattedTextControl(
224
240
  border_char("TOP_LEFT"),
@@ -565,7 +581,6 @@ class Cell:
565
581
  """Determine the current cell type."""
566
582
  return self.json.get("cell_type", "code")
567
583
 
568
- @property
569
584
  def suffix(self) -> str:
570
585
  """Return the file suffix matching the current cell type."""
571
586
  cell_type = self.cell_type
@@ -910,49 +925,3 @@ class Cell:
910
925
  def close(self) -> None:
911
926
  """Signal that the cell is no longer present in the notebook."""
912
927
  self.on_close()
913
-
914
- # ################################### Settings ####################################
915
-
916
- add_setting(
917
- name="show_cell_borders",
918
- title="cell borders",
919
- flags=["--show-cell-borders"],
920
- type_=bool,
921
- help_="Show or hide cell borders.",
922
- default=False,
923
- schema={
924
- "type": "boolean",
925
- },
926
- description="""
927
- Whether cell borders should be drawn for unselected cells.
928
- """,
929
- )
930
-
931
- add_setting(
932
- name="external_editor",
933
- flags=["--external-editor"],
934
- type_=str,
935
- help_="Set the external editor to use.",
936
- default=None,
937
- description="""
938
- A command to run when editing cells externally. The following strings in
939
- the command will be replaced with values which locate the cell being
940
- edited:
941
-
942
- * ``{top}``
943
- * ``{left}``
944
- * ``{bottom}``
945
- * ``{right}``
946
- * ``{width}``
947
- * ``{height}``
948
-
949
- This is useful if you run euporie inside a tmux session, and wish to launch
950
- your editor in a pop-up pane. This can be achieved by setting this parameter
951
- to something like the following:
952
-
953
- .. code-block::
954
-
955
- "tmux display-popup -x {left} -y {bottom} -w {width} -h {height} -B -E micro"
956
-
957
- """,
958
- )
@@ -4,22 +4,18 @@ from __future__ import annotations
4
4
 
5
5
  import logging
6
6
  from abc import ABCMeta, abstractmethod
7
+ from functools import cache
7
8
  from pathlib import PurePath
8
9
  from typing import TYPE_CHECKING
9
10
 
10
11
  from prompt_toolkit.cache import SimpleCache
11
- from prompt_toolkit.filters import buffer_has_focus
12
12
  from prompt_toolkit.layout.containers import (
13
13
  DynamicContainer,
14
14
  to_container,
15
15
  )
16
16
 
17
- from euporie.core.config import add_setting
18
- from euporie.core.convert.datum import Datum
19
- from euporie.core.convert.formats import BASE64_FORMATS
20
- from euporie.core.convert.mime import MIME_FORMATS
17
+ from euporie.core.app.current import get_app
21
18
  from euporie.core.convert.registry import find_route
22
- from euporie.core.current import get_app
23
19
  from euporie.core.layout.containers import HSplit
24
20
  from euporie.core.widgets.display import Display
25
21
  from euporie.core.widgets.layout import Box
@@ -30,7 +26,7 @@ if TYPE_CHECKING:
30
26
 
31
27
  from prompt_toolkit.layout.containers import AnyContainer
32
28
 
33
- from euporie.core.comm.base import KernelTab
29
+ from euporie.core.tabs.kernel import KernelTab
34
30
 
35
31
  KTParent = TypeVar("KTParent", bound=KernelTab)
36
32
 
@@ -98,6 +94,10 @@ class CellOutputDataElement(CellOutputElement):
98
94
  metadata: Any metadata relating to the data
99
95
  parent: The cell the output-element is attached to
100
96
  """
97
+ from euporie.core.convert.datum import Datum
98
+ from euporie.core.convert.formats import BASE64_FORMATS
99
+ from euporie.core.convert.mime import MIME_FORMATS
100
+
101
101
  self.parent = parent
102
102
 
103
103
  # Get foreground and background colors
@@ -129,16 +129,14 @@ class CellOutputDataElement(CellOutputElement):
129
129
  self._datum,
130
130
  focusable=False,
131
131
  focus_on_click=False,
132
- wrap_lines=config.filter("wrap_cell_outputs"),
132
+ wrap_lines=config.filters.wrap_cell_outputs,
133
133
  always_hide_cursor=True,
134
- style=f"class:mime-{mime.replace('/','-')}",
134
+ style=f"class:mime-{mime.replace('/', '-')}",
135
135
  scrollbar=False,
136
136
  )
137
137
 
138
138
  # Ensure container gets invalidated if `wrap_cell_output` changes
139
- self.container.control.invalidate_events.append(
140
- config.settings["wrap_cell_outputs"].event
141
- )
139
+ self.container.control.invalidate_events.append(config.events.wrap_cell_outputs)
142
140
 
143
141
  @property
144
142
  def data(self) -> Any:
@@ -148,6 +146,8 @@ class CellOutputDataElement(CellOutputElement):
148
146
  @data.setter
149
147
  def data(self, value: Any) -> None:
150
148
  """Set the cell output's data."""
149
+ from euporie.core.convert.datum import Datum
150
+
151
151
  self._datum = Datum(
152
152
  value,
153
153
  self._datum.format,
@@ -260,12 +260,12 @@ MIME_ORDER = [
260
260
  ]
261
261
 
262
262
 
263
- def _calculate_mime_rank(mime_data: tuple[str, Any]) -> int:
263
+ @cache
264
+ def _calculate_mime_rank(mime: str, have_escapes: bool) -> int:
264
265
  """Score the richness of mime output types."""
265
- mime, data = mime_data
266
266
  for i, ranked_mime in enumerate(MIME_ORDER):
267
267
  # Uprank plain text with escape sequences
268
- if mime == "text/plain" and "\x1b[" in data:
268
+ if mime == "text/plain" and have_escapes:
269
269
  i -= 7
270
270
  if PurePath(mime).match(ranked_mime):
271
271
  return i
@@ -273,6 +273,12 @@ def _calculate_mime_rank(mime_data: tuple[str, Any]) -> int:
273
273
  return 999
274
274
 
275
275
 
276
+ def _mime_ranker(mime_data: tuple[str, Any]) -> int:
277
+ """Score the richness of mime output types."""
278
+ mime, data = mime_data
279
+ return _calculate_mime_rank(mime, isinstance(data, str) and "\x1b[" in data)
280
+
281
+
276
282
  class CellOutput:
277
283
  """Represent a single cell output.
278
284
 
@@ -319,7 +325,7 @@ class CellOutput:
319
325
  data = {}
320
326
  output_type = self.json.get("output_type", "unknown")
321
327
  if output_type == "stream":
322
- data = {f'stream/{self.json.get("name")}': self.json.get("text", "")}
328
+ data = {f"stream/{self.json.get('name')}": self.json.get("text", "")}
323
329
  elif output_type == "error":
324
330
  ename = self.json.get("ename", "")
325
331
  evalue = self.json.get("evalue", "")
@@ -327,7 +333,7 @@ class CellOutput:
327
333
  data = {"text/x-python-traceback": f"{ename}: {evalue}\n{traceback}"}
328
334
  else:
329
335
  data = self.json.get("data", {"text/plain": ""})
330
- return dict(sorted(data.items(), key=_calculate_mime_rank))
336
+ return dict(sorted(data.items(), key=_mime_ranker))
331
337
 
332
338
  def update(self) -> None:
333
339
  """Update the output by updating all child containers."""
@@ -372,9 +378,8 @@ class CellOutput:
372
378
  if mime not in self._elements:
373
379
  element = self.make_element(mime)
374
380
  self._elements[mime] = element
375
- else:
376
- element = self._elements[mime]
377
- return element
381
+ return element
382
+ return self._elements[mime]
378
383
 
379
384
  @property
380
385
  def element(self) -> CellOutputElement:
@@ -511,19 +516,3 @@ class CellOutputArea:
511
516
  ):
512
517
  outputs.append(to_plain_text(line))
513
518
  return "\n".join(outputs)
514
-
515
- # ################################### Settings ####################################
516
-
517
- add_setting(
518
- name="wrap_cell_outputs",
519
- title="wrap cell outputs",
520
- flags=["--wrap-cell-outputs"],
521
- type_=bool,
522
- help_="Wrap cell output text.",
523
- default=False,
524
- schema={"type": "boolean"},
525
- description="""
526
- Whether text-based cell outputs should be wrapped.
527
- """,
528
- cmd_filter=~buffer_has_focus,
529
- )
@@ -9,15 +9,12 @@ from prompt_toolkit.filters import to_filter
9
9
  from prompt_toolkit.layout.containers import (
10
10
  ConditionalContainer,
11
11
  DynamicContainer,
12
- Float,
13
- FloatContainer,
14
12
  )
15
13
 
14
+ from euporie.core.app.current import get_app
16
15
  from euporie.core.border import ThinLine
17
- from euporie.core.config import add_setting
18
- from euporie.core.current import get_app
19
16
  from euporie.core.data_structures import DiBool
20
- from euporie.core.layout.containers import HSplit, VSplit, Window
17
+ from euporie.core.layout.containers import DummyContainer, HSplit, VSplit, Window
21
18
  from euporie.core.layout.decor import DropShadow
22
19
 
23
20
  if TYPE_CHECKING:
@@ -183,8 +180,7 @@ class Border:
183
180
  class Shadow:
184
181
  """Draw a shadow underneath/behind this container.
185
182
 
186
- This is a globally configurable version of the
187
- :py:class:`prompt_toolkit.widows.base.Shadow` class.
183
+ The container must be in a float.
188
184
  """
189
185
 
190
186
  def __init__(self, body: AnyContainer) -> None:
@@ -193,27 +189,14 @@ class Shadow:
193
189
  Args:
194
190
  body: Another container object.
195
191
  """
196
- filter_ = get_app().config.filter("show_shadows")
197
- shadow = FloatContainer(
198
- content=body,
199
- floats=[
200
- Float(
201
- bottom=-1,
202
- height=1,
203
- left=1,
204
- right=0,
205
- transparent=True,
206
- content=DropShadow(),
207
- ),
208
- Float(
209
- bottom=-1,
210
- top=1,
211
- width=1,
212
- right=-1,
213
- transparent=True,
214
- content=DropShadow(),
215
- ),
216
- ],
192
+ filter_ = get_app().config.filters.show_shadows
193
+
194
+ spacer = DummyContainer(width=1, height=1)
195
+ shadow = VSplit(
196
+ [
197
+ HSplit([body, VSplit([spacer, DropShadow()])]),
198
+ HSplit([spacer, DropShadow()]),
199
+ ]
217
200
  )
218
201
 
219
202
  def get_contents() -> AnyContainer:
@@ -227,16 +210,3 @@ class Shadow:
227
210
  def __pt_container__(self) -> AnyContainer:
228
211
  """Return the container's content."""
229
212
  return self.container
230
-
231
- # ################################### Settings ####################################
232
-
233
- add_setting(
234
- name="show_shadows",
235
- flags=["--show-shadows"],
236
- type_=bool,
237
- help_="Show or hide shadows under menus and dialogs",
238
- default=True,
239
- description="""
240
- Sets whether shadows are shown under dialogs and popup-menus.
241
- """,
242
- )