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.
Files changed (92) hide show
  1. euporie/console/__main__.py +3 -1
  2. euporie/console/app.py +6 -4
  3. euporie/console/tabs/console.py +34 -9
  4. euporie/core/__init__.py +6 -1
  5. euporie/core/__main__.py +1 -1
  6. euporie/core/app.py +79 -109
  7. euporie/core/border.py +44 -14
  8. euporie/core/comm/base.py +5 -4
  9. euporie/core/comm/ipywidgets.py +11 -11
  10. euporie/core/comm/registry.py +12 -6
  11. euporie/core/commands.py +30 -23
  12. euporie/core/completion.py +1 -4
  13. euporie/core/config.py +15 -5
  14. euporie/core/convert/{base.py → core.py} +117 -53
  15. euporie/core/convert/formats/ansi.py +46 -25
  16. euporie/core/convert/formats/base64.py +3 -3
  17. euporie/core/convert/formats/common.py +38 -13
  18. euporie/core/convert/formats/formatted_text.py +54 -12
  19. euporie/core/convert/formats/html.py +5 -5
  20. euporie/core/convert/formats/jpeg.py +1 -1
  21. euporie/core/convert/formats/markdown.py +4 -4
  22. euporie/core/convert/formats/pdf.py +1 -1
  23. euporie/core/convert/formats/pil.py +5 -3
  24. euporie/core/convert/formats/png.py +7 -6
  25. euporie/core/convert/formats/rich.py +4 -3
  26. euporie/core/convert/formats/sixel.py +5 -5
  27. euporie/core/convert/utils.py +1 -1
  28. euporie/core/current.py +11 -5
  29. euporie/core/formatted_text/ansi.py +4 -8
  30. euporie/core/formatted_text/html.py +1630 -856
  31. euporie/core/formatted_text/markdown.py +177 -166
  32. euporie/core/formatted_text/table.py +20 -14
  33. euporie/core/formatted_text/utils.py +21 -10
  34. euporie/core/io.py +14 -14
  35. euporie/core/kernel.py +48 -37
  36. euporie/core/key_binding/bindings/micro.py +5 -1
  37. euporie/core/key_binding/bindings/mouse.py +2 -2
  38. euporie/core/keys.py +3 -0
  39. euporie/core/launch.py +5 -2
  40. euporie/core/lexers.py +13 -2
  41. euporie/core/log.py +135 -139
  42. euporie/core/margins.py +32 -14
  43. euporie/core/path.py +273 -0
  44. euporie/core/processors.py +35 -0
  45. euporie/core/renderer.py +21 -5
  46. euporie/core/style.py +34 -19
  47. euporie/core/tabs/base.py +101 -17
  48. euporie/core/tabs/notebook.py +72 -30
  49. euporie/core/terminal.py +56 -48
  50. euporie/core/utils.py +12 -16
  51. euporie/core/widgets/cell.py +6 -5
  52. euporie/core/widgets/cell_outputs.py +2 -2
  53. euporie/core/widgets/decor.py +74 -82
  54. euporie/core/widgets/dialog.py +132 -28
  55. euporie/core/widgets/display.py +76 -24
  56. euporie/core/widgets/file_browser.py +87 -31
  57. euporie/core/widgets/formatted_text_area.py +1 -3
  58. euporie/core/widgets/forms.py +79 -40
  59. euporie/core/widgets/inputs.py +23 -13
  60. euporie/core/widgets/layout.py +4 -3
  61. euporie/core/widgets/menu.py +368 -216
  62. euporie/core/widgets/page.py +99 -58
  63. euporie/core/widgets/pager.py +1 -1
  64. euporie/core/widgets/palette.py +30 -27
  65. euporie/core/widgets/search_bar.py +38 -25
  66. euporie/core/widgets/status_bar.py +103 -5
  67. euporie/data/desktop/euporie-console.desktop +7 -0
  68. euporie/data/desktop/euporie-notebook.desktop +7 -0
  69. euporie/hub/__main__.py +3 -1
  70. euporie/hub/app.py +9 -7
  71. euporie/notebook/__main__.py +3 -1
  72. euporie/notebook/app.py +7 -30
  73. euporie/notebook/tabs/__init__.py +7 -3
  74. euporie/notebook/tabs/display.py +18 -9
  75. euporie/notebook/tabs/edit.py +106 -23
  76. euporie/notebook/tabs/json.py +73 -0
  77. euporie/notebook/tabs/log.py +18 -8
  78. euporie/notebook/tabs/notebook.py +60 -41
  79. euporie/preview/__main__.py +3 -1
  80. euporie/preview/app.py +2 -1
  81. euporie/preview/tabs/notebook.py +23 -10
  82. euporie/web/tabs/web.py +149 -0
  83. euporie/web/widgets/webview.py +563 -0
  84. euporie-2.4.1.data/data/share/applications/euporie-console.desktop +7 -0
  85. euporie-2.4.1.data/data/share/applications/euporie-notebook.desktop +7 -0
  86. {euporie-2.3.2.dist-info → euporie-2.4.1.dist-info}/METADATA +6 -5
  87. euporie-2.4.1.dist-info/RECORD +129 -0
  88. {euporie-2.3.2.dist-info → euporie-2.4.1.dist-info}/WHEEL +1 -1
  89. euporie/core/url.py +0 -64
  90. euporie-2.3.2.dist-info/RECORD +0 -122
  91. {euporie-2.3.2.dist-info → euporie-2.4.1.dist-info}/entry_points.txt +0 -0
  92. {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
- width = self.margin.get_width(lambda: self.target.render_info.ui_content)
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 = True,
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
- if window_render_info is None or not width:
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 = window_render_info.window_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
- content_height = window_render_info.content_height
242
- if content_height == 0 or content_height <= len(
243
- window_render_info.displayed_lines
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(window_render_info.displayed_lines):
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
- assert render_info is not None
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)
@@ -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
- if isinstance(output, Vt100_Output):
278
- cast(Vt100_Output, self.output).disable_extended_keys()
279
-
280
- super().reset(_scroll, leave_alternate_screen)
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 isinstance(output, Vt100_Output):
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.15)}",
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-menu.completion.keyword": "fg:#d700af",
456
- "completion-menu.completion.current.keyword": "fg:#fff bg:#d700ff",
457
- "completion-menu.completion.function": "fg:#005faf",
458
- "completion-menu.completion.current.function": "fg:#fff bg:#005fff",
459
- "completion-menu.completion.class": "fg:#008700",
460
- "completion-menu.completion.current.class": "fg:#fff bg:#00af00",
461
- "completion-menu.completion.statement": "fg:#5f0000",
462
- "completion-menu.completion.current.statement": "fg:#fff bg:#5f0000",
463
- "completion-menu.completion.instance": "fg:#d75f00",
464
- "completion-menu.completion.current.instance": "fg:#fff bg:#d78700",
465
- "completion-menu.completion.module": "fg:#d70000",
466
- "completion-menu.completion.current.module": "fg:#fff bg:#d70000",
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 tab-bar tab inactive": f"fg:{cp.bg.more(0.1)}",
516
- "tabbed-split tab-bar tab inactive border": f"fg:{cp.bg.more(0.15)}",
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)}",