euporie 2.8.0__tar.gz → 2.8.2__tar.gz

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 (144) hide show
  1. {euporie-2.8.0 → euporie-2.8.2}/PKG-INFO +3 -7
  2. {euporie-2.8.0 → euporie-2.8.2}/euporie/console/tabs/console.py +2 -1
  3. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/__init__.py +1 -9
  4. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/__main__.py +9 -0
  5. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/convert/datum.py +14 -9
  6. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/convert/formats/ansi.py +37 -7
  7. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/convert/utils.py +9 -19
  8. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/filters.py +5 -52
  9. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/format.py +13 -2
  10. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/ft/html.py +22 -11
  11. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/graphics.py +4 -5
  12. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/key_binding/bindings/micro.py +4 -1
  13. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/keys.py +1 -0
  14. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/launch.py +7 -2
  15. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/layout/scroll.py +7 -6
  16. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/widgets/cell.py +8 -0
  17. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/widgets/cell_outputs.py +1 -1
  18. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/widgets/display.py +18 -12
  19. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/widgets/inputs.py +3 -2
  20. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/widgets/menu.py +4 -4
  21. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/widgets/pager.py +3 -1
  22. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/widgets/search.py +135 -42
  23. {euporie-2.8.0 → euporie-2.8.2}/euporie/notebook/tabs/edit.py +4 -1
  24. {euporie-2.8.0 → euporie-2.8.2}/euporie/notebook/tabs/notebook.py +6 -12
  25. {euporie-2.8.0 → euporie-2.8.2}/euporie/web/widgets/webview.py +2 -1
  26. {euporie-2.8.0 → euporie-2.8.2}/pyproject.toml +6 -9
  27. {euporie-2.8.0 → euporie-2.8.2}/.gitignore +0 -0
  28. {euporie-2.8.0 → euporie-2.8.2}/LICENSE +0 -0
  29. {euporie-2.8.0 → euporie-2.8.2}/README.rst +0 -0
  30. {euporie-2.8.0 → euporie-2.8.2}/euporie/console/__init__.py +0 -0
  31. {euporie-2.8.0 → euporie-2.8.2}/euporie/console/__main__.py +0 -0
  32. {euporie-2.8.0 → euporie-2.8.2}/euporie/console/app.py +0 -0
  33. {euporie-2.8.0 → euporie-2.8.2}/euporie/console/py.typed +0 -0
  34. {euporie-2.8.0 → euporie-2.8.2}/euporie/console/tabs/__init__.py +0 -0
  35. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/app.py +0 -0
  36. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/border.py +0 -0
  37. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/clipboard.py +0 -0
  38. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/comm/__init__.py +0 -0
  39. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/comm/base.py +0 -0
  40. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/comm/ipywidgets.py +0 -0
  41. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/comm/registry.py +0 -0
  42. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/commands.py +0 -0
  43. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/completion.py +0 -0
  44. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/config.py +0 -0
  45. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/convert/__init__.py +0 -0
  46. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/convert/formats/__init__.py +0 -0
  47. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/convert/formats/base64.py +0 -0
  48. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/convert/formats/common.py +0 -0
  49. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/convert/formats/ft.py +0 -0
  50. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/convert/formats/html.py +0 -0
  51. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/convert/formats/jpeg.py +0 -0
  52. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/convert/formats/markdown.py +0 -0
  53. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/convert/formats/pdf.py +0 -0
  54. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/convert/formats/pil.py +0 -0
  55. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/convert/formats/png.py +0 -0
  56. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/convert/formats/rich.py +0 -0
  57. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/convert/formats/sixel.py +0 -0
  58. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/convert/formats/svg.py +0 -0
  59. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/convert/mime.py +0 -0
  60. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/convert/registry.py +0 -0
  61. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/current.py +0 -0
  62. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/data_structures.py +0 -0
  63. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/diagnostics.py +0 -0
  64. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/ft/__init__.py +0 -0
  65. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/ft/ansi.py +0 -0
  66. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/ft/table.py +0 -0
  67. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/ft/utils.py +0 -0
  68. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/history.py +0 -0
  69. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/inspection.py +0 -0
  70. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/io.py +0 -0
  71. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/kernel.py +0 -0
  72. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/key_binding/__init__.py +0 -0
  73. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/key_binding/bindings/__init__.py +0 -0
  74. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/key_binding/bindings/basic.py +0 -0
  75. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/key_binding/bindings/completion.py +0 -0
  76. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/key_binding/bindings/mouse.py +0 -0
  77. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/key_binding/bindings/page_navigation.py +0 -0
  78. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/key_binding/key_processor.py +0 -0
  79. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/key_binding/micro_state.py +0 -0
  80. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/key_binding/registry.py +0 -0
  81. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/key_binding/utils.py +0 -0
  82. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/key_binding/vi_state.py +0 -0
  83. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/layout/__init__.py +0 -0
  84. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/layout/cache.py +0 -0
  85. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/layout/containers.py +0 -0
  86. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/layout/controls.py +0 -0
  87. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/layout/decor.py +0 -0
  88. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/layout/mouse.py +0 -0
  89. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/layout/print.py +0 -0
  90. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/layout/screen.py +0 -0
  91. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/lexers.py +0 -0
  92. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/log.py +0 -0
  93. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/lsp.py +0 -0
  94. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/margins.py +0 -0
  95. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/path.py +0 -0
  96. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/processors.py +0 -0
  97. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/py.typed +0 -0
  98. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/pygments.py +0 -0
  99. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/reference.py +0 -0
  100. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/renderer.py +0 -0
  101. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/style.py +0 -0
  102. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/suggest.py +0 -0
  103. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/tabs/__init__.py +0 -0
  104. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/tabs/base.py +0 -0
  105. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/tabs/notebook.py +0 -0
  106. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/terminal.py +0 -0
  107. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/utils.py +0 -0
  108. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/validation.py +0 -0
  109. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/widgets/__init__.py +0 -0
  110. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/widgets/decor.py +0 -0
  111. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/widgets/dialog.py +0 -0
  112. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/widgets/file_browser.py +0 -0
  113. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/widgets/formatted_text_area.py +0 -0
  114. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/widgets/forms.py +0 -0
  115. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/widgets/layout.py +0 -0
  116. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/widgets/palette.py +0 -0
  117. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/widgets/status.py +0 -0
  118. {euporie-2.8.0 → euporie-2.8.2}/euporie/core/widgets/tree.py +0 -0
  119. {euporie-2.8.0 → euporie-2.8.2}/euporie/data/desktop/euporie-console.desktop +0 -0
  120. {euporie-2.8.0 → euporie-2.8.2}/euporie/data/desktop/euporie-notebook.desktop +0 -0
  121. {euporie-2.8.0 → euporie-2.8.2}/euporie/hub/__init__.py +0 -0
  122. {euporie-2.8.0 → euporie-2.8.2}/euporie/hub/__main__.py +0 -0
  123. {euporie-2.8.0 → euporie-2.8.2}/euporie/hub/app.py +0 -0
  124. {euporie-2.8.0 → euporie-2.8.2}/euporie/hub/py.typed +0 -0
  125. {euporie-2.8.0 → euporie-2.8.2}/euporie/notebook/__init__.py +0 -0
  126. {euporie-2.8.0 → euporie-2.8.2}/euporie/notebook/__main__.py +0 -0
  127. {euporie-2.8.0 → euporie-2.8.2}/euporie/notebook/app.py +0 -0
  128. {euporie-2.8.0 → euporie-2.8.2}/euporie/notebook/current.py +0 -0
  129. {euporie-2.8.0 → euporie-2.8.2}/euporie/notebook/enums.py +0 -0
  130. {euporie-2.8.0 → euporie-2.8.2}/euporie/notebook/filters.py +0 -0
  131. {euporie-2.8.0 → euporie-2.8.2}/euporie/notebook/py.typed +0 -0
  132. {euporie-2.8.0 → euporie-2.8.2}/euporie/notebook/tabs/__init__.py +0 -0
  133. {euporie-2.8.0 → euporie-2.8.2}/euporie/notebook/tabs/display.py +0 -0
  134. {euporie-2.8.0 → euporie-2.8.2}/euporie/notebook/tabs/json.py +0 -0
  135. {euporie-2.8.0 → euporie-2.8.2}/euporie/notebook/tabs/log.py +0 -0
  136. {euporie-2.8.0 → euporie-2.8.2}/euporie/notebook/widgets/__init__.py +0 -0
  137. {euporie-2.8.0 → euporie-2.8.2}/euporie/notebook/widgets/side_bar.py +0 -0
  138. {euporie-2.8.0 → euporie-2.8.2}/euporie/preview/__init__.py +0 -0
  139. {euporie-2.8.0 → euporie-2.8.2}/euporie/preview/__main__.py +0 -0
  140. {euporie-2.8.0 → euporie-2.8.2}/euporie/preview/app.py +0 -0
  141. {euporie-2.8.0 → euporie-2.8.2}/euporie/preview/py.typed +0 -0
  142. {euporie-2.8.0 → euporie-2.8.2}/euporie/preview/tabs/__init__.py +0 -0
  143. {euporie-2.8.0 → euporie-2.8.2}/euporie/preview/tabs/notebook.py +0 -0
  144. {euporie-2.8.0 → euporie-2.8.2}/euporie/web/tabs/web.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: euporie
3
- Version: 2.8.0
3
+ Version: 2.8.2
4
4
  Summary: Euporie is a suite of terminal applications for interacting with Jupyter kernels
5
5
  Project-URL: Documentation, https://euporie.readthedocs.io/en/latest
6
6
  Project-URL: Issues, https://github.com/joouha/euporie/issues
@@ -32,7 +32,7 @@ Requires-Dist: linkify-it-py~=1.0
32
32
  Requires-Dist: markdown-it-py~=2.1.0
33
33
  Requires-Dist: mdit-py-plugins~=0.3.0
34
34
  Requires-Dist: nbformat~=5.0
35
- Requires-Dist: pillow~=9.0
35
+ Requires-Dist: pillow>=9.0
36
36
  Requires-Dist: platformdirs~=3.5
37
37
  Requires-Dist: prompt-toolkit~=3.0.36
38
38
  Requires-Dist: pygments~=2.11
@@ -41,10 +41,6 @@ Requires-Dist: sixelcrop~=0.1.6
41
41
  Requires-Dist: timg~=1.1.6
42
42
  Requires-Dist: typing-extensions~=4.5
43
43
  Requires-Dist: universal-pathlib~=0.2.1
44
- Provides-Extra: format
45
- Requires-Dist: black>=19.3.b0; extra == 'format'
46
- Requires-Dist: isort~=5.10.1; extra == 'format'
47
- Requires-Dist: ruff~=0.1.0; extra == 'format'
48
44
  Provides-Extra: hub
49
45
  Requires-Dist: asyncssh~=2.10.1; extra == 'hub'
50
46
  Description-Content-Type: text/x-rst
@@ -503,7 +503,8 @@ class Console(KernelTab):
503
503
  *input_row,
504
504
  ],
505
505
  key_bindings=load_registered_bindings(
506
- "euporie.console.tabs.console.Console"
506
+ "euporie.console.tabs.console.Console",
507
+ config=self.app.config,
507
508
  ),
508
509
  )
509
510
 
@@ -1,18 +1,10 @@
1
1
  """This package defines the euporie application and its components."""
2
2
 
3
3
  __app_name__ = "euporie"
4
- __version__ = "2.8.0"
4
+ __version__ = "2.8.2"
5
5
  __logo__ = "⚈"
6
6
  __strapline__ = "Jupyter in the terminal"
7
7
  __author__ = "Josiah Outram Halstead"
8
8
  __email__ = "josiah@halstead.email"
9
9
  __copyright__ = f"© 2024, {__author__}"
10
10
  __license__ = "MIT"
11
-
12
-
13
- # Register extensions to external packages
14
- from euporie.core import path # noqa F401
15
- from euporie.core import pygments # noqa F401
16
-
17
- # Monkey-patch prompt_toolkit
18
- from euporie.core.layout import containers # noqa: F401
@@ -5,6 +5,15 @@ def main(name: "str" = "launch") -> "None":
5
5
  """Load and launches the application."""
6
6
  from importlib.metadata import entry_points
7
7
 
8
+ # Register extensions to external packages
9
+ from euporie.core import (
10
+ path, # noqa F401
11
+ pygments, # noqa F401
12
+ )
13
+
14
+ # Monkey-patch prompt_toolkit
15
+ from euporie.core.layout import containers # noqa: F401
16
+
8
17
  eps = entry_points() # group="euporie.apps")
9
18
  if isinstance(eps, dict):
10
19
  points = eps.get("euporie.apps")
@@ -230,9 +230,7 @@ class Datum(Generic[T], metaclass=_MetaDatum):
230
230
  return self._conversions[key]
231
231
 
232
232
  routes = _CONVERTOR_ROUTE_CACHE[(self.format, to)]
233
- # log.debug(
234
- # "Converting from '%s' to '%s' using route: %s", self, to, routes
235
- # )
233
+ # log.debug("Converting from '%s' to '%s' using route: %s", self, to, routes)
236
234
  output: T | None = None
237
235
  if routes:
238
236
  datum = self
@@ -244,18 +242,25 @@ class Datum(Generic[T], metaclass=_MetaDatum):
244
242
  output = self._conversions[key]
245
243
  else:
246
244
  # Find converter with lowest weight
247
- func = sorted(
245
+ for converter in sorted(
248
246
  [
249
247
  conv
250
248
  for conv in converters[stage_b][stage_a]
251
249
  if _FILTER_CACHE.get((conv,), conv.filter_)
252
250
  ],
253
251
  key=lambda x: x.weight,
254
- )[0].func
255
- try:
256
- output = await func(datum, cols, rows, fg, bg, extend)
257
- self._conversions[key] = output
258
- except Exception:
252
+ ):
253
+ try:
254
+ output = await converter.func(
255
+ datum, cols, rows, fg, bg, extend
256
+ )
257
+ self._conversions[key] = output
258
+ except Exception:
259
+ log.debug("Conversion step %s failed", converter)
260
+ continue
261
+ else:
262
+ break
263
+ else:
259
264
  log.exception("An error occurred during format conversion")
260
265
  output = None
261
266
  if output is None:
@@ -169,9 +169,10 @@ async def html_to_ansi_py_htmlparser(
169
169
  @register(
170
170
  from_="latex",
171
171
  to="ansi",
172
- filter_=have_modules("flatlatex.latexfuntypes"),
172
+ filter_=command_exists("utftex"),
173
+ weight=0,
173
174
  )
174
- async def latex_to_ansi_py_flatlatex(
175
+ async def latex_to_ansi_utftex(
175
176
  datum: Datum,
176
177
  cols: int | None = None,
177
178
  rows: int | None = None,
@@ -179,16 +180,15 @@ async def latex_to_ansi_py_flatlatex(
179
180
  bg: str | None = None,
180
181
  extend: bool = True,
181
182
  ) -> str:
182
- """Convert LaTeX to ANSI using :py:mod:`flatlatex`."""
183
- import flatlatex
184
-
185
- return flatlatex.converter().convert(datum.data.strip().strip("$").strip())
183
+ """Render LaTeX maths as unicode."""
184
+ return (await call_subproc(datum.data, ["utftex"])).decode()
186
185
 
187
186
 
188
187
  @register(
189
188
  from_="latex",
190
189
  to="ansi",
191
190
  filter_=have_modules("pylatexenc"),
191
+ weight=0,
192
192
  )
193
193
  async def latex_to_ansi_py_pylatexenc(
194
194
  datum: Datum,
@@ -204,6 +204,36 @@ async def latex_to_ansi_py_pylatexenc(
204
204
  return LatexNodes2Text().latex_to_text(datum.data.strip().strip("$").strip())
205
205
 
206
206
 
207
+ @register(
208
+ from_="latex",
209
+ to="ansi",
210
+ filter_=have_modules("flatlatex.latexfuntypes"),
211
+ weight=0,
212
+ )
213
+ async def latex_to_ansi_py_flatlatex(
214
+ datum: Datum,
215
+ cols: int | None = None,
216
+ rows: int | None = None,
217
+ fg: str | None = None,
218
+ bg: str | None = None,
219
+ extend: bool = True,
220
+ ) -> str:
221
+ """Convert LaTeX to ANSI using :py:mod:`flatlatex`."""
222
+ import flatlatex
223
+ from flatlatex.latexfuntypes import latexfun
224
+
225
+ converter = flatlatex.converter()
226
+ for style in (
227
+ r"\textstyle",
228
+ r"\displaystyle",
229
+ r"\scriptstyle",
230
+ r"\scriptscriptstyle",
231
+ ):
232
+ converter._converter__cmds[style] = latexfun(lambda x: "", 0)
233
+
234
+ return converter.convert(datum.data.strip().strip("$").strip())
235
+
236
+
207
237
  @register(
208
238
  from_="latex",
209
239
  to="ansi",
@@ -261,7 +291,7 @@ async def pil_to_ansi_py_timg(
261
291
  assert rows is not None
262
292
  assert cols is not None
263
293
 
264
- # `timg` assumes a 2x1 terminal cell aspect ratio, so we correct for while
294
+ # `timg` assumes a 2x1 terminal cell aspect ratio, so we correct for this while
265
295
  # resizing the image
266
296
  data = data.resize((cols, ceil(rows * 2 * (px / py) / 0.5)))
267
297
 
@@ -61,28 +61,18 @@ async def call_subproc(
61
61
  stderr=asyncio.subprocess.DEVNULL,
62
62
  )
63
63
  output_bytes, _ = await proc.communicate(stdinput)
64
- except FileNotFoundError as error_:
64
+ except FileNotFoundError as error:
65
65
  log.error("Could not run external command `%s`", cmd)
66
- error = error_
67
- except subprocess.CalledProcessError as error_:
66
+ raise error
67
+ except subprocess.CalledProcessError as error:
68
68
  log.error("There was an error while running external command `%s`", cmd)
69
- error = error_
69
+ raise error
70
+ else:
71
+ if (proc.returncode or 0) > 0 or error:
72
+ # Raise an exception if the process failed so we can continue on the the
73
+ # next conversion method
74
+ raise subprocess.CalledProcessError(proc.returncode or 0, cmd)
70
75
  finally:
71
- if error is not None:
72
- # Generate an output stating there was an error
73
- output_bytes = (
74
- b"\x1b[33m" # Set fg to yellow
75
- b"\xee\x82\xb6" # Draw left pill side
76
- b"\x1b[43m\x1b[30m" # Set fg to black, bg to yellow
77
- b"\xe2\x9a\xa0" # Draw warning symbol
78
- b" Rendering Error"
79
- b"\x1b[33m\x1b[49m" # Set fg to yellow, reset bg
80
- b"\xee\x82\xb4" # Draw right pill side
81
- b"\x1b[n" # Reset style
82
- )
83
-
84
- # TODO Log any stderr
85
-
86
76
  # Clean up any temporary file
87
77
  if use_tempfile:
88
78
  tfile.close()
@@ -3,7 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import os
6
- from functools import lru_cache, partial, reduce
6
+ from functools import partial, reduce
7
7
  from importlib import import_module
8
8
  from shutil import which
9
9
  from typing import TYPE_CHECKING
@@ -13,6 +13,7 @@ from prompt_toolkit.filters import (
13
13
  Condition,
14
14
  emacs_insert_mode,
15
15
  emacs_mode,
16
+ has_completions,
16
17
  to_filter,
17
18
  vi_insert_mode,
18
19
  vi_mode,
@@ -50,57 +51,6 @@ def have_modules(*modules: str) -> Filter:
50
51
  return reduce(lambda a, b: a & b, filters, to_filter(True))
51
52
 
52
53
 
53
- @Condition
54
- @lru_cache
55
- def have_ruff() -> bool:
56
- """Determine if ruff is available."""
57
- try:
58
- import ruff # noqa F401
59
- except ModuleNotFoundError:
60
- return False
61
- else:
62
- return True
63
-
64
-
65
- @Condition
66
- @lru_cache
67
- def have_black() -> bool:
68
- """Determine if black is available."""
69
- try:
70
- import black.const # noqa F401
71
- except ModuleNotFoundError:
72
- return False
73
- else:
74
- return True
75
-
76
-
77
- @Condition
78
- @lru_cache
79
- def have_isort() -> bool:
80
- """Determine if isort is available."""
81
- try:
82
- import isort # noqa F401
83
- except ModuleNotFoundError:
84
- return False
85
- else:
86
- return True
87
-
88
-
89
- @Condition
90
- @lru_cache
91
- def have_ssort() -> bool:
92
- """Determine if ssort is available."""
93
- try:
94
- import ssort # noqa F401
95
- except ModuleNotFoundError:
96
- return False
97
- else:
98
- return True
99
-
100
-
101
- # Determine if we have at least one formatter
102
- have_formatter = have_black | have_isort | have_ssort | have_ruff
103
-
104
54
  # Determine if euporie is running inside a multiplexer.
105
55
  in_screen = to_filter(os.environ.get("TERM", "").startswith("screen"))
106
56
  in_tmux = to_filter(os.environ.get("TMUX") is not None)
@@ -157,6 +107,9 @@ def has_menus() -> bool:
157
107
  return False
158
108
 
159
109
 
110
+ has_float = has_dialog | has_menus | has_completions
111
+
112
+
160
113
  @Condition
161
114
  def tab_has_focus() -> bool:
162
115
  """Determine if there is a currently focused tab."""
@@ -67,11 +67,18 @@ class CliFormatter(Formatter):
67
67
  except Exception:
68
68
  return text
69
69
  try:
70
- output, _error = proc.communicate(text)
70
+ output, error = proc.communicate(text)
71
71
  except Exception:
72
72
  return text
73
73
  else:
74
- return output.rstrip("\r\n")
74
+ if output and not error:
75
+ return output.rstrip("\r\n")
76
+ else:
77
+ return text
78
+
79
+ def __repr__(self) -> str:
80
+ """Return representation of the formatter as a string."""
81
+ return f"{self.command[0].title()}Formatter()"
75
82
 
76
83
 
77
84
  class LspFormatter(Formatter):
@@ -115,3 +122,7 @@ class LspFormatter(Formatter):
115
122
  text = text.rstrip()
116
123
 
117
124
  return text
125
+
126
+ def __repr__(self) -> str:
127
+ """Return representation of the formatter as a string."""
128
+ return f"{self.lsp.name.title()}Formatter()"
@@ -15,7 +15,6 @@ from math import ceil
15
15
  from operator import eq, ge, gt, le, lt
16
16
  from typing import TYPE_CHECKING, NamedTuple, cast, overload
17
17
 
18
- from flatlatex.data import subscript, superscript
19
18
  from fsspec.core import url_to_fs
20
19
  from prompt_toolkit.application.current import get_app_session
21
20
  from prompt_toolkit.data_structures import Size
@@ -74,6 +73,16 @@ from euporie.core.ft.utils import (
74
73
  valign,
75
74
  )
76
75
 
76
+ sub_trans = str.maketrans(
77
+ "0123456789+-=()aeijoruvxβγρφχ",
78
+ "₀₁₂₃₄₅₆₇₈₉₊₋₌₍₎ₐₑᵢⱼₒᵣᵤᵥₓᵦᵧᵨᵩᵪ",
79
+ )
80
+
81
+ sup_trans = str.maketrans(
82
+ "0123456789+-=()abcdefghijklmnoprstuvwxyzABDEGHIJKLMNOPRTUVWαβγδ∊θιΦφχ",
83
+ "⁰¹²³⁴⁵⁶⁷⁸⁹⁺⁻⁼⁽⁾ᵃᵇᶜᵈᵉᶠᵍʰⁱʲᵏˡᵐⁿᵒᵖʳˢᵗᵘᵛʷˣʸᶻᴬᴮᴰᴱᴳᴴᴵᴶᴷᴸᴹᴺᴼᴾᴿᵀᵁⱽᵂᵅᵝᵞᵟᵋᶿᶥᶲᵠᵡ",
84
+ )
85
+
77
86
 
78
87
  class CssSelector(NamedTuple):
79
88
  """A named tuple to hold CSS selector data."""
@@ -343,6 +352,7 @@ _HERITABLE_PROPS = {
343
352
  "text_transform",
344
353
  "text_decoration",
345
354
  "text_align",
355
+ "vertical_align",
346
356
  "visibility",
347
357
  "white_space",
348
358
  "list_style_type",
@@ -1761,18 +1771,19 @@ class Theme(Mapping):
1761
1771
 
1762
1772
  async def text_transform(self, value: str) -> str:
1763
1773
  """Return a function which transforms text."""
1764
- if "uppercase" in self.theme["text_transform"]:
1774
+ transform = self.theme["text_transform"]
1775
+ if "uppercase" in transform:
1765
1776
  return value.upper()
1766
- elif "lowercase" in self.theme["text_transform"]:
1777
+ elif "lowercase" in transform:
1767
1778
  return value.lower()
1768
- elif "capitalize" in self.theme["text_transform"]:
1779
+ elif "capitalize" in transform:
1769
1780
  return value.capitalize()
1770
- elif "sub" in self.theme["vertical_align"]:
1771
- return "".join(subscript.get(c, c) for c in value)
1772
- elif "super" in self.theme["vertical_align"]:
1773
- return "".join(superscript.get(c, c) for c in value)
1774
- elif "latex" in self.theme["text_transform"]:
1781
+ elif "latex" in transform:
1775
1782
  return await Datum(value, "latex").convert_async("ansi")
1783
+ if "sub" in self.theme["vertical_align"]:
1784
+ return value.translate(sub_trans)
1785
+ elif "super" in self.theme["vertical_align"]:
1786
+ return value.translate(sup_trans)
1776
1787
  return value
1777
1788
 
1778
1789
  @cached_property
@@ -2771,7 +2782,7 @@ _BROWSER_CSS: CssSelectors = {
2771
2782
  "text_transform": "latex",
2772
2783
  "display": "inline-block",
2773
2784
  "vertical_align": "top",
2774
- # "margin_right": "1em",
2785
+ "white_space": "pre",
2775
2786
  },
2776
2787
  (
2777
2788
  (
@@ -4275,7 +4286,7 @@ class HTML:
4275
4286
 
4276
4287
  # Render text representation
4277
4288
  ft: StyleAndTextTuples = await self.render_node_content(
4278
- element, left, fill, align_content
4289
+ element, left=0, fill=False, align_content=False
4279
4290
  )
4280
4291
 
4281
4292
  # Render graphic representation
@@ -10,7 +10,6 @@ from typing import TYPE_CHECKING
10
10
 
11
11
  from prompt_toolkit.cache import FastDictCache, SimpleCache
12
12
  from prompt_toolkit.data_structures import Point
13
- from prompt_toolkit.filters.app import has_completions
14
13
  from prompt_toolkit.filters.base import Condition
15
14
  from prompt_toolkit.filters.utils import to_filter
16
15
  from prompt_toolkit.formatted_text.base import to_formatted_text
@@ -25,7 +24,7 @@ from euporie.core.convert.datum import Datum
25
24
  from euporie.core.convert.registry import find_route
26
25
  from euporie.core.current import get_app
27
26
  from euporie.core.data_structures import DiInt
28
- from euporie.core.filters import has_dialog, has_menus, in_mplex
27
+ from euporie.core.filters import has_float, in_mplex
29
28
  from euporie.core.ft.utils import _ZERO_WIDTH_FRAGMENTS
30
29
  from euporie.core.layout.scroll import BoundedWritePosition
31
30
  from euporie.core.terminal import passthrough
@@ -192,7 +191,7 @@ class SixelGraphicControl(GraphicControl):
192
191
  def render_lines() -> list[StyleAndTextTuples]:
193
192
  """Render the lines to display in the control."""
194
193
  ft: list[StyleAndTextTuples] = []
195
- if height:
194
+ if height >= 0:
196
195
  cmd = self.convert_data(
197
196
  BoundedWritePosition(0, 0, width, height, self.bbox)
198
197
  )
@@ -282,7 +281,7 @@ class ItermGraphicControl(GraphicControl):
282
281
  def render_lines() -> list[StyleAndTextTuples]:
283
282
  """Render the lines to display in the control."""
284
283
  ft: list[StyleAndTextTuples] = []
285
- if height:
284
+ if height > 0 and width > 0:
286
285
  b64data = self.convert_data(
287
286
  BoundedWritePosition(0, 0, width, height, self.bbox)
288
287
  )
@@ -574,7 +573,7 @@ class GraphicWindow(Window):
574
573
  super().__init__(*args, **kwargs)
575
574
  self.content = content
576
575
  self.get_position = get_position
577
- self.filter = ~has_completions & ~has_dialog & ~has_menus & to_filter(filter)
576
+ self.filter = ~has_float & to_filter(filter)
578
577
  self._pre_rendered = False
579
578
 
580
579
  def write_to_screen(
@@ -680,6 +680,9 @@ def dent_buffer(event: KeyPressEvent, indenting: bool = True) -> None:
680
680
  )
681
681
  * sign
682
682
  )
683
+ selection_state.original_cursor_position = max(
684
+ min(selection_state.original_cursor_position, len(buffer.text)), 0
685
+ )
683
686
  # Maintain the selection state before indentation
684
687
  buffer.selection_state = selection_state
685
688
 
@@ -722,7 +725,7 @@ def indent_lines(event: KeyPressEvent) -> None:
722
725
  @add_cmd(
723
726
  filter=buffer_has_focus
724
727
  & (cursor_in_leading_ws | has_selection)
725
- & ~cursor_at_start_of_line,
728
+ & (~cursor_at_start_of_line | cursor_at_start_of_line),
726
729
  )
727
730
  def unindent_lines(event: KeyPressEvent) -> None:
728
731
  """Unindent the current or selected lines."""
@@ -59,6 +59,7 @@ _kitty_functional_codes = {
59
59
  "F12": (24, "~"),
60
60
  "Backspace": (127, "u"),
61
61
  "Enter": (13, "u"),
62
+ "Tab": (9, "u"),
62
63
  }
63
64
 
64
65
  # Add Alt-key shortcuts
@@ -11,6 +11,12 @@ APP_ALIASES = {
11
11
  "edit": "notebook",
12
12
  }
13
13
 
14
+ _EPS = entry_points()
15
+ if isinstance(_EPS, dict):
16
+ APP_ENTRY_POINTS = _EPS.get("euporie.apps")
17
+ else:
18
+ APP_ENTRY_POINTS = _EPS.select(group="euporie.apps")
19
+
14
20
 
15
21
  class CoreApp:
16
22
  """Launch a euporie application."""
@@ -50,8 +56,7 @@ class CoreApp:
50
56
  required=False,
51
57
  help_="The application to launch",
52
58
  choices=sorted(
53
- {entry.name for entry in entry_points()["euporie.apps"]} - {"launch"}
54
- | APP_ALIASES.keys()
59
+ {entry.name for entry in APP_ENTRY_POINTS} - {"launch"} | APP_ALIASES.keys()
55
60
  ),
56
61
  description="""
57
62
  The name of the application to launch.
@@ -600,9 +600,10 @@ class ScrollingContainer(Container):
600
600
 
601
601
  def all_children(self) -> Sequence[Container]:
602
602
  """Return the list of all children of this container."""
603
- if self.refresh_children:
604
- _children = self._children
605
- _children.clear()
603
+ _children = self._children
604
+ if self.refresh_children or not self._children:
605
+ self.refresh_children = False
606
+ new_children = []
606
607
  new_child_hashes = set()
607
608
  for child in self.children_func():
608
609
  if not (
@@ -611,21 +612,21 @@ class ScrollingContainer(Container):
611
612
  wrapped_child = self._child_cache[child_hash] = CachedContainer(
612
613
  child, mouse_handler_wrapper=self._mouse_handler_wrapper
613
614
  )
614
- _children.append(wrapped_child)
615
+ new_children.append(wrapped_child)
615
616
  new_child_hashes.add(child_hash)
617
+ _children[:] = new_children
616
618
 
617
619
  # Clean up metacache
618
620
  for child_hash in set(self._child_cache) - new_child_hashes:
619
621
  del self._child_cache[child_hash]
620
622
 
621
- self.refresh_children = False
622
623
  # Clean up positions
623
624
  self.index_positions = {
624
625
  i: pos
625
626
  for i, pos in self.index_positions.items()
626
627
  if i < len(self._children)
627
628
  }
628
- return self._children
629
+ return _children
629
630
 
630
631
  def get_children(self) -> list[Container]:
631
632
  """Return the list of currently visible children to include in the layout."""
@@ -17,6 +17,7 @@ from prompt_toolkit.completion.base import (
17
17
  _MergedCompleter,
18
18
  )
19
19
  from prompt_toolkit.document import Document
20
+ from prompt_toolkit.filters.app import is_searching
20
21
  from prompt_toolkit.filters.base import Condition
21
22
  from prompt_toolkit.layout.containers import (
22
23
  ConditionalContainer,
@@ -163,6 +164,11 @@ class Cell:
163
164
  """Respond to cursor movements."""
164
165
  # Tell the scrolling container to scroll the cursor into view on the next render
165
166
  weak_self.kernel_tab.page.scroll_to_cursor = True
167
+ if not is_searching():
168
+ if not self.selected:
169
+ weak_self.kernel_tab.select(self.index, scroll=True)
170
+ if not weak_self.kernel_tab.in_edit_mode():
171
+ weak_self.kernel_tab.enter_edit_mode()
166
172
 
167
173
  # Now we generate the main container used to represent a kernel_tab cell
168
174
 
@@ -720,6 +726,8 @@ class Cell:
720
726
  self.input_box.control._fragment_cache.clear()
721
727
  # Trigger callbacks
722
728
  self.on_change()
729
+ # Flag notebook as modified
730
+ self.kernel_tab.dirty = True
723
731
 
724
732
  @property
725
733
  def path(self) -> Path:
@@ -358,7 +358,7 @@ class CellOutput:
358
358
  metadata=self.json.get("metadata", {}).get(mime, {}),
359
359
  parent=self.parent,
360
360
  )
361
- except NotImplementedError:
361
+ except (NotImplementedError, KeyError):
362
362
  self.selected_mime = mime = list(data.keys())[-1]
363
363
  continue
364
364
  else: