euporie 2.6.2__py3-none-any.whl → 2.7.0__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 (67) hide show
  1. euporie/console/tabs/console.py +52 -38
  2. euporie/core/__init__.py +5 -2
  3. euporie/core/app.py +74 -57
  4. euporie/core/comm/ipywidgets.py +7 -3
  5. euporie/core/config.py +51 -27
  6. euporie/core/convert/__init__.py +2 -0
  7. euporie/core/convert/datum.py +82 -45
  8. euporie/core/convert/formats/ansi.py +1 -2
  9. euporie/core/convert/formats/common.py +7 -11
  10. euporie/core/convert/formats/ft.py +10 -7
  11. euporie/core/convert/formats/png.py +7 -6
  12. euporie/core/convert/formats/sixel.py +1 -1
  13. euporie/core/convert/formats/svg.py +28 -0
  14. euporie/core/convert/mime.py +4 -7
  15. euporie/core/data_structures.py +24 -22
  16. euporie/core/filters.py +16 -2
  17. euporie/core/format.py +30 -4
  18. euporie/core/ft/ansi.py +2 -1
  19. euporie/core/ft/html.py +155 -42
  20. euporie/core/{widgets/graphics.py → graphics.py} +225 -227
  21. euporie/core/io.py +8 -0
  22. euporie/core/key_binding/bindings/__init__.py +8 -2
  23. euporie/core/key_binding/bindings/basic.py +9 -14
  24. euporie/core/key_binding/bindings/micro.py +0 -12
  25. euporie/core/key_binding/bindings/mouse.py +107 -80
  26. euporie/core/key_binding/bindings/page_navigation.py +129 -0
  27. euporie/core/key_binding/key_processor.py +9 -1
  28. euporie/core/layout/__init__.py +1 -0
  29. euporie/core/layout/containers.py +1011 -0
  30. euporie/core/layout/decor.py +381 -0
  31. euporie/core/layout/print.py +130 -0
  32. euporie/core/layout/screen.py +75 -0
  33. euporie/core/{widgets/page.py → layout/scroll.py} +166 -111
  34. euporie/core/log.py +1 -1
  35. euporie/core/margins.py +11 -5
  36. euporie/core/path.py +43 -176
  37. euporie/core/renderer.py +31 -8
  38. euporie/core/style.py +2 -0
  39. euporie/core/tabs/base.py +2 -1
  40. euporie/core/terminal.py +19 -21
  41. euporie/core/widgets/cell.py +2 -4
  42. euporie/core/widgets/cell_outputs.py +2 -2
  43. euporie/core/widgets/decor.py +3 -359
  44. euporie/core/widgets/dialog.py +5 -5
  45. euporie/core/widgets/display.py +32 -12
  46. euporie/core/widgets/file_browser.py +3 -4
  47. euporie/core/widgets/forms.py +36 -14
  48. euporie/core/widgets/inputs.py +171 -99
  49. euporie/core/widgets/layout.py +80 -5
  50. euporie/core/widgets/menu.py +1 -3
  51. euporie/core/widgets/pager.py +3 -3
  52. euporie/core/widgets/palette.py +3 -2
  53. euporie/core/widgets/status_bar.py +2 -6
  54. euporie/core/widgets/tree.py +3 -6
  55. euporie/notebook/app.py +8 -8
  56. euporie/notebook/tabs/notebook.py +2 -2
  57. euporie/notebook/widgets/side_bar.py +1 -1
  58. euporie/preview/tabs/notebook.py +2 -2
  59. euporie/web/tabs/web.py +6 -1
  60. euporie/web/widgets/webview.py +52 -32
  61. {euporie-2.6.2.dist-info → euporie-2.7.0.dist-info}/METADATA +9 -11
  62. {euporie-2.6.2.dist-info → euporie-2.7.0.dist-info}/RECORD +67 -60
  63. {euporie-2.6.2.dist-info → euporie-2.7.0.dist-info}/WHEEL +1 -1
  64. {euporie-2.6.2.data → euporie-2.7.0.data}/data/share/applications/euporie-console.desktop +0 -0
  65. {euporie-2.6.2.data → euporie-2.7.0.data}/data/share/applications/euporie-notebook.desktop +0 -0
  66. {euporie-2.6.2.dist-info → euporie-2.7.0.dist-info}/entry_points.txt +0 -0
  67. {euporie-2.6.2.dist-info → euporie-2.7.0.dist-info}/licenses/LICENSE +0 -0
@@ -5,207 +5,34 @@ from __future__ import annotations
5
5
  import logging
6
6
  from typing import TYPE_CHECKING
7
7
 
8
- from prompt_toolkit.cache import FastDictCache
9
- from prompt_toolkit.filters import has_focus, to_filter
8
+ from prompt_toolkit.filters import to_filter
10
9
  from prompt_toolkit.layout.containers import (
11
10
  ConditionalContainer,
12
- Container,
13
11
  DynamicContainer,
14
12
  Float,
15
13
  FloatContainer,
16
- HSplit,
17
- VSplit,
18
- Window,
19
- to_container,
20
14
  )
21
- from prompt_toolkit.layout.dimension import Dimension
22
- from prompt_toolkit.layout.screen import Char, Screen, WritePosition
23
- from prompt_toolkit.mouse_events import MouseEventType
24
15
 
25
16
  from euporie.core.border import ThinLine
26
17
  from euporie.core.config import add_setting
27
18
  from euporie.core.current import get_app
28
19
  from euporie.core.data_structures import DiBool
29
- from euporie.core.style import ColorPaletteColor
20
+ from euporie.core.layout.containers import HSplit, VSplit, Window
21
+ from euporie.core.layout.decor import DropShadow
30
22
 
31
23
  if TYPE_CHECKING:
32
24
  from typing import Callable
33
25
 
34
- from prompt_toolkit.key_binding.key_bindings import NotImplementedOrNone
35
26
  from prompt_toolkit.layout.containers import AnyContainer
36
- from prompt_toolkit.layout.mouse_handlers import MouseHandlers
37
27
  from prompt_toolkit.mouse_events import MouseEvent
38
28
 
39
29
  from euporie.core.border import GridStyle
40
- from euporie.core.style import ColorPalette
41
30
 
42
31
  MouseHandler = Callable[[MouseEvent], object]
43
32
 
44
33
  log = logging.getLogger(__name__)
45
34
 
46
35
 
47
- class Line(Container):
48
- """Draw a horizontal or vertical line."""
49
-
50
- def __init__(
51
- self,
52
- char: str | None = None,
53
- width: int | None = None,
54
- height: int | None = None,
55
- collapse: bool = False,
56
- style: str = "class:grid-line",
57
- ) -> None:
58
- """Initialize a grid line.
59
-
60
- Args:
61
- char: The character to draw. If unset, the relevant character from
62
- :py:class:`euporie.core.box.grid` is used
63
- width: The length of the line. If specified, the line will be horizontal
64
- height: The height of the line. If specified, the line will be vertical
65
- collapse: Whether to hide the line when there is not enough space
66
- style: Style to apply to the line
67
-
68
- Raises:
69
- ValueError: If both width and height are specified. A line must only have a
70
- single dimension.
71
-
72
- """
73
- if width and height:
74
- raise ValueError("Only one of `width` or `height` must be set")
75
- self.width = width
76
- self.height = height
77
- if char is None:
78
- char = ThinLine.grid.VERTICAL if width else ThinLine.grid.HORIZONTAL
79
- self.char = Char(char, style)
80
- self.collapse = collapse
81
-
82
- def reset(self) -> None:
83
- """Reet the state of the line. Does nothing."""
84
-
85
- def preferred_width(self, max_available_width: int) -> Dimension:
86
- """Return the preferred width of the line."""
87
- return Dimension(min=int(not self.collapse), max=self.width)
88
-
89
- def preferred_height(self, width: int, max_available_height: int) -> Dimension:
90
- """Return the preferred height of the line."""
91
- return Dimension(min=int(not self.collapse), max=self.height)
92
-
93
- def write_to_screen(
94
- self,
95
- screen: Screen,
96
- mouse_handlers: MouseHandlers,
97
- write_position: WritePosition,
98
- parent_style: str,
99
- erase_bg: bool,
100
- z_index: int | None,
101
- ) -> None:
102
- """Draw a continuous line in the ``write_position`` area.
103
-
104
- Args:
105
- screen: The :class:`~prompt_toolkit.layout.screen.Screen` class to which
106
- the output has to be written.
107
- mouse_handlers: :class:`prompt_toolkit.layout.mouse_handlers.MouseHandlers`.
108
- write_position: A :class:`prompt_toolkit.layout.screen.WritePosition` object
109
- defining where this container should be drawn.
110
- erase_bg: If true, the background will be erased prior to drawing.
111
- parent_style: Style string to pass to the :class:`.Window` object. This will
112
- be applied to all content of the windows. :class:`.VSplit` and
113
- :class:`prompt_toolkit.layout.containers.HSplit` can use it to pass
114
- their style down to the windows that they contain.
115
- z_index: Used for propagating z_index from parent to child.
116
-
117
- """
118
- ypos = write_position.ypos
119
- xpos = write_position.xpos
120
-
121
- for y in range(ypos, ypos + write_position.height):
122
- row = screen.data_buffer[y]
123
- for x in range(xpos, xpos + write_position.width):
124
- row[x] = self.char
125
-
126
- def get_children(self) -> list:
127
- """Return an empty list of the container's children."""
128
- return []
129
-
130
-
131
- class Pattern(Container):
132
- """Fill an area with a repeating background pattern."""
133
-
134
- def __init__(
135
- self,
136
- char: str | Callable[[], str],
137
- pattern: int | Callable[[], int] = 1,
138
- ) -> None:
139
- """Initialize the :class:`Pattern`."""
140
- self.bg = Char(" ", "class:pattern")
141
- self.char = char
142
- self.pattern = pattern
143
-
144
- def reset(self) -> None:
145
- """Reet the pattern. Does nothing."""
146
-
147
- def preferred_width(self, max_available_width: int) -> Dimension:
148
- """Return an empty dimension (expand to available width)."""
149
- return Dimension()
150
-
151
- def preferred_height(self, width: int, max_available_height: int) -> Dimension:
152
- """Return an empty dimension (expand to available height)."""
153
- return Dimension()
154
-
155
- def write_to_screen(
156
- self,
157
- screen: Screen,
158
- mouse_handlers: MouseHandlers,
159
- write_position: WritePosition,
160
- parent_style: str,
161
- erase_bg: bool,
162
- z_index: int | None,
163
- ) -> None:
164
- """Fill the whole area of write_position with a pattern.
165
-
166
- Args:
167
- screen: The :class:`~prompt_toolkit.layout.screen.Screen` class to which
168
- the output has to be written.
169
- mouse_handlers: :class:`prompt_toolkit.layout.mouse_handlers.MouseHandlers`.
170
- write_position: A :class:`prompt_toolkit.layout.screen.WritePosition` object
171
- defining where this container should be drawn.
172
- erase_bg: If true, the background will be erased prior to drawing.
173
- parent_style: Style string to pass to the :class:`.Window` object. This will
174
- be applied to all content of the windows. :class:`.VSplit` and
175
- :class:`prompt_toolkit.layout.containers.HSplit` can use it to pass
176
- their style down to the windows that they contain.
177
- z_index: Used for propagating z_index from parent to child.
178
-
179
- """
180
- bg = self.bg
181
- pattern = self.pattern() if callable(self.pattern) else self.pattern
182
- if callable(self.char):
183
- char = Char(self.char(), "class:pattern")
184
- else:
185
- char = Char(self.char, "class:pattern")
186
-
187
- ypos = write_position.ypos
188
- xpos = write_position.xpos
189
-
190
- for y in range(ypos, ypos + write_position.height):
191
- row = screen.data_buffer[y]
192
- for x in range(xpos, xpos + write_position.width):
193
- if (
194
- (pattern == 1)
195
- or (pattern == 2 and (x + y) % 2 == 0)
196
- or (pattern == 3 and (x + 2 * y) % 4 == 0)
197
- or (pattern == 4 and (x + y) % 3 == 0)
198
- or (pattern == 5 and ((x + y % 2 * 3) % 6) % 4 == 0)
199
- ):
200
- row[x] = char
201
- else:
202
- row[x] = bg
203
-
204
- def get_children(self) -> list:
205
- """Return an empty list of the container's children."""
206
- return []
207
-
208
-
209
36
  class Border:
210
37
  """Draw a border around any container."""
211
38
 
@@ -353,189 +180,6 @@ class Border:
353
180
  return self.container
354
181
 
355
182
 
356
- class FocusedStyle(Container):
357
- """Apply a style to child containers when focused or hovered."""
358
-
359
- def __init__(
360
- self,
361
- body: AnyContainer,
362
- style_focus: str | Callable[[], str] = "class:focused",
363
- style_hover: str | Callable[[], str] = "",
364
- ) -> None:
365
- """Create a new instance of the widget.
366
-
367
- Args:
368
- body: The container to act on
369
- style_focus: The style to apply when the body has focus
370
- style_hover: The style to apply when the body is hovered
371
- """
372
- self.body = body
373
- self.style_focus = style_focus
374
- self.style_hover = style_hover
375
- self.hover = False
376
- self.has_focus = has_focus(self.body)
377
-
378
- def reset(self) -> None:
379
- """Reet the wrapped container."""
380
- to_container(self.body).reset()
381
-
382
- def preferred_width(self, max_available_width: int) -> Dimension:
383
- """Return the wrapped container's preferred width."""
384
- return to_container(self.body).preferred_width(max_available_width)
385
-
386
- def preferred_height(self, width: int, max_available_height: int) -> Dimension:
387
- """Return the wrapped container's preferred height."""
388
- return to_container(self.body).preferred_height(width, max_available_height)
389
-
390
- def write_to_screen(
391
- self,
392
- screen: Screen,
393
- mouse_handlers: MouseHandlers,
394
- write_position: WritePosition,
395
- parent_style: str,
396
- erase_bg: bool,
397
- z_index: int | None,
398
- ) -> None:
399
- """Draw the wrapped container with the additional style."""
400
- to_container(self.body).write_to_screen(
401
- screen,
402
- mouse_handlers,
403
- write_position,
404
- f"{parent_style} {self.get_style()}",
405
- erase_bg,
406
- z_index,
407
- )
408
-
409
- if self.style_hover:
410
- x_min = write_position.xpos
411
- x_max = x_min + write_position.width
412
- y_min = write_position.ypos
413
- y_max = y_min + write_position.height
414
-
415
- # Wrap mouse handlers to add "hover" class on hover
416
- def _wrap_mouse_handler(handler: Callable) -> MouseHandler:
417
- def wrapped_mouse_handler(
418
- mouse_event: MouseEvent,
419
- ) -> NotImplementedOrNone:
420
- result = handler(mouse_event)
421
-
422
- if mouse_event.event_type == MouseEventType.MOUSE_MOVE:
423
- app = get_app()
424
- if (
425
- mouse_event.position.x,
426
- mouse_event.position.y,
427
- ) == app.mouse_position:
428
- app.mouse_limits = write_position
429
- self.hover = True
430
- else:
431
- app.mouse_limits = None
432
- self.hover = False
433
- result = None
434
- return result
435
-
436
- return wrapped_mouse_handler
437
-
438
- mouse_handler_wrappers: FastDictCache[
439
- tuple[Callable], MouseHandler
440
- ] = FastDictCache(get_value=_wrap_mouse_handler)
441
- for y in range(y_min, y_max):
442
- row = mouse_handlers.mouse_handlers[y]
443
- for x in range(x_min, x_max):
444
- row[x] = mouse_handler_wrappers[(row[x],)]
445
-
446
- def get_style(self) -> str:
447
- """Determine the style to apply depending on the focus status."""
448
- style = ""
449
- if self.has_focus():
450
- style += (
451
- self.style_focus() if callable(self.style_focus) else self.style_focus
452
- )
453
- if self.hover:
454
- style += " " + (
455
- self.style_hover() if callable(self.style_hover) else self.style_hover
456
- )
457
- return style
458
-
459
- def get_children(self) -> list[Container]:
460
- """Return the list of child :class:`.Container` objects."""
461
- return [to_container(self.body)]
462
-
463
-
464
- class DropShadow(Container):
465
- """A transparent container which makes the background darker."""
466
-
467
- def __init__(self, amount: float = 0.5) -> None:
468
- """Create a new instance."""
469
- self.amount = amount
470
- self.renderer = get_app().renderer
471
-
472
- @property
473
- def cp(self) -> ColorPalette:
474
- """Get the current app's current color palette."""
475
- return get_app().color_palette
476
-
477
- def reset(self) -> None:
478
- """Reet the wrapped container - here, do nothing."""
479
-
480
- def preferred_width(self, max_available_width: int) -> Dimension:
481
- """Return the wrapped container's preferred width."""
482
- return Dimension(weight=1)
483
-
484
- def preferred_height(self, width: int, max_available_height: int) -> Dimension:
485
- """Return the wrapped container's preferred height."""
486
- return Dimension(weight=1)
487
-
488
- def write_to_screen(
489
- self,
490
- screen: Screen,
491
- mouse_handlers: MouseHandlers,
492
- write_position: WritePosition,
493
- parent_style: str,
494
- erase_bg: bool,
495
- z_index: int | None,
496
- ) -> None:
497
- """Draw the wrapped container with the additional style."""
498
- attr_cache = self.renderer._attrs_for_style
499
- if attr_cache is not None:
500
- ypos = write_position.ypos
501
- xpos = write_position.xpos
502
- amount = self.amount
503
- for y in range(ypos, ypos + write_position.height):
504
- row = screen.data_buffer[y]
505
- for x in range(xpos, xpos + write_position.width):
506
- char = row[x]
507
- style = char.style
508
- attrs = attr_cache[style]
509
-
510
- if not (fg := attrs.color) or fg == "default":
511
- color = self.cp.fg
512
- style += f" fg:{color.darker(amount)}"
513
- else:
514
- try:
515
- color = ColorPaletteColor(fg)
516
- except ValueError:
517
- pass
518
- else:
519
- style += f" fg:{color.darker(amount)}"
520
-
521
- if not (bg := attrs.bgcolor) or bg == "default":
522
- color = self.cp.bg
523
- style += f" bg:{color.darker(amount)}"
524
- else:
525
- try:
526
- color = ColorPaletteColor(bg)
527
- except ValueError:
528
- pass
529
- else:
530
- style += f" bg:{color.darker(amount)}"
531
-
532
- row[x] = Char(char=char.char, style=style)
533
-
534
- def get_children(self) -> list[Container]:
535
- """Return an empty list of child :class:`.Container` objects."""
536
- return []
537
-
538
-
539
183
  class Shadow:
540
184
  """Draw a shadow underneath/behind this container.
541
185
 
@@ -27,15 +27,12 @@ from prompt_toolkit.layout.containers import (
27
27
  ConditionalContainer,
28
28
  DynamicContainer,
29
29
  Float,
30
- HSplit,
31
- VSplit,
32
- Window,
33
30
  )
34
31
  from prompt_toolkit.layout.controls import FormattedTextControl, UIContent, UIControl
35
32
  from prompt_toolkit.layout.dimension import Dimension
36
33
  from prompt_toolkit.layout.screen import WritePosition
37
34
  from prompt_toolkit.mouse_events import MouseButton, MouseEventType
38
- from prompt_toolkit.widgets.base import Box, Label
35
+ from prompt_toolkit.widgets.base import Label
39
36
 
40
37
  from euporie.core.app import get_app
41
38
  from euporie.core.border import (
@@ -47,10 +44,13 @@ from euporie.core.commands import add_cmd
47
44
  from euporie.core.filters import tab_has_focus
48
45
  from euporie.core.ft.utils import FormattedTextAlign, align, lex
49
46
  from euporie.core.key_binding.registry import register_bindings
47
+ from euporie.core.layout.containers import HSplit, VSplit, Window
48
+ from euporie.core.layout.decor import FocusedStyle
50
49
  from euporie.core.tabs.base import Tab
51
- from euporie.core.widgets.decor import Border, FocusedStyle, Shadow
50
+ from euporie.core.widgets.decor import Border, Shadow
52
51
  from euporie.core.widgets.file_browser import FileBrowser
53
52
  from euporie.core.widgets.forms import Button, LabelledWidget, Select, Text
53
+ from euporie.core.widgets.layout import Box
54
54
 
55
55
  if TYPE_CHECKING:
56
56
  from typing import Any, Callable, Hashable
@@ -3,14 +3,15 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import logging
6
+ from functools import partial
6
7
  from math import ceil
7
8
  from typing import TYPE_CHECKING, cast
8
9
 
9
- from prompt_toolkit.cache import FastDictCache
10
+ from prompt_toolkit.cache import FastDictCache, SimpleCache
10
11
  from prompt_toolkit.data_structures import Point, Size
11
12
  from prompt_toolkit.filters.utils import to_filter
12
13
  from prompt_toolkit.formatted_text.utils import fragment_list_width, split_lines
13
- from prompt_toolkit.layout.containers import ConditionalContainer, VSplit, Window
14
+ from prompt_toolkit.layout.containers import ConditionalContainer
14
15
  from prompt_toolkit.layout.controls import GetLinePrefixCallable, UIContent, UIControl
15
16
  from prompt_toolkit.mouse_events import MouseEvent, MouseEventType
16
17
  from prompt_toolkit.utils import Event, to_str
@@ -23,15 +24,14 @@ from euporie.core.filters import (
23
24
  scrollable,
24
25
  )
25
26
  from euporie.core.ft.utils import wrap
27
+ from euporie.core.graphics import GraphicProcessor
26
28
  from euporie.core.key_binding.registry import (
27
29
  load_registered_bindings,
28
30
  register_bindings,
29
31
  )
32
+ from euporie.core.layout.containers import VSplit, Window
30
33
  from euporie.core.margins import MarginContainer, ScrollbarMargin
31
34
  from euporie.core.utils import run_in_thread_with_context
32
- from euporie.core.widgets.graphics import (
33
- GraphicProcessor,
34
- )
35
35
 
36
36
  if TYPE_CHECKING:
37
37
  from typing import Any, Callable, Iterable
@@ -96,10 +96,17 @@ class DisplayControl(UIControl):
96
96
  self.on_cursor_position_changed,
97
97
  ]
98
98
 
99
+ # Caches
100
+ self._content_cache: FastDictCache = FastDictCache(self.get_content, size=1000)
99
101
  self._line_cache: FastDictCache[
100
102
  tuple[Datum, int | None, int | None, bool], list[StyleAndTextTuples]
101
- ] = FastDictCache(get_value=self.get_lines, size=100_000)
102
- self._content_cache: FastDictCache = FastDictCache(self.get_content, size=1_000)
103
+ ] = FastDictCache(get_value=self.get_lines, size=1000)
104
+ self._line_width_cache: SimpleCache[
105
+ tuple[Datum, int | None, int | None, bool, int], int
106
+ ] = SimpleCache(maxsize=10_000)
107
+ self._max_line_width_cache: FastDictCache[
108
+ tuple[Datum, int | None, int | None, bool], int
109
+ ] = FastDictCache(get_value=self.get_max_line_width, size=1000)
103
110
 
104
111
  @property
105
112
  def datum(self) -> Any:
@@ -157,6 +164,23 @@ class DisplayControl(UIControl):
157
164
  lines.extend([[]] * max(0, height - len(lines)))
158
165
  return lines
159
166
 
167
+ def get_max_line_width(
168
+ self,
169
+ datum: Datum,
170
+ width: int | None,
171
+ height: int | None,
172
+ wrap_lines: bool = False,
173
+ ) -> int:
174
+ """Get the maximum lines width for a given rendering."""
175
+ lines = self._line_cache[datum, width, height, wrap_lines]
176
+ return max(
177
+ self._line_width_cache.get(
178
+ (datum, width, height, wrap_lines, i),
179
+ partial(fragment_list_width, line),
180
+ )
181
+ for i, line in enumerate(lines)
182
+ )
183
+
160
184
  def render(self) -> None:
161
185
  """Render the HTML DOM in a thread."""
162
186
  datum = self.datum
@@ -167,7 +191,6 @@ class DisplayControl(UIControl):
167
191
  rows = ceil(cols * aspect) if aspect else self.height
168
192
 
169
193
  def _render() -> None:
170
- # Potentially redirect url
171
194
  self.lines = self._line_cache[datum, cols, rows, wrap_lines]
172
195
  self.loading = False
173
196
  self.resizing = False
@@ -189,11 +212,9 @@ class DisplayControl(UIControl):
189
212
  max_cols, aspect = self.datum.cell_size()
190
213
  if max_cols:
191
214
  return min(max_cols, max_available_width)
192
- self.lines = self._line_cache[
215
+ return self._max_line_width_cache[
193
216
  self.datum, max_available_width, None, self.wrap_lines()
194
217
  ]
195
- return max(fragment_list_width(line) for line in self.lines)
196
- return max_available_width
197
218
 
198
219
  def preferred_height(
199
220
  self,
@@ -206,7 +227,6 @@ class DisplayControl(UIControl):
206
227
  max_cols, aspect = self.datum.cell_size()
207
228
  if aspect:
208
229
  return ceil(min(width, max_cols) * aspect)
209
-
210
230
  self.lines = self._line_cache[self.datum, width, None, self.wrap_lines()]
211
231
  return len(self.lines)
212
232
 
@@ -14,9 +14,6 @@ from prompt_toolkit.filters.utils import to_filter
14
14
  from prompt_toolkit.key_binding.key_bindings import KeyBindings, KeyBindingsBase
15
15
  from prompt_toolkit.layout.containers import (
16
16
  ConditionalContainer,
17
- HSplit,
18
- VSplit,
19
- Window,
20
17
  )
21
18
  from prompt_toolkit.layout.controls import UIContent, UIControl
22
19
  from prompt_toolkit.layout.screen import WritePosition
@@ -27,8 +24,10 @@ from upath import UPath
27
24
  from euporie.core.app import get_app
28
25
  from euporie.core.border import InsetGrid
29
26
  from euporie.core.config import add_setting
27
+ from euporie.core.layout.containers import HSplit, VSplit, Window
28
+ from euporie.core.layout.decor import FocusedStyle
30
29
  from euporie.core.margins import MarginContainer, ScrollbarMargin
31
- from euporie.core.widgets.decor import Border, FocusedStyle
30
+ from euporie.core.widgets.decor import Border
32
31
  from euporie.core.widgets.forms import Button, Text
33
32
 
34
33
  if TYPE_CHECKING:
@@ -44,7 +44,7 @@ from prompt_toolkit.layout.utils import explode_text_fragments
44
44
  from prompt_toolkit.mouse_events import MouseButton, MouseEvent, MouseEventType
45
45
  from prompt_toolkit.utils import Event
46
46
  from prompt_toolkit.validation import Validator
47
- from prompt_toolkit.widgets.base import Box, TextArea
47
+ from prompt_toolkit.widgets.base import TextArea
48
48
 
49
49
  from euporie.core.border import InsetGrid
50
50
  from euporie.core.current import get_app
@@ -52,7 +52,7 @@ from euporie.core.data_structures import DiBool
52
52
  from euporie.core.ft.utils import FormattedTextAlign, align
53
53
  from euporie.core.margins import MarginContainer, ScrollbarMargin
54
54
  from euporie.core.widgets.decor import Border, Shadow
55
- from euporie.core.widgets.layout import ConditionalSplit
55
+ from euporie.core.widgets.layout import Box, ConditionalSplit
56
56
 
57
57
  if TYPE_CHECKING:
58
58
  from typing import Any, Callable, Sequence
@@ -240,7 +240,12 @@ class Button:
240
240
  if mouse_event.button == MouseButton.LEFT:
241
241
  if mouse_event.event_type == MouseEventType.MOUSE_DOWN:
242
242
  self.selected = True
243
- if (render_info := self.window.render_info) is not None:
243
+ # Set global mouse capture
244
+ if (
245
+ self.window is not None
246
+ and (render_info := self.window.render_info) is not None
247
+ and render_info._rowcol_to_yx
248
+ ):
244
249
  y_min, x_min = min(render_info._rowcol_to_yx.values())
245
250
  y_max, x_max = max(render_info._rowcol_to_yx.values())
246
251
  get_app().mouse_limits = WritePosition(
@@ -632,6 +637,19 @@ class Text:
632
637
  key_bindings=self.text_area.control.key_bindings,
633
638
  expand=expand,
634
639
  )
640
+ window = self.text_area.window
641
+ self.text_area.window = Window(
642
+ height=window.height,
643
+ width=window.width,
644
+ dont_extend_height=window.dont_extend_height,
645
+ dont_extend_width=window.dont_extend_width,
646
+ content=self.text_area.control,
647
+ style=window.style,
648
+ wrap_lines=window.wrap_lines,
649
+ left_margins=window.left_margins,
650
+ right_margins=window.right_margins,
651
+ get_line_prefix=window.get_line_prefix,
652
+ )
635
653
  self.text_area.window.content = self.text_area.control
636
654
 
637
655
  if on_text_changed:
@@ -1568,18 +1586,21 @@ class ToggleButtons(SelectableWidget):
1568
1586
 
1569
1587
  def load_container(self) -> AnyContainer:
1570
1588
  """Load the widget's container."""
1571
- if self.vertical():
1572
- show_borders_values = [
1573
- DiBool(False, True, False, True) for _ in self.options
1574
- ]
1575
- show_borders_values[0] = show_borders_values[0]._replace(top=True)
1576
- show_borders_values[-1] = show_borders_values[-1]._replace(bottom=True)
1589
+ if self.options:
1590
+ if self.vertical():
1591
+ show_borders_values = [
1592
+ DiBool(False, True, False, True) for _ in self.options
1593
+ ]
1594
+ show_borders_values[0] = show_borders_values[0]._replace(top=True)
1595
+ show_borders_values[-1] = show_borders_values[-1]._replace(bottom=True)
1596
+ else:
1597
+ show_borders_values = [
1598
+ DiBool(True, False, True, False) for _ in self.options
1599
+ ]
1600
+ show_borders_values[0] = show_borders_values[0]._replace(left=True)
1601
+ show_borders_values[-1] = show_borders_values[-1]._replace(right=True)
1577
1602
  else:
1578
- show_borders_values = [
1579
- DiBool(True, False, True, False) for _ in self.options
1580
- ]
1581
- show_borders_values[0] = show_borders_values[0]._replace(left=True)
1582
- show_borders_values[-1] = show_borders_values[-1]._replace(right=True)
1603
+ show_borders_values = []
1583
1604
  self.buttons = [
1584
1605
  ToggleButton(
1585
1606
  text=label,
@@ -1825,6 +1846,7 @@ class SliderControl(UIControl):
1825
1846
  if (
1826
1847
  self.window is not None
1827
1848
  and (render_info := self.window.render_info) is not None
1849
+ and render_info._rowcol_to_yx
1828
1850
  ):
1829
1851
  y_min, x_min = min(render_info._rowcol_to_yx.values())
1830
1852
  y_max, x_max = max(render_info._rowcol_to_yx.values())