euporie 2.8.0__py3-none-any.whl → 2.8.2__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.
@@ -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
 
euporie/core/__init__.py CHANGED
@@ -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
euporie/core/__main__.py CHANGED
@@ -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()
euporie/core/filters.py CHANGED
@@ -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."""
euporie/core/format.py CHANGED
@@ -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()"
euporie/core/ft/html.py CHANGED
@@ -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
euporie/core/graphics.py CHANGED
@@ -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."""
euporie/core/keys.py CHANGED
@@ -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
euporie/core/launch.py CHANGED
@@ -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:
@@ -19,10 +19,7 @@ from prompt_toolkit.utils import Event, to_str
19
19
  from euporie.core.commands import add_cmd
20
20
  from euporie.core.convert.datum import Datum
21
21
  from euporie.core.current import get_app
22
- from euporie.core.filters import (
23
- display_has_focus,
24
- scrollable,
25
- )
22
+ from euporie.core.filters import display_has_focus, scrollable
26
23
  from euporie.core.ft.utils import wrap
27
24
  from euporie.core.graphics import GraphicProcessor
28
25
  from euporie.core.key_binding.registry import (
@@ -89,7 +86,8 @@ class DisplayControl(UIControl):
89
86
  self.height = 0
90
87
 
91
88
  self.key_bindings = load_registered_bindings(
92
- "euporie.core.widgets.display.DisplayControl"
89
+ "euporie.core.widgets.display.DisplayControl",
90
+ config=get_app().config,
93
91
  )
94
92
 
95
93
  self.rendered = Event(self)
@@ -159,7 +157,7 @@ class DisplayControl(UIControl):
159
157
  extend=not self.dont_extend_width(),
160
158
  )
161
159
  if width and height:
162
- key = Datum.add_size(datum, Size(height, self.width))
160
+ key = Datum.add_size(datum, Size(self.height, self.width))
163
161
  ft = [(f"[Graphic_{key}]", ""), *ft]
164
162
  lines = list(split_lines(ft))
165
163
  if wrap_lines and width:
@@ -241,12 +239,18 @@ class DisplayControl(UIControl):
241
239
  get_line_prefix: GetLinePrefixCallable | None,
242
240
  ) -> int | None:
243
241
  """Calculate and return the preferred height of the control."""
242
+ height = None
244
243
  max_cols, aspect = self.datum.cell_size()
245
244
  if aspect:
246
- return ceil(min(width, max_cols) * aspect)
245
+ height = ceil(min(width, max_cols) * aspect)
247
246
  cp = self.color_palette
248
247
  self.lines = self._line_cache[
249
- self.datum, width, None, cp.fg.base_hex, cp.bg.base_hex, self.wrap_lines()
248
+ self.datum,
249
+ width,
250
+ height,
251
+ cp.fg.base_hex,
252
+ cp.bg.base_hex,
253
+ self.wrap_lines(),
250
254
  ]
251
255
  return len(self.lines)
252
256
 
@@ -299,17 +303,19 @@ class DisplayControl(UIControl):
299
303
  A :py:class:`UIContent` instance.
300
304
  """
301
305
  # Trigger a re-render in the future if things have changed
306
+ render = False
302
307
  if self.loading:
303
- self.render()
304
- if width != self.width:
308
+ render = True
309
+ if width != self.width or height != self.height:
305
310
  self.resizing = True
306
311
  self.width = width
307
312
  self.height = height
308
- self.render()
313
+ render = True
309
314
  if (cp := get_app().color_palette) != self.color_palette:
310
315
  self.color_palette = cp
316
+ render = True
317
+ if render:
311
318
  self.render()
312
-
313
319
  content = self._content_cache[
314
320
  self.datum, width, height, self.loading, self.cursor_position, cp
315
321
  ]
@@ -129,7 +129,7 @@ class KernelInput(TextArea):
129
129
  scrollbar: FilterOrBool = True,
130
130
  style: str = "class:kernel-input",
131
131
  search_field: SearchToolbar | None = None,
132
- preview_search: FilterOrBool = True,
132
+ preview_search: FilterOrBool = False,
133
133
  prompt: AnyFormattedText = "",
134
134
  input_processors: list[Processor] | None = None,
135
135
  name: str = "",
@@ -228,7 +228,8 @@ class KernelInput(TextArea):
228
228
 
229
229
  # Set extra key-bindings
230
230
  widgets_key_bindings = load_registered_bindings(
231
- "euporie.core.widgets.inputs.KernelInput"
231
+ "euporie.core.widgets.inputs.KernelInput",
232
+ config=app.config,
232
233
  )
233
234
  if key_bindings:
234
235
  widgets_key_bindings = merge_key_bindings(
@@ -408,8 +408,6 @@ class MenuBar:
408
408
  """Close the current menu."""
409
409
  if self.selected_menu:
410
410
  self.selected_menu = self.selected_menu[:-1]
411
- log.debug(self.selected_menu)
412
- log.debug(self.last_focused)
413
411
  self.refocus()
414
412
 
415
413
  # Add global CUA menu shortcut
@@ -516,7 +514,7 @@ class MenuBar:
516
514
  focused = self.focused()
517
515
 
518
516
  # This is called during the rendering. When we discover that this
519
- # widget doesn't have the focus anymore. Reset menu state.
517
+ # widget doesn't have the focus anymore, reset the menu state.
520
518
  if not focused:
521
519
  self.selected_menu = []
522
520
 
@@ -527,9 +525,11 @@ class MenuBar:
527
525
  # Toggle focus.
528
526
  if not hover and focused and self.selected_menu == [index]:
529
527
  self.selected_menu = []
530
- self.selected_menu = [index]
528
+ else:
529
+ self.selected_menu = [index]
531
530
  self.refocus()
532
531
  return None
532
+
533
533
  return NotImplemented
534
534
 
535
535
  results: StyleAndTextTuples = []
@@ -106,6 +106,7 @@ class PagerOutput(CellOutput):
106
106
  Returns:
107
107
  A :class:`PagerOutputDataElement` container for the currently selected mime-type.
108
108
  """
109
+ self._selected_mime = None
109
110
  return PagerOutputDataElement(
110
111
  mime=self.selected_mime,
111
112
  data=self.data[self.selected_mime],
@@ -146,7 +147,8 @@ class Pager:
146
147
  ],
147
148
  style="class:pager",
148
149
  key_bindings=load_registered_bindings(
149
- "euporie.core.widgets.pager.Pager"
150
+ "euporie.core.widgets.pager.Pager",
151
+ config=get_app().config,
150
152
  ),
151
153
  height=height,
152
154
  ),
@@ -5,7 +5,9 @@ from __future__ import annotations
5
5
  import logging
6
6
  from typing import TYPE_CHECKING
7
7
 
8
+ from prompt_toolkit.document import Document
8
9
  from prompt_toolkit.filters.app import is_searching
10
+ from prompt_toolkit.filters.base import Condition
9
11
  from prompt_toolkit.key_binding.vi_state import InputMode
10
12
  from prompt_toolkit.layout.controls import BufferControl, SearchBufferControl
11
13
  from prompt_toolkit.search import SearchDirection
@@ -55,7 +57,12 @@ class SearchBar(PtkSearchToolbar):
55
57
  ],
56
58
  )
57
59
  self.control.key_bindings = load_registered_bindings(
58
- "euporie.core.widgets.search.SearchBar"
60
+ "euporie.core.widgets.search.SearchBar",
61
+ config=get_app().config,
62
+ )
63
+ search_state = self.control.searcher_search_state
64
+ search_state.ignore_case = Condition(
65
+ lambda: self.search_buffer.text.islower() or search_state.text.islower()
59
66
  )
60
67
 
61
68
  register_bindings(
@@ -73,28 +80,44 @@ class SearchBar(PtkSearchToolbar):
73
80
  )
74
81
 
75
82
 
76
- def start_global_search(
77
- buffer_control: BufferControl | None = None,
78
- direction: SearchDirection = SearchDirection.FORWARD,
79
- ) -> None:
80
- """Start a search through all searchable `buffer_controls` in the layout."""
83
+ def find_search_control() -> tuple[SearchBufferControl | None, BufferControl | None]:
84
+ """Find the current search buffer and buffer control."""
85
+ current_buffer_control: BufferControl | None = None
86
+ search_buffer_control: SearchBufferControl | None = None
87
+
81
88
  app = get_app()
82
89
  layout = app.layout
83
- current_control = layout.current_control
84
- # Find the search buffer control
85
- if app.search_bar is not None:
90
+ current_control = app.layout.current_control
91
+
92
+ if isinstance(current_control, SearchBufferControl):
93
+ search_buffer_control = current_control
94
+
95
+ if search_buffer_control is None and app.search_bar is not None:
86
96
  search_buffer_control = app.search_bar.control
87
- elif (
88
- isinstance(current_control, BufferControl)
89
- and current_control.search_buffer_control is not None
97
+
98
+ if search_buffer_control is not None and current_buffer_control is None:
99
+ current_buffer_control = layout.search_links.get(search_buffer_control)
100
+
101
+ if current_buffer_control is None and isinstance(current_control, BufferControl):
102
+ current_buffer_control = current_control
103
+
104
+ if (
105
+ search_buffer_control is None
106
+ and current_buffer_control is not None
107
+ and current_buffer_control.search_buffer_control is not None
90
108
  ):
91
- search_buffer_control = current_control.search_buffer_control
92
- else:
93
- return
94
- # Find all searchable controls
109
+ search_buffer_control = current_buffer_control.search_buffer_control
110
+
111
+ return search_buffer_control, current_buffer_control
112
+
113
+
114
+ def find_searchable_controls(
115
+ search_buffer_control: SearchBufferControl, current_control: BufferControl | None
116
+ ) -> list[BufferControl]:
117
+ """Find list of searchable controls and the index of the next control."""
95
118
  searchable_controls: list[BufferControl] = []
96
119
  next_control_index = 0
97
- for control in layout.find_all_controls():
120
+ for control in get_app().layout.find_all_controls():
98
121
  # Find the index of the next searchable control so we can link the search
99
122
  # control to it if the currently focused control is not searchable. This is so
100
123
  # that the next searchable control can be focused when search is completed.
@@ -105,24 +128,40 @@ def start_global_search(
105
128
  isinstance(control, BufferControl)
106
129
  and control.search_buffer_control == search_buffer_control
107
130
  ):
108
- # Set its search direction
109
- control.search_state.direction = direction
110
131
  # Add it to our list
111
132
  searchable_controls.append(control)
133
+ searchable_controls = (
134
+ searchable_controls[next_control_index:]
135
+ + searchable_controls[:next_control_index]
136
+ )
137
+ return searchable_controls
138
+
139
+
140
+ def start_global_search(
141
+ buffer_control: BufferControl | None = None,
142
+ direction: SearchDirection = SearchDirection.FORWARD,
143
+ ) -> None:
144
+ """Start a search through all searchable `buffer_controls` in the layout."""
145
+ search_buffer_control, current_control = find_search_control()
146
+ if search_buffer_control is None:
147
+ return
148
+ searchable_controls = find_searchable_controls(
149
+ search_buffer_control, current_control
150
+ )
112
151
 
113
152
  # Stop the search if we did not find any searchable controls
114
153
  if not searchable_controls:
115
154
  return
116
155
 
117
156
  # If the current control is searchable, link it
157
+ app = get_app()
158
+ layout = app.layout
118
159
  if current_control in searchable_controls:
119
160
  assert isinstance(current_control, BufferControl)
120
161
  layout.search_links[search_buffer_control] = current_control
121
162
  else:
122
163
  # otherwise use the next after the currently selected control
123
- layout.search_links[search_buffer_control] = searchable_controls[
124
- next_control_index % len(searchable_controls)
125
- ]
164
+ layout.search_links[search_buffer_control] = searchable_controls[0]
126
165
  # Make sure to focus the search BufferControl
127
166
  layout.focus(search_buffer_control)
128
167
  # If we're in Vi mode, make sure to go into insert mode.
@@ -137,31 +176,85 @@ def find() -> None:
137
176
 
138
177
  def find_prev_next(direction: SearchDirection) -> None:
139
178
  """Find the previous or next search match."""
140
- app = get_app()
141
- layout = app.layout
142
- control = app.layout.current_control
143
- # Determine search buffer and searched buffer
144
- search_buffer_control = None
145
- if isinstance(control, SearchBufferControl):
146
- search_buffer_control = control
147
- control = layout.search_links[search_buffer_control]
148
- elif isinstance(control, BufferControl):
149
- if control.search_buffer_control is not None:
150
- search_buffer_control = control.search_buffer_control
151
- elif app.search_bar is not None:
152
- search_buffer_control = app.search_bar.control
153
- if isinstance(control, BufferControl) and search_buffer_control is not None:
179
+ if is_searching():
180
+ accept_search()
181
+
182
+ search_buffer_control, current_control = find_search_control()
183
+ if search_buffer_control is None:
184
+ return
185
+ searchable_controls = find_searchable_controls(
186
+ search_buffer_control, current_control
187
+ )
188
+
189
+ if direction == SearchDirection.BACKWARD:
190
+ searchable_controls = searchable_controls[:1] + searchable_controls[1:][::-1]
191
+
192
+ # Search over all searchable buffers
193
+ for i, control in enumerate(searchable_controls):
154
194
  # Update search_state.
155
195
  search_state = control.search_state
156
196
  search_state.direction = direction
157
197
  # Apply search to buffer
158
198
  buffer = control.buffer
159
- buffer.apply_search(search_state, include_current_position=False, count=1)
160
- # Set selection
161
- buffer.selection_state = SelectionState(
162
- buffer.cursor_position + len(search_state.text)
163
- )
164
- buffer.selection_state.enter_shift_mode()
199
+
200
+ search_result: tuple[int, int] | None = None
201
+
202
+ # If we are searching history, use the PTK buffer search implementation
203
+ if buffer.enable_history_search():
204
+ search_result = buffer._search(search_state)
205
+
206
+ # Otherwise, only search the buffer's current "working line"
207
+ else:
208
+ document = buffer.document
209
+ # If have move to the next buffer, set the cursor position for the start of
210
+ # the search to the start or the end of the text, depending on if we are
211
+ # searching forwards or backwards
212
+ if i > 0:
213
+ if direction == SearchDirection.FORWARD:
214
+ document = Document(document.text, 0)
215
+ else:
216
+ document = Document(document.text, len(document.text))
217
+
218
+ text = search_state.text
219
+ ignore_case = search_state.ignore_case()
220
+
221
+ if direction == SearchDirection.FORWARD:
222
+ # Try find at the current input.
223
+ new_index = document.find(
224
+ text,
225
+ # If we have moved to the next buffer, include the current position
226
+ # which will be the start of the document text
227
+ include_current_position=i > 0,
228
+ ignore_case=ignore_case,
229
+ )
230
+ if new_index is not None:
231
+ search_result = (
232
+ buffer.working_index,
233
+ document.cursor_position + new_index,
234
+ )
235
+ else:
236
+ # Try find at the current input.
237
+ new_index = document.find_backwards(text, ignore_case=ignore_case)
238
+ if new_index is not None:
239
+ search_result = (
240
+ buffer.working_index,
241
+ document.cursor_position + new_index,
242
+ )
243
+
244
+ if search_result is not None:
245
+ working_index, cursor_position = search_result
246
+ buffer.working_index = working_index
247
+ buffer.cursor_position = cursor_position
248
+ # Set SelectionState
249
+ buffer.selection_state = SelectionState(
250
+ buffer.cursor_position + len(search_state.text)
251
+ )
252
+ buffer.selection_state.enter_shift_mode()
253
+
254
+ # Trigger a cursor position changed event on this buffer
255
+ buffer._cursor_position_changed()
256
+
257
+ break
165
258
 
166
259
 
167
260
  @add_cmd()
@@ -148,7 +148,10 @@ class EditorTab(KernelTab):
148
148
  [self.input_box],
149
149
  width=Dimension(weight=1),
150
150
  height=Dimension(weight=1),
151
- key_bindings=load_registered_bindings("euporie.core.tabs.base.Tab"),
151
+ key_bindings=load_registered_bindings(
152
+ "euporie.core.tabs.base.Tab",
153
+ config=self.app.config,
154
+ ),
152
155
  )
153
156
 
154
157
  def save(self, path: Path | None = None, cb: Callable | None = None) -> None:
@@ -28,9 +28,7 @@ from euporie.core.filters import (
28
28
  cursor_on_first_line,
29
29
  cursor_on_last_line,
30
30
  display_has_focus,
31
- have_formatter,
32
31
  insert_mode,
33
- kernel_is_python,
34
32
  kernel_tab_has_focus,
35
33
  multiple_cells_selected,
36
34
  replace_mode,
@@ -86,7 +84,7 @@ class Notebook(BaseNotebook):
86
84
  except ModuleNotFoundError:
87
85
  pass
88
86
  else:
89
- file_extensions |= dict.fromkeys(NOTEBOOK_EXTENSIONS)
87
+ file_extensions.update(dict.fromkeys(NOTEBOOK_EXTENSIONS))
90
88
 
91
89
  allow_stdin = True
92
90
 
@@ -246,15 +244,16 @@ class Notebook(BaseNotebook):
246
244
  key_bindings=load_registered_bindings(
247
245
  "euporie.core.tabs.base.Tab",
248
246
  "euporie.notebook.tabs.notebook.Notebook",
247
+ config=self.app.config,
249
248
  ),
250
249
  )
251
250
 
252
251
  @property
253
252
  def cell(self) -> Cell:
254
253
  """Return the currently selected `Cell` in this `Notebook`."""
255
- cell = self.page.get_child().content
256
- assert isinstance(cell, Cell)
257
- return cell
254
+ if isinstance(cell := self.page.get_child().content, Cell):
255
+ return cell
256
+ return Cell(0, {}, self)
258
257
 
259
258
  # Editing specific stuff
260
259
 
@@ -1096,12 +1095,7 @@ class Notebook(BaseNotebook):
1096
1095
  cell.input_box.reformat()
1097
1096
 
1098
1097
  @staticmethod
1099
- @add_cmd(
1100
- filter=have_formatter
1101
- & kernel_is_python
1102
- & notebook_has_focus
1103
- & ~buffer_has_focus,
1104
- )
1098
+ @add_cmd(filter=notebook_has_focus & ~buffer_has_focus)
1105
1099
  def _reformat_notebook() -> None:
1106
1100
  """Automatically reformat all code cells in the notebook."""
1107
1101
  nb = get_app().tab
@@ -95,7 +95,8 @@ class WebViewControl(UIControl):
95
95
  self.render_thread = Thread(target=self.loop.run_forever, daemon=True)
96
96
 
97
97
  self.key_bindings = load_registered_bindings(
98
- "euporie.web.widgets.webview.WebViewControl"
98
+ "euporie.web.widgets.webview.WebViewControl",
99
+ config=get_app().config,
99
100
  )
100
101
 
101
102
  self._dom_cache: FastDictCache[tuple[Path], HTML] = FastDictCache(
@@ -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
@@ -3,9 +3,9 @@ euporie/console/__main__.py,sha256=m2EnzIDLO4dHlDt41JNKpUvAUSw6wD-X0V3YhymXqxc,2
3
3
  euporie/console/app.py,sha256=Icu-q-qTOuap873-s0ONcrtluD6EH5DPlbxX8ue6Zqo,7379
4
4
  euporie/console/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  euporie/console/tabs/__init__.py,sha256=Grm9EO1gnBO1o-k6Nqnlzo8XLIgFZSXNAdz49Vvh4zY,55
6
- euporie/console/tabs/console.py,sha256=GR-sGiOMnVigOYWqBMuzAnrfImUPDhnS5lWFIuUCcbk,26068
7
- euporie/core/__init__.py,sha256=jxseVjT-dxbt7sKWcD4hEFUMp2b-iYpKAwdavMolIvI,536
8
- euporie/core/__main__.py,sha256=8sHP02sjS29QZzOwPF7hSzOR9UwPEvsMCbuGuOlxVu4,603
6
+ euporie/console/tabs/console.py,sha256=egClI0kRqQeBKjXOthxgpQ4eQpekaaN1LM76tKXJlEA,26109
7
+ euporie/core/__init__.py,sha256=YSCEIHk41Hc1BVsqRvmgKdvTgcxf8Ctf8rOuAbPdDzI,313
8
+ euporie/core/__main__.py,sha256=_TeewhwpThCpmL8qYYtd61qeE_t61To1zrcM9o4lL_s,842
9
9
  euporie/core/app.py,sha256=MVGQJbXb30LbDgETCLbHTnpFD_zRhn8vSE0mWQzNw2o,46040
10
10
  euporie/core/border.py,sha256=kJbpyxwJBtIRpFOpXhDtaCT1_oyVRjRiYKG4lXn3-iA,48507
11
11
  euporie/core/clipboard.py,sha256=Ps1wTcIV2B1PKfXiNKITR8nee2bo5qVfdCuhobiCNms,4461
@@ -15,15 +15,15 @@ euporie/core/config.py,sha256=ka6wloVH2hLhe7ga3w0nKLtuYsMenXcumdIC4eNSqM8,22811
15
15
  euporie/core/current.py,sha256=BdSVCLngNpZdtYJaxdijuu8N9yRcoU_wr0jI6RGRSms,557
16
16
  euporie/core/data_structures.py,sha256=eA54Cg305R1mVX6YGTRPIvTN4piEFKrltehif49l92o,1800
17
17
  euporie/core/diagnostics.py,sha256=5rS3DbJqNiPVfOb3tU4RKyq2rIMoS3O3_0-a62zknPE,1686
18
- euporie/core/filters.py,sha256=OSyOzp-7LuBsfQoJQqD3aKpMayeJOAT5eYSa-RGoYWk,9180
19
- euporie/core/format.py,sha256=jFt-GgLDId6Wzz88cA6spkDM6Hix_ihRx9fV_feBENw,3764
20
- euporie/core/graphics.py,sha256=JxBIpa0gL8iT_GH_Zk0RhwetMk1683KaQCCueemrTvY,32728
18
+ euporie/core/filters.py,sha256=wjTE7INpUAglprngLipLa66S_pNXoalu0HrcmmJeSlM,8253
19
+ euporie/core/format.py,sha256=oFlD3aX_uVeLhqxo2n-zaUBsiNKpWqhx6W-5arFY5C4,4154
20
+ euporie/core/graphics.py,sha256=g9kJeuoOCgx4IeD_EIlf3tAdRfQy4bTLs7istiZnvl0,32651
21
21
  euporie/core/history.py,sha256=uO14q8keGEtH1R9loaZrBfy66GfXcJhxyXW95-35niA,2542
22
22
  euporie/core/inspection.py,sha256=JhUttlOqvcYOLTSqrn49zSACbaeGLwbwK-BVOLllh6Y,2563
23
23
  euporie/core/io.py,sha256=ipl11Uo3mOmbqTjqC9k1Z8nwXmgNWgY-7ROI3WG4FwY,4469
24
24
  euporie/core/kernel.py,sha256=9bKFeE41Hrr241scVSnIjTFwKVSEk3yDc9HGK2rLXMs,46223
25
- euporie/core/keys.py,sha256=AZQI9e1l7OpUTEdH1r2feYAC2lyFPqkZw8-BYDjnbbI,3288
26
- euporie/core/launch.py,sha256=a51t67xWCGXVPRfzQMeHSJHaMBC5SDkGsjBGUqNSX_w,1405
25
+ euporie/core/keys.py,sha256=hfxzZjh4dbDdEkel1YXu8p5RgGwD44oZ9mptHZ0TcfA,3309
26
+ euporie/core/launch.py,sha256=drtCrvVmGS19nMjL21Q0YwB7zZ0EK0E-0Wa_rRS0N70,1540
27
27
  euporie/core/lexers.py,sha256=AXNDYOR0BZf0sYj9o8YSb0oF4AGYZdKygwFIUKJ3XFM,1083
28
28
  euporie/core/log.py,sha256=6d8Dk5SA2fYr2QL1BqATmMtTU_BQ_DuONqNSQAYiOek,16323
29
29
  euporie/core/lsp.py,sha256=LU-sPEl-jeGA-nqlJMoFl0SzcTS9cY9KDKVmU3bDXdk,49204
@@ -44,12 +44,12 @@ euporie/core/comm/base.py,sha256=72PF6upybtBnNB3M8oNkR8WDzC2vptRS9TmIKJkNRq4,404
44
44
  euporie/core/comm/ipywidgets.py,sha256=obOKgcYc8OBB1PfKfNRB59ppZbnVA2eCDokSTUucZ7Q,52725
45
45
  euporie/core/comm/registry.py,sha256=cxH5r6BZe-NUy0zFqxTriAnP0_spMY3m9r1Na688i7o,1353
46
46
  euporie/core/convert/__init__.py,sha256=SdXTdFh5Exb7NLtiY7pDuv4AcQdP5b1syQSoZeanJco,391
47
- euporie/core/convert/datum.py,sha256=VRkaVVVHHpfVxWY6zhBXgHD2qmPXfQAM9IfeMxJW1bE,14029
47
+ euporie/core/convert/datum.py,sha256=UxZBmiizxwviQF06IyBqPLiRWre4FSTdFOljb1WZWjM,14327
48
48
  euporie/core/convert/mime.py,sha256=uQwXw2hUsfS_dMeR4Ci6Zv4fORIBnEa9rdRa-nVT-Ls,3142
49
49
  euporie/core/convert/registry.py,sha256=IP43FE1edE2WDpjlKNylWhQV88WULpzWCIddBOQxxk8,2757
50
- euporie/core/convert/utils.py,sha256=aB1QdbfAvRJ0RRllhgI-knsX9eJQEU9Ygp26549n7Rk,2782
50
+ euporie/core/convert/utils.py,sha256=h1qlZBQ0YqV-EgdIDZcO14KJwIqA5FfDOW_nd4ZtIDA,2438
51
51
  euporie/core/convert/formats/__init__.py,sha256=sPJqKh1aA0V2jJs5cXUBmAEadsozUsGxv55irlrVr68,164
52
- euporie/core/convert/formats/ansi.py,sha256=fN2VJPYZ4D0fY77mbeW2hrT-JfnU7vqDEyPt8kl-HPo,13613
52
+ euporie/core/convert/formats/ansi.py,sha256=SqkwtKqo-7wD2_tCgsJYwsCcM4vBgXPA5tGB7oVqtcM,14327
53
53
  euporie/core/convert/formats/base64.py,sha256=8tP29nX_patgOG9lYr-HIn0I_6VyV_SyfDi-ZZBTp6Q,878
54
54
  euporie/core/convert/formats/common.py,sha256=bDfVSBrMYCM1yAuKO254R2JBO-viZHB0MArezti5txM,4959
55
55
  euporie/core/convert/formats/ft.py,sha256=6RKWq0y3kbJ96kbMjfazGeBg3uha8CWoseRPa4WLWHE,2810
@@ -64,7 +64,7 @@ euporie/core/convert/formats/sixel.py,sha256=gsBDwXowN5eg-X-khzUjhYcNUsNl2up3zQV
64
64
  euporie/core/convert/formats/svg.py,sha256=SY-rioXSm5lK6LLp3mu3YeFYTFOJ-jCvEwH-wusfU1s,805
65
65
  euporie/core/ft/__init__.py,sha256=Ib-pswos3RiOM2Vn8GCZ4rwuoTNPeBJ0jhgRpwu7BKE,55
66
66
  euporie/core/ft/ansi.py,sha256=ICUBohoOVtbkf96T3EsFcHf-oSsWMxD7uyRoEFnhS1I,5796
67
- euporie/core/ft/html.py,sha256=qZAwXikisfiUKfirtPVOlsrTDdOyRKGtW8HNon8z9YU,179010
67
+ euporie/core/ft/html.py,sha256=ITqARO7LK6UKMb6NTCl73UdxwfOT2nMSE-x1Ysp15WQ,179417
68
68
  euporie/core/ft/table.py,sha256=L6rtT1XHArydL7gYX1m98MPoKCDYk8L7jibKRiayaGc,53325
69
69
  euporie/core/ft/utils.py,sha256=Or3eZwoJi2NbYQg0M2mk815RlKcDmGXr7K5-7JJcT1k,27773
70
70
  euporie/core/key_binding/__init__.py,sha256=zFKrJrZARNht6Tl4TT5Fw3fk-IrD8SBYk5mBJ5K_F1A,47
@@ -76,7 +76,7 @@ euporie/core/key_binding/vi_state.py,sha256=ek3Wxsckihsr5x9ULiDtNnVkOEiCBzoF4m4q
76
76
  euporie/core/key_binding/bindings/__init__.py,sha256=vzrTyqaHWRxLDdiLStPQ-AprURIie76-i3cibELIDsA,274
77
77
  euporie/core/key_binding/bindings/basic.py,sha256=djhGhPC6yDPfgXcNXfh14wLhFdraUBQH8VMfkZnLZvI,1648
78
78
  euporie/core/key_binding/bindings/completion.py,sha256=npf61P0NvmrYMG8qT-1AYDReLcoMHsgdG5gzBloSTWk,2081
79
- euporie/core/key_binding/bindings/micro.py,sha256=04ByQ-7N5YNPOouHZiYcBIFIuHX2KuLW3XUZ1wzSBBo,27847
79
+ euporie/core/key_binding/bindings/micro.py,sha256=NZSeYZIlN2kA-nNRC8_0ECqT4jRbGOfcTllKnpRrXh8,28020
80
80
  euporie/core/key_binding/bindings/mouse.py,sha256=xbsBFSF2zLtK-f_Aj2NQ5kFaWaR1fimvs7dXhBpiFFM,6545
81
81
  euporie/core/key_binding/bindings/page_navigation.py,sha256=FWIv4iqWUCMjFHLu-I5Z08NIULXuTIjrn9fC25XImYg,4142
82
82
  euporie/core/layout/__init__.py,sha256=T5iGOnMGfNwVffJlfFMwGTFBLV7RkOuKw5gVp6cl4wA,59
@@ -87,25 +87,25 @@ euporie/core/layout/decor.py,sha256=qMCgkrjokpqH9gaBXLe_rGLx9AwzfJ7rtyVhSAVh45w,
87
87
  euporie/core/layout/mouse.py,sha256=GJvFwOCZ8x-CppW8rGmJ6kV6JdW0Ndjihf8IwkuBI6Q,5165
88
88
  euporie/core/layout/print.py,sha256=YNDafjVTcI8GwOjueLBuqWMY72alxV9HE0BQaka9sgA,4645
89
89
  euporie/core/layout/screen.py,sha256=0xP9ZErMcw51nPgBbrm-DSQqFq5WNvsh9fw1amz2Z28,2224
90
- euporie/core/layout/scroll.py,sha256=DMBoVNNfahMDbq_vKezT3qDfQm2EwBGwOv2qy5QYkDc,33093
90
+ euporie/core/layout/scroll.py,sha256=mVmAbECfytPk2oPMZ_HpeAbSsYIXyX8FDQQ7a8UWRw4,33149
91
91
  euporie/core/tabs/__init__.py,sha256=Grm9EO1gnBO1o-k6Nqnlzo8XLIgFZSXNAdz49Vvh4zY,55
92
92
  euporie/core/tabs/base.py,sha256=Gdydp8Km9p6Hdqs-pdHvxGAKyWhvE_76zdE4ywcZF-g,20934
93
93
  euporie/core/tabs/notebook.py,sha256=4EdMMVvHzIAMAVkIZyDeToz5ad-emse5g5l2sMluTDU,17953
94
94
  euporie/core/widgets/__init__.py,sha256=ektso_Ea1-pZB04alUujQsa2r-kv8xAF0SmQOv_bvHQ,52
95
- euporie/core/widgets/cell.py,sha256=PMx_XHhnIXuqd0BcVmZPIDt1SaLlpnbTJuiB5-3yK64,35650
96
- euporie/core/widgets/cell_outputs.py,sha256=bVDbnf8KvZIsmUojopOn2iXjUCwP2rHuP1Q7r2_Z5eI,17322
95
+ euporie/core/widgets/cell.py,sha256=MucSmfWmlNaN4hoEsLnwFo5MVo3qrLShtR9AVfUZJlo,36040
96
+ euporie/core/widgets/cell_outputs.py,sha256=FSTvEYvwqNiHvj1UgbDYyQnW8pZpfdz2sKodxysqFsE,17334
97
97
  euporie/core/widgets/decor.py,sha256=GPZG7-h3MKynQIXJ_YoR6tPpYxtmrXi0uoyVCovSjBQ,8770
98
98
  euporie/core/widgets/dialog.py,sha256=Dj6CAHtHlSPmk2F30FXZZ2kYrEFYGkPAKzbWHUfaw-c,32675
99
- euporie/core/widgets/display.py,sha256=iGq5UTE_q3TS9fg1IIZ7dHDMyCMCDPqj0ue5kEwH4dw,21558
99
+ euporie/core/widgets/display.py,sha256=UicJq9iSzHrrYXdXTXWPvBsC69ttZU8GHJ7xRK5Fbmo,21767
100
100
  euporie/core/widgets/file_browser.py,sha256=SmoQnngytD8-NgiRCKNLeFSdrqSCWRUUaGu8Tq295TQ,20731
101
101
  euporie/core/widgets/formatted_text_area.py,sha256=J7P_TNXPZKZkiBdjAVkPvkfRY1veXe4tzXU241k4b-Q,4037
102
102
  euporie/core/widgets/forms.py,sha256=iZj09v9I91ByuwUpGvf1FZrH2LF8FDIE-2MuAGIbLeA,85459
103
- euporie/core/widgets/inputs.py,sha256=BEYyP8SIH7h5yLkzY8NZExcc8-yL9z1feOCS_o1Q_78,21476
103
+ euporie/core/widgets/inputs.py,sha256=U0hdE8PkucHhwBuzDrU-iTjaJKeC98pydKBH1ViUkgo,21509
104
104
  euporie/core/widgets/layout.py,sha256=GAmWCUIhyhFvk0RBx8L7VeQpudKaFgkoyTk1tRKDJ94,23490
105
- euporie/core/widgets/menu.py,sha256=r5fdg63V3_VbZsd4EsAp2OBwaaGNnJemVFju8VyvM6E,32916
106
- euporie/core/widgets/pager.py,sha256=NX4cXP6T0dPVCcjeO7w4ebf-mdmjq-69Qc9I0I0AUdc,6446
105
+ euporie/core/widgets/menu.py,sha256=K5Zkv_wKs7-2ZgMDygLDTKj5Vkh7MPLwc9ZWIhXa_xM,32856
106
+ euporie/core/widgets/pager.py,sha256=mbqB2-mX8bORUKM5492s5K9rpoGrvrBN1qY7uYE9o0g,6527
107
107
  euporie/core/widgets/palette.py,sha256=e15dnPAg7VYjjahae-XsWViCjUA63jR5fdS3Oyof3MY,10847
108
- euporie/core/widgets/search.py,sha256=vz747icOZtwcEL1nH7p1hvrRtd-0W4LtSvXddO3y-hU,8227
108
+ euporie/core/widgets/search.py,sha256=szGAC19LYhAnMvQGLRWrjPoUjQa9Q3nhyugQ3uFshaU,11816
109
109
  euporie/core/widgets/status.py,sha256=WnS9PzlfPyyVXLTaswLHV1tgbvmYCSWqs1re-DOTxXQ,5558
110
110
  euporie/core/widgets/tree.py,sha256=BG7QRsI2GzSJPReNQu9zWFjO0iy8TeJwrmCmP5dc2ek,3760
111
111
  euporie/data/desktop/euporie-console.desktop,sha256=DI08G0Dl2s5asM6afWUfkKvO5YmcBm-pWQZzUHiNnqc,153
@@ -123,10 +123,10 @@ euporie/notebook/filters.py,sha256=c2PncF-n4dE67cFmVmhsMhFVWCfxePAaCW6mb6zm0Yo,1
123
123
  euporie/notebook/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
124
124
  euporie/notebook/tabs/__init__.py,sha256=CnHrMzmr4tKEd8Rs8swFvNDWj0fCiLChdqha0AtDNqY,417
125
125
  euporie/notebook/tabs/display.py,sha256=y8wMa-MsQZky_JgJEwUtMUGIbLnFiAZMfrX1u-km6VM,2595
126
- euporie/notebook/tabs/edit.py,sha256=_X7pnBLyXTAziblT4F_behiB4awE_GOR7bB-cRTHrKw,6458
126
+ euporie/notebook/tabs/edit.py,sha256=Hop0AIvIaXJ9SaWtgxpocjGN0JEcsNuPkBsZPcJ76UE,6529
127
127
  euporie/notebook/tabs/json.py,sha256=xKDHYkA59aleBpm6qLuic5GNPNQU6qApLuIT1vZzJrg,2179
128
128
  euporie/notebook/tabs/log.py,sha256=VJQRC_4SB4aZ4tVELm6ldkFCON_A_rqxBfi-sZzGBOw,3550
129
- euporie/notebook/tabs/notebook.py,sha256=gg-lLlVWZOivJTD6JMFaw73ca307mkqYe7ALZKdkKsw,43052
129
+ euporie/notebook/tabs/notebook.py,sha256=oIWfzPLhQZIvaStB1Nh2UXhko4GTW39xmRH0UYe39NA,43002
130
130
  euporie/notebook/widgets/__init__.py,sha256=74tOl0bYuWkaKT-4pgD5zmdiIkoFYx8uGj4SxcLHLtQ,48
131
131
  euporie/notebook/widgets/side_bar.py,sha256=EDFAulshV7YqA50w84RRl88UI60AehIXYYA2exlFh0M,7243
132
132
  euporie/preview/__init__.py,sha256=B6RsBuuT0JJk1v6U5n95VuFXcOeFLq6UGgt4w-n_nEI,51
@@ -136,11 +136,11 @@ euporie/preview/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
136
136
  euporie/preview/tabs/__init__.py,sha256=rY6DfQ-8qRo_AiICCD-zTyhJBnuMjawWDd8tRxR6q58,43
137
137
  euporie/preview/tabs/notebook.py,sha256=IHY-cAWo5UmehmXpSSLyuTJvjN9jBeD5BsSoa86zrUw,8504
138
138
  euporie/web/tabs/web.py,sha256=6wTB7ZLRqG79eQwKwsPIw03gHDUalmMVROSS9Z9j3YM,5228
139
- euporie/web/widgets/webview.py,sha256=jjJhZfujzyPCCz6t7bfgdPQ3ud20-1YhT-Lod5x-rrI,20762
140
- euporie-2.8.0.data/data/share/applications/euporie-console.desktop,sha256=DI08G0Dl2s5asM6afWUfkKvO5YmcBm-pWQZzUHiNnqc,153
141
- euporie-2.8.0.data/data/share/applications/euporie-notebook.desktop,sha256=RtpJzvizTDuOp0BLa2bLgVHx11LG6L7PL-oF-wHTsgU,155
142
- euporie-2.8.0.dist-info/METADATA,sha256=erYYRif6Bjt-WPHUGQagcXY5eSzCxI96ISkAiRwpy5c,6583
143
- euporie-2.8.0.dist-info/WHEEL,sha256=TJPnKdtrSue7xZ_AVGkp9YXcvDrobsjBds1du3Nx6dc,87
144
- euporie-2.8.0.dist-info/entry_points.txt,sha256=iHdjwf9iCAipy7w3tXCH2W_SavHVCSHpJk_84--b7rE,776
145
- euporie-2.8.0.dist-info/licenses/LICENSE,sha256=rI0bfSsCfCVw6d8vk7WokRxd3t8yx5xV4fC5fwaMgCg,1079
146
- euporie-2.8.0.dist-info/RECORD,,
139
+ euporie/web/widgets/webview.py,sha256=PA8hWqJs8FLMEFNAHYKz4Zt2fJTg-BSyuCji6isxz3o,20800
140
+ euporie-2.8.2.data/data/share/applications/euporie-console.desktop,sha256=DI08G0Dl2s5asM6afWUfkKvO5YmcBm-pWQZzUHiNnqc,153
141
+ euporie-2.8.2.data/data/share/applications/euporie-notebook.desktop,sha256=RtpJzvizTDuOp0BLa2bLgVHx11LG6L7PL-oF-wHTsgU,155
142
+ euporie-2.8.2.dist-info/METADATA,sha256=NdFolI9f2fVf0wavBferKZQtwhPIWylVIn5h8MB1wZ4,6417
143
+ euporie-2.8.2.dist-info/WHEEL,sha256=zEMcRr9Kr03x1ozGwg5v9NQBKn3kndp6LSoSlVg-jhU,87
144
+ euporie-2.8.2.dist-info/entry_points.txt,sha256=iHdjwf9iCAipy7w3tXCH2W_SavHVCSHpJk_84--b7rE,776
145
+ euporie-2.8.2.dist-info/licenses/LICENSE,sha256=rI0bfSsCfCVw6d8vk7WokRxd3t8yx5xV4fC5fwaMgCg,1079
146
+ euporie-2.8.2.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.21.1
2
+ Generator: hatchling 1.24.2
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any