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.
- euporie/console/tabs/console.py +2 -1
- euporie/core/__init__.py +1 -9
- euporie/core/__main__.py +9 -0
- euporie/core/convert/datum.py +14 -9
- euporie/core/convert/formats/ansi.py +37 -7
- euporie/core/convert/utils.py +9 -19
- euporie/core/filters.py +5 -52
- euporie/core/format.py +13 -2
- euporie/core/ft/html.py +22 -11
- euporie/core/graphics.py +4 -5
- euporie/core/key_binding/bindings/micro.py +4 -1
- euporie/core/keys.py +1 -0
- euporie/core/launch.py +7 -2
- euporie/core/layout/scroll.py +7 -6
- euporie/core/widgets/cell.py +8 -0
- euporie/core/widgets/cell_outputs.py +1 -1
- euporie/core/widgets/display.py +18 -12
- euporie/core/widgets/inputs.py +3 -2
- euporie/core/widgets/menu.py +4 -4
- euporie/core/widgets/pager.py +3 -1
- euporie/core/widgets/search.py +135 -42
- euporie/notebook/tabs/edit.py +4 -1
- euporie/notebook/tabs/notebook.py +6 -12
- euporie/web/widgets/webview.py +2 -1
- {euporie-2.8.0.dist-info → euporie-2.8.2.dist-info}/METADATA +3 -7
- {euporie-2.8.0.dist-info → euporie-2.8.2.dist-info}/RECORD +31 -31
- {euporie-2.8.0.dist-info → euporie-2.8.2.dist-info}/WHEEL +1 -1
- {euporie-2.8.0.data → euporie-2.8.2.data}/data/share/applications/euporie-console.desktop +0 -0
- {euporie-2.8.0.data → euporie-2.8.2.data}/data/share/applications/euporie-notebook.desktop +0 -0
- {euporie-2.8.0.dist-info → euporie-2.8.2.dist-info}/entry_points.txt +0 -0
- {euporie-2.8.0.dist-info → euporie-2.8.2.dist-info}/licenses/LICENSE +0 -0
euporie/console/tabs/console.py
CHANGED
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.
|
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")
|
euporie/core/convert/datum.py
CHANGED
@@ -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
|
-
|
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
|
-
)
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
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_=
|
172
|
+
filter_=command_exists("utftex"),
|
173
|
+
weight=0,
|
173
174
|
)
|
174
|
-
async def
|
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
|
-
"""
|
183
|
-
|
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
|
|
euporie/core/convert/utils.py
CHANGED
@@ -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
|
64
|
+
except FileNotFoundError as error:
|
65
65
|
log.error("Could not run external command `%s`", cmd)
|
66
|
-
error
|
67
|
-
except subprocess.CalledProcessError as
|
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
|
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
|
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,
|
70
|
+
output, error = proc.communicate(text)
|
71
71
|
except Exception:
|
72
72
|
return text
|
73
73
|
else:
|
74
|
-
|
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
|
-
|
1774
|
+
transform = self.theme["text_transform"]
|
1775
|
+
if "uppercase" in transform:
|
1765
1776
|
return value.upper()
|
1766
|
-
elif "lowercase" in
|
1777
|
+
elif "lowercase" in transform:
|
1767
1778
|
return value.lower()
|
1768
|
-
elif "capitalize" in
|
1779
|
+
elif "capitalize" in transform:
|
1769
1780
|
return value.capitalize()
|
1770
|
-
elif "
|
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
|
-
|
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
|
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 = ~
|
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
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
|
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.
|
euporie/core/layout/scroll.py
CHANGED
@@ -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
|
-
|
604
|
-
|
605
|
-
|
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
|
-
|
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
|
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."""
|
euporie/core/widgets/cell.py
CHANGED
@@ -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:
|
euporie/core/widgets/display.py
CHANGED
@@ -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
|
-
|
245
|
+
height = ceil(min(width, max_cols) * aspect)
|
247
246
|
cp = self.color_palette
|
248
247
|
self.lines = self._line_cache[
|
249
|
-
self.datum,
|
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
|
-
|
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
|
-
|
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
|
]
|
euporie/core/widgets/inputs.py
CHANGED
@@ -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 =
|
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(
|
euporie/core/widgets/menu.py
CHANGED
@@ -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
|
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
|
-
|
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 = []
|
euporie/core/widgets/pager.py
CHANGED
@@ -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
|
),
|
euporie/core/widgets/search.py
CHANGED
@@ -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
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
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
|
-
|
85
|
-
if
|
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
|
-
|
88
|
-
|
89
|
-
|
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 =
|
92
|
-
|
93
|
-
|
94
|
-
|
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
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
search_buffer_control
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
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
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
)
|
164
|
-
|
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()
|
euporie/notebook/tabs/edit.py
CHANGED
@@ -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(
|
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
|
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
|
256
|
-
|
257
|
-
return
|
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
|
euporie/web/widgets/webview.py
CHANGED
@@ -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
|
+
Metadata-Version: 2.3
|
2
2
|
Name: euporie
|
3
|
-
Version: 2.8.
|
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
|
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=
|
7
|
-
euporie/core/__init__.py,sha256=
|
8
|
-
euporie/core/__main__.py,sha256=
|
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=
|
19
|
-
euporie/core/format.py,sha256=
|
20
|
-
euporie/core/graphics.py,sha256=
|
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=
|
26
|
-
euporie/core/launch.py,sha256=
|
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=
|
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=
|
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=
|
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=
|
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=
|
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=
|
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=
|
96
|
-
euporie/core/widgets/cell_outputs.py,sha256=
|
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=
|
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=
|
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=
|
106
|
-
euporie/core/widgets/pager.py,sha256=
|
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=
|
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=
|
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=
|
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=
|
140
|
-
euporie-2.8.
|
141
|
-
euporie-2.8.
|
142
|
-
euporie-2.8.
|
143
|
-
euporie-2.8.
|
144
|
-
euporie-2.8.
|
145
|
-
euporie-2.8.
|
146
|
-
euporie-2.8.
|
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,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|