euporie 2.3.2__py3-none-any.whl → 2.4.1__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/__main__.py +3 -1
- euporie/console/app.py +6 -4
- euporie/console/tabs/console.py +34 -9
- euporie/core/__init__.py +6 -1
- euporie/core/__main__.py +1 -1
- euporie/core/app.py +79 -109
- euporie/core/border.py +44 -14
- euporie/core/comm/base.py +5 -4
- euporie/core/comm/ipywidgets.py +11 -11
- euporie/core/comm/registry.py +12 -6
- euporie/core/commands.py +30 -23
- euporie/core/completion.py +1 -4
- euporie/core/config.py +15 -5
- euporie/core/convert/{base.py → core.py} +117 -53
- euporie/core/convert/formats/ansi.py +46 -25
- euporie/core/convert/formats/base64.py +3 -3
- euporie/core/convert/formats/common.py +38 -13
- euporie/core/convert/formats/formatted_text.py +54 -12
- euporie/core/convert/formats/html.py +5 -5
- euporie/core/convert/formats/jpeg.py +1 -1
- euporie/core/convert/formats/markdown.py +4 -4
- euporie/core/convert/formats/pdf.py +1 -1
- euporie/core/convert/formats/pil.py +5 -3
- euporie/core/convert/formats/png.py +7 -6
- euporie/core/convert/formats/rich.py +4 -3
- euporie/core/convert/formats/sixel.py +5 -5
- euporie/core/convert/utils.py +1 -1
- euporie/core/current.py +11 -5
- euporie/core/formatted_text/ansi.py +4 -8
- euporie/core/formatted_text/html.py +1630 -856
- euporie/core/formatted_text/markdown.py +177 -166
- euporie/core/formatted_text/table.py +20 -14
- euporie/core/formatted_text/utils.py +21 -10
- euporie/core/io.py +14 -14
- euporie/core/kernel.py +48 -37
- euporie/core/key_binding/bindings/micro.py +5 -1
- euporie/core/key_binding/bindings/mouse.py +2 -2
- euporie/core/keys.py +3 -0
- euporie/core/launch.py +5 -2
- euporie/core/lexers.py +13 -2
- euporie/core/log.py +135 -139
- euporie/core/margins.py +32 -14
- euporie/core/path.py +273 -0
- euporie/core/processors.py +35 -0
- euporie/core/renderer.py +21 -5
- euporie/core/style.py +34 -19
- euporie/core/tabs/base.py +101 -17
- euporie/core/tabs/notebook.py +72 -30
- euporie/core/terminal.py +56 -48
- euporie/core/utils.py +12 -16
- euporie/core/widgets/cell.py +6 -5
- euporie/core/widgets/cell_outputs.py +2 -2
- euporie/core/widgets/decor.py +74 -82
- euporie/core/widgets/dialog.py +132 -28
- euporie/core/widgets/display.py +76 -24
- euporie/core/widgets/file_browser.py +87 -31
- euporie/core/widgets/formatted_text_area.py +1 -3
- euporie/core/widgets/forms.py +79 -40
- euporie/core/widgets/inputs.py +23 -13
- euporie/core/widgets/layout.py +4 -3
- euporie/core/widgets/menu.py +368 -216
- euporie/core/widgets/page.py +99 -58
- euporie/core/widgets/pager.py +1 -1
- euporie/core/widgets/palette.py +30 -27
- euporie/core/widgets/search_bar.py +38 -25
- euporie/core/widgets/status_bar.py +103 -5
- euporie/data/desktop/euporie-console.desktop +7 -0
- euporie/data/desktop/euporie-notebook.desktop +7 -0
- euporie/hub/__main__.py +3 -1
- euporie/hub/app.py +9 -7
- euporie/notebook/__main__.py +3 -1
- euporie/notebook/app.py +7 -30
- euporie/notebook/tabs/__init__.py +7 -3
- euporie/notebook/tabs/display.py +18 -9
- euporie/notebook/tabs/edit.py +106 -23
- euporie/notebook/tabs/json.py +73 -0
- euporie/notebook/tabs/log.py +18 -8
- euporie/notebook/tabs/notebook.py +60 -41
- euporie/preview/__main__.py +3 -1
- euporie/preview/app.py +2 -1
- euporie/preview/tabs/notebook.py +23 -10
- euporie/web/tabs/web.py +149 -0
- euporie/web/widgets/webview.py +563 -0
- euporie-2.4.1.data/data/share/applications/euporie-console.desktop +7 -0
- euporie-2.4.1.data/data/share/applications/euporie-notebook.desktop +7 -0
- {euporie-2.3.2.dist-info → euporie-2.4.1.dist-info}/METADATA +6 -5
- euporie-2.4.1.dist-info/RECORD +129 -0
- {euporie-2.3.2.dist-info → euporie-2.4.1.dist-info}/WHEEL +1 -1
- euporie/core/url.py +0 -64
- euporie-2.3.2.dist-info/RECORD +0 -122
- {euporie-2.3.2.dist-info → euporie-2.4.1.dist-info}/entry_points.txt +0 -0
- {euporie-2.3.2.dist-info → euporie-2.4.1.dist-info}/licenses/LICENSE +0 -0
euporie/core/margins.py
CHANGED
@@ -35,7 +35,7 @@ if TYPE_CHECKING:
|
|
35
35
|
class ScrollableContainer(Protocol):
|
36
36
|
"""Protocol for a scrollable container."""
|
37
37
|
|
38
|
-
render_info: WindowRenderInfo
|
38
|
+
render_info: WindowRenderInfo | None
|
39
39
|
vertical_scroll: int
|
40
40
|
|
41
41
|
|
@@ -65,7 +65,7 @@ class MarginContainer(Window):
|
|
65
65
|
def create_fragments(self) -> StyleAndTextTuples:
|
66
66
|
"""Generate text fragments to display."""
|
67
67
|
return self.margin.create_margin(
|
68
|
-
self.target.render_info,
|
68
|
+
cast("WindowRenderInfo", self.target.render_info), # Minor type hack
|
69
69
|
self.write_position.width,
|
70
70
|
self.write_position.height,
|
71
71
|
)
|
@@ -75,7 +75,13 @@ class MarginContainer(Window):
|
|
75
75
|
|
76
76
|
def preferred_width(self, max_available_width: int) -> Dimension:
|
77
77
|
"""Return a the desired width for this container."""
|
78
|
-
|
78
|
+
|
79
|
+
def _get_ui_content() -> UIContent:
|
80
|
+
render_info = self.target.render_info
|
81
|
+
assert render_info is not None
|
82
|
+
return render_info.ui_content
|
83
|
+
|
84
|
+
width = self.margin.get_width(_get_ui_content)
|
79
85
|
return Dimension(min=width, max=width)
|
80
86
|
|
81
87
|
def preferred_height(self, width: int, max_available_height: int) -> Dimension:
|
@@ -186,7 +192,7 @@ class ScrollbarMargin(ClickableMargin):
|
|
186
192
|
display_arrows: FilterOrBool = True,
|
187
193
|
up_arrow_symbol: str = "▴",
|
188
194
|
down_arrow_symbol: str = "▾",
|
189
|
-
autohide: FilterOrBool =
|
195
|
+
autohide: FilterOrBool = False,
|
190
196
|
smooth: bool = True,
|
191
197
|
style: str = "",
|
192
198
|
) -> None:
|
@@ -214,7 +220,7 @@ class ScrollbarMargin(ClickableMargin):
|
|
214
220
|
|
215
221
|
def create_margin(
|
216
222
|
self,
|
217
|
-
window_render_info: WindowRenderInfo,
|
223
|
+
window_render_info: WindowRenderInfo | None,
|
218
224
|
width: int,
|
219
225
|
height: int,
|
220
226
|
margin_render_info: WindowRenderInfo | None = None,
|
@@ -225,22 +231,31 @@ class ScrollbarMargin(ClickableMargin):
|
|
225
231
|
self.window_render_info = window_render_info
|
226
232
|
self.margin_render_info = margin_render_info
|
227
233
|
|
228
|
-
|
234
|
+
# If this is the first time the target is being drawn, it may not yet have a
|
235
|
+
# render_info yet. Thus, invalidate the app so we can immediately redraw the
|
236
|
+
# scroll-bar with the render_info
|
237
|
+
if window_render_info is None:
|
238
|
+
get_app().invalidate()
|
239
|
+
|
240
|
+
if not width:
|
229
241
|
return result
|
230
242
|
|
231
243
|
# Show we render the arrow buttons?
|
232
244
|
display_arrows = self.display_arrows()
|
233
245
|
|
234
246
|
# The height of the scrollbar, excluding the optional buttons
|
235
|
-
self.track_height =
|
247
|
+
self.track_height = (
|
248
|
+
window_render_info.window_height if window_render_info else height
|
249
|
+
)
|
236
250
|
if display_arrows:
|
237
251
|
self.track_height -= 2
|
238
252
|
|
239
253
|
# Height of all text in the output: If there is none, we cannot divide
|
240
254
|
# by zero so we hide the thumb
|
241
|
-
|
242
|
-
|
243
|
-
window_render_info.
|
255
|
+
if (
|
256
|
+
window_render_info is None
|
257
|
+
or (content_height := window_render_info.content_height) == 0
|
258
|
+
or content_height <= len(window_render_info.displayed_lines)
|
244
259
|
):
|
245
260
|
self.thumb_size = 0
|
246
261
|
else:
|
@@ -259,7 +274,9 @@ class ScrollbarMargin(ClickableMargin):
|
|
259
274
|
self.thumb_size = int(self.thumb_size)
|
260
275
|
|
261
276
|
# Calculate the position of the thumb
|
262
|
-
if content_height <= len(
|
277
|
+
if window_render_info is None or content_height <= len(
|
278
|
+
window_render_info.displayed_lines
|
279
|
+
):
|
263
280
|
fraction_above = 0.0
|
264
281
|
else:
|
265
282
|
fraction_above = window_render_info.vertical_scroll / (
|
@@ -381,7 +398,8 @@ class ScrollbarMargin(ClickableMargin):
|
|
381
398
|
|
382
399
|
"""
|
383
400
|
render_info = self.window_render_info
|
384
|
-
|
401
|
+
if render_info is None:
|
402
|
+
return NotImplemented
|
385
403
|
|
386
404
|
content_height = render_info.content_height
|
387
405
|
if isinstance(mouse_event, MouseEvent):
|
@@ -410,9 +428,9 @@ class ScrollbarMargin(ClickableMargin):
|
|
410
428
|
|
411
429
|
if isinstance(window, Window):
|
412
430
|
if delta < 0:
|
413
|
-
func = window._scroll_up
|
414
|
-
else:
|
415
431
|
func = window._scroll_down
|
432
|
+
else:
|
433
|
+
func = window._scroll_up
|
416
434
|
for _ in range(abs(delta)):
|
417
435
|
func()
|
418
436
|
elif hasattr(window, "scrolling"):
|
euporie/core/path.py
ADDED
@@ -0,0 +1,273 @@
|
|
1
|
+
"""Responible for loading data from urls."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
import base64
|
6
|
+
import binascii
|
7
|
+
import io
|
8
|
+
import logging
|
9
|
+
from pathlib import Path
|
10
|
+
from typing import TYPE_CHECKING, overload
|
11
|
+
from urllib.parse import unquote, urljoin, urlunsplit
|
12
|
+
|
13
|
+
import upath
|
14
|
+
from aiohttp.client_reqrep import ClientResponse
|
15
|
+
from fsspec.implementations.cached import WholeFileCacheFileSystem
|
16
|
+
from fsspec.implementations.http import HTTPFileSystem as FsHTTPFileSystem
|
17
|
+
from fsspec.registry import register_implementation as fs_register_implementation
|
18
|
+
from upath import UPath
|
19
|
+
from upath.implementations.http import HTTPPath as _HTTPPath
|
20
|
+
from upath.implementations.http import _HTTPAccessor
|
21
|
+
from upath.registry import _registry
|
22
|
+
|
23
|
+
if TYPE_CHECKING:
|
24
|
+
from io import BufferedRandom, BufferedReader, BufferedWriter, FileIO, TextIOWrapper
|
25
|
+
from os import PathLike
|
26
|
+
from typing import IO, Any, BinaryIO, Literal
|
27
|
+
from urllib.parse import SplitResult
|
28
|
+
|
29
|
+
from _typeshed import (
|
30
|
+
OpenBinaryMode,
|
31
|
+
OpenBinaryModeReading,
|
32
|
+
OpenBinaryModeUpdating,
|
33
|
+
OpenBinaryModeWriting,
|
34
|
+
OpenTextMode,
|
35
|
+
)
|
36
|
+
from upath.core import PT
|
37
|
+
|
38
|
+
|
39
|
+
log = logging.getLogger(__name__)
|
40
|
+
|
41
|
+
|
42
|
+
def parse_path(path: str | PathLike) -> Path:
|
43
|
+
"""Parse and resolve a path."""
|
44
|
+
if not isinstance(path, Path):
|
45
|
+
path = UPath(path)
|
46
|
+
try:
|
47
|
+
path = path.expanduser()
|
48
|
+
except NotImplementedError:
|
49
|
+
pass
|
50
|
+
try:
|
51
|
+
path = path.resolve()
|
52
|
+
except (AttributeError, NotImplementedError):
|
53
|
+
pass
|
54
|
+
return path
|
55
|
+
|
56
|
+
|
57
|
+
def _raise_for_status(self: ClientResponse) -> None:
|
58
|
+
"""Monkey-patch :py:class:`aiohttp.ClientResponse` not to raise for any status."""
|
59
|
+
pass
|
60
|
+
|
61
|
+
|
62
|
+
setattr(ClientResponse, "raise_for_status", _raise_for_status) # noqa B010
|
63
|
+
|
64
|
+
|
65
|
+
class DataPath(upath.core.UPath):
|
66
|
+
"""A :py:class:`pathlib` compatible class for reading data URIs."""
|
67
|
+
|
68
|
+
@overload
|
69
|
+
def open(
|
70
|
+
self,
|
71
|
+
mode: OpenTextMode = "r",
|
72
|
+
buffering: int = -1,
|
73
|
+
encoding: str | None = None,
|
74
|
+
errors: str | None = None,
|
75
|
+
newline: str | None = None,
|
76
|
+
) -> TextIOWrapper:
|
77
|
+
...
|
78
|
+
|
79
|
+
# Unbuffered binary mode: returns a FileIO
|
80
|
+
@overload
|
81
|
+
def open(
|
82
|
+
self,
|
83
|
+
mode: OpenBinaryMode,
|
84
|
+
buffering: Literal[0],
|
85
|
+
encoding: None = None,
|
86
|
+
errors: None = None,
|
87
|
+
newline: None = None,
|
88
|
+
) -> FileIO:
|
89
|
+
...
|
90
|
+
|
91
|
+
# Buffering is on: return BufferedRandom, BufferedReader, or BufferedWriter
|
92
|
+
@overload
|
93
|
+
def open(
|
94
|
+
self,
|
95
|
+
mode: OpenBinaryModeUpdating,
|
96
|
+
buffering: Literal[-1, 1] = -1,
|
97
|
+
encoding: None = None,
|
98
|
+
errors: None = None,
|
99
|
+
newline: None = None,
|
100
|
+
) -> BufferedRandom:
|
101
|
+
...
|
102
|
+
|
103
|
+
@overload
|
104
|
+
def open(
|
105
|
+
self,
|
106
|
+
mode: OpenBinaryModeWriting,
|
107
|
+
buffering: Literal[-1, 1] = -1,
|
108
|
+
encoding: None = None,
|
109
|
+
errors: None = None,
|
110
|
+
newline: None = None,
|
111
|
+
) -> BufferedWriter:
|
112
|
+
...
|
113
|
+
|
114
|
+
@overload
|
115
|
+
def open(
|
116
|
+
self,
|
117
|
+
mode: OpenBinaryModeReading,
|
118
|
+
buffering: Literal[-1, 1] = -1,
|
119
|
+
encoding: None = None,
|
120
|
+
errors: None = None,
|
121
|
+
newline: None = None,
|
122
|
+
) -> BufferedReader:
|
123
|
+
...
|
124
|
+
|
125
|
+
# Buffering cannot be determined: fall back to BinaryIO
|
126
|
+
@overload
|
127
|
+
def open(
|
128
|
+
self,
|
129
|
+
mode: OpenBinaryMode,
|
130
|
+
buffering: int = -1,
|
131
|
+
encoding: None = None,
|
132
|
+
errors: None = None,
|
133
|
+
newline: None = None,
|
134
|
+
) -> BinaryIO:
|
135
|
+
...
|
136
|
+
|
137
|
+
# Fallback if mode is not specified
|
138
|
+
@overload
|
139
|
+
def open(
|
140
|
+
self,
|
141
|
+
mode: str,
|
142
|
+
buffering: int = -1,
|
143
|
+
encoding: str | None = None,
|
144
|
+
errors: str | None = None,
|
145
|
+
newline: str | None = None,
|
146
|
+
) -> IO[Any]:
|
147
|
+
...
|
148
|
+
|
149
|
+
def open(
|
150
|
+
self,
|
151
|
+
mode: OpenTextMode
|
152
|
+
| OpenBinaryMode
|
153
|
+
| OpenBinaryModeReading
|
154
|
+
| OpenBinaryModeWriting
|
155
|
+
| OpenBinaryModeUpdating
|
156
|
+
| str = "r",
|
157
|
+
buffering: Literal[-1, 0, 1] | int = -1,
|
158
|
+
encoding: str | None = None,
|
159
|
+
errors: str | None = None,
|
160
|
+
newline: str | None = None,
|
161
|
+
) -> IO[Any]:
|
162
|
+
"""Return an io object for the data in the URI."""
|
163
|
+
assert self._url is not None
|
164
|
+
data_format, _, encoded_data = self._url.path.partition(",")
|
165
|
+
_mime, *params = data_format.split(";")
|
166
|
+
|
167
|
+
data_bytes = None
|
168
|
+
data_str = None
|
169
|
+
|
170
|
+
if "base64" in params:
|
171
|
+
try:
|
172
|
+
data_bytes = base64.b64decode(encoded_data)
|
173
|
+
except binascii.Error:
|
174
|
+
log.warning("Failed to decode base64 encoded data")
|
175
|
+
data_bytes = b""
|
176
|
+
else:
|
177
|
+
data_str = unquote(encoded_data)
|
178
|
+
|
179
|
+
if "b" in mode:
|
180
|
+
if data_bytes is None:
|
181
|
+
assert data_str is not None
|
182
|
+
data_bytes = data_str.encode()
|
183
|
+
return io.BytesIO(data_bytes)
|
184
|
+
else:
|
185
|
+
if data_str is None:
|
186
|
+
assert data_bytes is not None
|
187
|
+
decode_kwargs: dict[str, str] = {}
|
188
|
+
if encoding is not None: #:= kwargs.get("encoding"):
|
189
|
+
decode_kwargs["encoding"] = str(encoding)
|
190
|
+
data_str = data_bytes.decode(**decode_kwargs)
|
191
|
+
return io.StringIO(data_str)
|
192
|
+
|
193
|
+
def exists(self, **kwargs: Any) -> bool:
|
194
|
+
"""Affirm tat data URIs always exist."""
|
195
|
+
return True
|
196
|
+
|
197
|
+
@property
|
198
|
+
def _mime(self) -> str:
|
199
|
+
"""Return the media type of the data URI."""
|
200
|
+
assert self._url is not None
|
201
|
+
data_format, _, _encoded_data = self._url.path.partition(",")
|
202
|
+
mime, *params = data_format.split(";")
|
203
|
+
return mime
|
204
|
+
|
205
|
+
|
206
|
+
_registry.known_implementations["data"] = "euporie.core.path.DataPath"
|
207
|
+
|
208
|
+
|
209
|
+
class CachingHTTPAccessor(_HTTPAccessor):
|
210
|
+
"""A :py:module:`universal_pathlib` accessor which caches content."""
|
211
|
+
|
212
|
+
def __init__(self, parsed_url: SplitResult | None, **kwargs: Any) -> None:
|
213
|
+
"""Load a caching filesystem."""
|
214
|
+
cls = WholeFileCacheFileSystem
|
215
|
+
assert parsed_url is not None
|
216
|
+
url_kwargs = cls._get_kwargs_from_urls(urlunsplit(parsed_url))
|
217
|
+
url_kwargs.update(kwargs)
|
218
|
+
url_kwargs.setdefault("target_protocol", "http")
|
219
|
+
# url_kwargs.setdefault(
|
220
|
+
# "cache_storage", str(Path(user_cache_dir("euporie")) / "web")
|
221
|
+
# )
|
222
|
+
self._fs = cls(**url_kwargs)
|
223
|
+
|
224
|
+
def _format_path(self, path: upath.UPath) -> str:
|
225
|
+
if (url := path._url) is not None:
|
226
|
+
return url.geturl()
|
227
|
+
return super()._format_path(path)
|
228
|
+
|
229
|
+
|
230
|
+
class HTTPPath(_HTTPPath):
|
231
|
+
"""An HTTP path which caches content."""
|
232
|
+
|
233
|
+
_hash: int
|
234
|
+
_default_accessor = CachingHTTPAccessor
|
235
|
+
|
236
|
+
def __truediv__(self: PT, key: str | PathLike) -> PT:
|
237
|
+
"""Join a path to a HTTP URI."""
|
238
|
+
return self.__class__(urljoin(str(self), str(key)))
|
239
|
+
|
240
|
+
def __str__(self) -> str:
|
241
|
+
"""Represent the path as a string."""
|
242
|
+
try:
|
243
|
+
return self._str
|
244
|
+
except AttributeError:
|
245
|
+
if url := self._url:
|
246
|
+
self._str = url.geturl()
|
247
|
+
else:
|
248
|
+
return super().__str__()
|
249
|
+
return self._str
|
250
|
+
|
251
|
+
def __hash__(self) -> int:
|
252
|
+
"""Provide a unique hash of the path."""
|
253
|
+
try:
|
254
|
+
return self._hash
|
255
|
+
except AttributeError:
|
256
|
+
self._hash = hash(self._url)
|
257
|
+
return self._hash
|
258
|
+
|
259
|
+
|
260
|
+
_registry.known_implementations["http"] = "euporie.core.path.HTTPPath"
|
261
|
+
_registry.known_implementations["https"] = "euporie.core.path.HTTPPath"
|
262
|
+
|
263
|
+
|
264
|
+
class HTTPFileSystem(FsHTTPFileSystem):
|
265
|
+
"""A :py:class:`HTTPFileSystem` which does not raise exceptions on 404 errors."""
|
266
|
+
|
267
|
+
def _raise_not_found_for_status(self, response: ClientResponse, url: str) -> None:
|
268
|
+
"""Do not raise an exception for 404 errors."""
|
269
|
+
pass
|
270
|
+
|
271
|
+
|
272
|
+
fs_register_implementation("http", HTTPFileSystem, clobber=True)
|
273
|
+
fs_register_implementation("https", HTTPFileSystem, clobber=True)
|
euporie/core/processors.py
CHANGED
@@ -5,14 +5,18 @@ from __future__ import annotations
|
|
5
5
|
import logging
|
6
6
|
from typing import TYPE_CHECKING
|
7
7
|
|
8
|
+
from prompt_toolkit.data_structures import Point
|
8
9
|
from prompt_toolkit.layout.processors import (
|
9
10
|
AppendAutoSuggestion,
|
10
11
|
Processor,
|
11
12
|
Transformation,
|
12
13
|
)
|
13
14
|
from prompt_toolkit.layout.utils import explode_text_fragments
|
15
|
+
from prompt_toolkit.utils import get_cwidth
|
14
16
|
|
15
17
|
if TYPE_CHECKING:
|
18
|
+
from typing import Callable
|
19
|
+
|
16
20
|
from prompt_toolkit.layout.processors import TransformationInput
|
17
21
|
|
18
22
|
|
@@ -61,3 +65,34 @@ class ShowTrailingWhiteSpaceProcessor(Processor):
|
|
61
65
|
else:
|
62
66
|
break
|
63
67
|
return Transformation(fragments)
|
68
|
+
|
69
|
+
|
70
|
+
class CursorProcessor(Processor):
|
71
|
+
"""Show a mouse cursor."""
|
72
|
+
|
73
|
+
def __init__(
|
74
|
+
self,
|
75
|
+
get_cursor_position: Callable[[], Point],
|
76
|
+
char: str = "🮰",
|
77
|
+
style: str = "class:mouse",
|
78
|
+
) -> None:
|
79
|
+
"""Create a new processor instance."""
|
80
|
+
self.char = char
|
81
|
+
self.style = style
|
82
|
+
self.get_cursor_position = get_cursor_position
|
83
|
+
|
84
|
+
def apply_transformation(self, ti: TransformationInput) -> Transformation:
|
85
|
+
"""Replace character at the cursor position."""
|
86
|
+
pos = self.get_cursor_position()
|
87
|
+
fragments = ti.fragments
|
88
|
+
if ti.lineno == pos.y:
|
89
|
+
fragments = explode_text_fragments(fragments)
|
90
|
+
if (length := len(fragments)) < (x := pos.x):
|
91
|
+
fragments.append(("", " " * (x - length)))
|
92
|
+
frag = fragments[x]
|
93
|
+
char = self.char.ljust(get_cwidth(frag[1]))
|
94
|
+
fragments[x] = (
|
95
|
+
f"{frag[0]} {self.style}",
|
96
|
+
char,
|
97
|
+
)
|
98
|
+
return Transformation(fragments)
|
euporie/core/renderer.py
CHANGED
@@ -263,6 +263,7 @@ class Renderer(PtkRenderer):
|
|
263
263
|
extend_width: FilterOrBool = False,
|
264
264
|
) -> None:
|
265
265
|
"""Create a new :py:class:`Renderer` instance."""
|
266
|
+
self.app: Application[Any] | None = None
|
266
267
|
super().__init__(
|
267
268
|
style, output, full_screen, mouse_support, cpr_not_supported_callback
|
268
269
|
)
|
@@ -271,22 +272,37 @@ class Renderer(PtkRenderer):
|
|
271
272
|
|
272
273
|
def reset(self, _scroll: bool = False, leave_alternate_screen: bool = True) -> None:
|
273
274
|
"""Disable extended keys before resetting the output."""
|
275
|
+
from euporie.core.app import BaseApp
|
276
|
+
|
277
|
+
super().reset(_scroll, leave_alternate_screen)
|
278
|
+
|
274
279
|
output = self.output
|
275
280
|
|
276
281
|
# Disable extended keys
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
282
|
+
app = self.app
|
283
|
+
if (
|
284
|
+
app
|
285
|
+
and isinstance(app, BaseApp)
|
286
|
+
and app.term_info.csiu_status.value
|
287
|
+
and isinstance(output, Vt100_Output)
|
288
|
+
):
|
289
|
+
cast("Vt100_Output", self.output).disable_extended_keys()
|
281
290
|
|
282
291
|
def render(
|
283
292
|
self, app: Application[Any], layout: Layout, is_done: bool = False
|
284
293
|
) -> None:
|
285
294
|
"""Render the current interface to the output."""
|
295
|
+
from euporie.core.app import BaseApp
|
296
|
+
|
286
297
|
output = self.output
|
298
|
+
self.app = app
|
287
299
|
|
288
300
|
# Enable extended keys
|
289
|
-
if
|
301
|
+
if (
|
302
|
+
isinstance(app, BaseApp)
|
303
|
+
and app.term_info.csiu_status.value
|
304
|
+
and isinstance(output, Vt100_Output)
|
305
|
+
):
|
290
306
|
output.enable_extended_keys()
|
291
307
|
|
292
308
|
# Enter alternate screen.
|
euporie/core/style.py
CHANGED
@@ -398,6 +398,8 @@ def build_style(
|
|
398
398
|
"app-tab-bar tab active": "bold fg:default bg:default",
|
399
399
|
"app-tab-bar tab active close": "fg:darkred",
|
400
400
|
"app-tab-bar tab active border top": f"fg:{cp.hl} bg:{cp.bg.less(0.15)}",
|
401
|
+
# Tabs
|
402
|
+
"loading": "fg:#888888",
|
401
403
|
# Buffer
|
402
404
|
"line-number": f"fg:{cp.fg.more(0.5)} bg:{cp.bg.more(0.05)}",
|
403
405
|
"line-number.current": f"bold orange bg:{cp.bg.more(0.1)}",
|
@@ -434,7 +436,7 @@ def build_style(
|
|
434
436
|
# Scrollbars
|
435
437
|
"scrollbar": f"fg:{cp.bg.more(0.75)} bg:{cp.bg.more(0.15)}",
|
436
438
|
"scrollbar.background": f"fg:{cp.bg.more(0.75)} bg:{cp.bg.more(0.15)}",
|
437
|
-
"scrollbar.arrow": f"fg:{cp.bg.more(0.75)} bg:{cp.bg.more(0.
|
439
|
+
"scrollbar.arrow": f"fg:{cp.bg.more(0.75)} bg:{cp.bg.more(0.20)}",
|
438
440
|
"scrollbar.start": "",
|
439
441
|
# "scrollbar.start": f"fg:{cp.bg.more(0.75)} bg:{cp.bg.more(0.25)}",
|
440
442
|
"scrollbar.button": f"fg:{cp.bg.more(0.75)} bg:{cp.bg.more(0.75)}",
|
@@ -442,8 +444,7 @@ def build_style(
|
|
442
444
|
# Overflow margin
|
443
445
|
"overflow": f"fg:{cp.fg.more(0.5)}",
|
444
446
|
# Dialogs
|
445
|
-
"dialog title": f"fg:white bg:{cp.hl.darker(0.25)} bold",
|
446
|
-
"dialog title border": "fg:ansired",
|
447
|
+
"dialog dialog-title": f"fg:white bg:{cp.hl.darker(0.25)} bold",
|
447
448
|
"dialog": f"fg:{cp.fg.base} bg:{cp.bg.darker(0.1)}",
|
448
449
|
"dialog text-area": f"bg:{cp.bg.lighter(0.05)}",
|
449
450
|
"dialog input text text-area": f"fg:default bg:{cp.bg.less(0.1)}",
|
@@ -452,18 +453,22 @@ def build_style(
|
|
452
453
|
# Horizontals rule
|
453
454
|
"hr": "fg:ansired",
|
454
455
|
# Completions menu
|
455
|
-
"completion-
|
456
|
-
"completion-
|
457
|
-
"completion-
|
458
|
-
"completion-
|
459
|
-
"completion-
|
460
|
-
"completion-
|
461
|
-
"completion-
|
462
|
-
"completion-
|
463
|
-
"completion-
|
464
|
-
"completion-
|
465
|
-
"completion-
|
466
|
-
"completion-
|
456
|
+
"menu completion-keyword": "fg:#d700af",
|
457
|
+
"menu completion-function": "fg:#005faf",
|
458
|
+
"menu completion-class": "fg:#008700",
|
459
|
+
"menu completion-statement": "fg:#5f0000",
|
460
|
+
"menu completion-instance": "fg:#d75f00",
|
461
|
+
"menu completion-module": "fg:#d70000",
|
462
|
+
"menu completion-magic": "fg:#888888",
|
463
|
+
"menu completion-path": "fg:#aa8800",
|
464
|
+
"menu selection completion-keyword": f"fg:{ColorPaletteColor('#d700af').lighter(0.75)}",
|
465
|
+
"menu selection completion-function": f"fg:{ColorPaletteColor('#005faf').lighter(0.75)}",
|
466
|
+
"menu selection completion-class": f"fg:{ColorPaletteColor('#008700').lighter(0.75)}",
|
467
|
+
"menu selection completion-statement": f"fg:{ColorPaletteColor('#5f0000').lighter(0.75)}",
|
468
|
+
"menu selection completion-instance": f"fg:{ColorPaletteColor('#d75f00').lighter(0.75)}",
|
469
|
+
"menu selection completion-module": f"fg:{ColorPaletteColor('#d70000').lighter(0.75)}",
|
470
|
+
"menu selection completion-magic": f"fg:{ColorPaletteColor('#888888').lighter(0.75)}",
|
471
|
+
"menu selection completion-path": f"fg:{ColorPaletteColor('#aa8800').lighter(0.75)}",
|
467
472
|
# Log
|
468
473
|
"log.level.nonset": "fg:grey",
|
469
474
|
"log.level.debug": "fg:green",
|
@@ -512,11 +517,21 @@ def build_style(
|
|
512
517
|
"side_bar buttons separator selection before": f"fg:{cp.bg.less(0.15)} bg:{cp.hl}",
|
513
518
|
"side_bar buttons separator selection after": f"fg:{cp.hl} bg:{cp.bg.less(0.15)}",
|
514
519
|
# Tabbed split
|
515
|
-
"tabbed-split
|
516
|
-
"tabbed-split
|
520
|
+
"tabbed-split border": f"fg:{cp.bg.more(0.2)}",
|
521
|
+
"tabbed-split border left": f"bg:{cp.bg.more(0.025)}",
|
522
|
+
"tabbed-split border right": f"bg:{cp.bg.more(0.025)}",
|
523
|
+
"tabbed-split border bottom left": f"bg:{cp.bg}",
|
524
|
+
"tabbed-split border bottom right": f"bg:{cp.bg}",
|
525
|
+
"tabbed-split page": f"bg:{cp.bg.more(0.025)}",
|
526
|
+
"dialog tabbed-split border bottom right": f"bg:{cp.bg.darker(0.1)}",
|
527
|
+
"dialog tabbed-split border bottom left": f"bg:{cp.bg.darker(0.1)}",
|
528
|
+
"tabbed-split tab-bar tab inactive": f"fg:{cp.bg.more(0.3)}",
|
529
|
+
"tabbed-split tab-bar tab inactive title": f"bg:{cp.bg.darker(0.05)}",
|
530
|
+
"tabbed-split tab-bar tab inactive border left": f"bg:{cp.bg.darker(0.05)}",
|
531
|
+
"tabbed-split tab-bar tab inactive border right": f"bg:{cp.bg.darker(0.05)}",
|
517
532
|
"tabbed-split tab-bar tab active": f"bold fg:{cp.fg}",
|
533
|
+
"tabbed-split tab-bar tab active title": f"bg:{cp.bg.more(0.025)}",
|
518
534
|
"tabbed-split tab-bar tab active close": "fg:darkred",
|
519
|
-
"tabbed-split border": f"fg:{cp.bg.more(0.2)}",
|
520
535
|
# Ipywidgets
|
521
536
|
"ipywidget focused": f"bg:{cp.bg.more(0.05)}",
|
522
537
|
"ipywidget slider track": f"fg:{cp.fg.darker(0.5)}",
|
@@ -611,7 +626,7 @@ def build_style(
|
|
611
626
|
"input inset border top selection focused": f"fg:{cp.hl.lighter(0.5)}",
|
612
627
|
"input inset border left selection focused": f"fg:{cp.hl.lighter(0.5)}",
|
613
628
|
"input text placeholder": f"fg:{cp.fg.more(0.6)}",
|
614
|
-
"input text text-area": f"bg:{cp.bg.lighter(0.1)}",
|
629
|
+
"input text text-area": f"fg:default bg:{cp.bg.lighter(0.1)}",
|
615
630
|
"input text border top": f"fg:{cp.bg.darker(0.5)}",
|
616
631
|
"input text border right": f"fg:{cp.bg.lighter(0.25)}",
|
617
632
|
"input text border bottom": f"fg:{cp.bg.lighter(0.25)}",
|