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.
- euporie/console/tabs/console.py +52 -38
- euporie/core/__init__.py +5 -2
- euporie/core/app.py +74 -57
- euporie/core/comm/ipywidgets.py +7 -3
- euporie/core/config.py +51 -27
- euporie/core/convert/__init__.py +2 -0
- euporie/core/convert/datum.py +82 -45
- euporie/core/convert/formats/ansi.py +1 -2
- euporie/core/convert/formats/common.py +7 -11
- euporie/core/convert/formats/ft.py +10 -7
- euporie/core/convert/formats/png.py +7 -6
- euporie/core/convert/formats/sixel.py +1 -1
- euporie/core/convert/formats/svg.py +28 -0
- euporie/core/convert/mime.py +4 -7
- euporie/core/data_structures.py +24 -22
- euporie/core/filters.py +16 -2
- euporie/core/format.py +30 -4
- euporie/core/ft/ansi.py +2 -1
- euporie/core/ft/html.py +155 -42
- euporie/core/{widgets/graphics.py → graphics.py} +225 -227
- euporie/core/io.py +8 -0
- euporie/core/key_binding/bindings/__init__.py +8 -2
- euporie/core/key_binding/bindings/basic.py +9 -14
- euporie/core/key_binding/bindings/micro.py +0 -12
- euporie/core/key_binding/bindings/mouse.py +107 -80
- euporie/core/key_binding/bindings/page_navigation.py +129 -0
- euporie/core/key_binding/key_processor.py +9 -1
- euporie/core/layout/__init__.py +1 -0
- euporie/core/layout/containers.py +1011 -0
- euporie/core/layout/decor.py +381 -0
- euporie/core/layout/print.py +130 -0
- euporie/core/layout/screen.py +75 -0
- euporie/core/{widgets/page.py → layout/scroll.py} +166 -111
- euporie/core/log.py +1 -1
- euporie/core/margins.py +11 -5
- euporie/core/path.py +43 -176
- euporie/core/renderer.py +31 -8
- euporie/core/style.py +2 -0
- euporie/core/tabs/base.py +2 -1
- euporie/core/terminal.py +19 -21
- euporie/core/widgets/cell.py +2 -4
- euporie/core/widgets/cell_outputs.py +2 -2
- euporie/core/widgets/decor.py +3 -359
- euporie/core/widgets/dialog.py +5 -5
- euporie/core/widgets/display.py +32 -12
- euporie/core/widgets/file_browser.py +3 -4
- euporie/core/widgets/forms.py +36 -14
- euporie/core/widgets/inputs.py +171 -99
- euporie/core/widgets/layout.py +80 -5
- euporie/core/widgets/menu.py +1 -3
- euporie/core/widgets/pager.py +3 -3
- euporie/core/widgets/palette.py +3 -2
- euporie/core/widgets/status_bar.py +2 -6
- euporie/core/widgets/tree.py +3 -6
- euporie/notebook/app.py +8 -8
- euporie/notebook/tabs/notebook.py +2 -2
- euporie/notebook/widgets/side_bar.py +1 -1
- euporie/preview/tabs/notebook.py +2 -2
- euporie/web/tabs/web.py +6 -1
- euporie/web/widgets/webview.py +52 -32
- {euporie-2.6.2.dist-info → euporie-2.7.0.dist-info}/METADATA +9 -11
- {euporie-2.6.2.dist-info → euporie-2.7.0.dist-info}/RECORD +67 -60
- {euporie-2.6.2.dist-info → euporie-2.7.0.dist-info}/WHEEL +1 -1
- {euporie-2.6.2.data → euporie-2.7.0.data}/data/share/applications/euporie-console.desktop +0 -0
- {euporie-2.6.2.data → euporie-2.7.0.data}/data/share/applications/euporie-notebook.desktop +0 -0
- {euporie-2.6.2.dist-info → euporie-2.7.0.dist-info}/entry_points.txt +0 -0
- {euporie-2.6.2.dist-info → euporie-2.7.0.dist-info}/licenses/LICENSE +0 -0
@@ -21,14 +21,14 @@ from prompt_toolkit.layout.controls import UIContent
|
|
21
21
|
from prompt_toolkit.layout.dimension import Dimension, to_dimension
|
22
22
|
from prompt_toolkit.layout.layout import walk
|
23
23
|
from prompt_toolkit.layout.mouse_handlers import MouseHandlers
|
24
|
-
from prompt_toolkit.layout.screen import Char, Screen, WritePosition
|
25
24
|
from prompt_toolkit.mouse_events import MouseEvent, MouseEventType, MouseModifier
|
26
25
|
|
27
26
|
from euporie.core.data_structures import DiInt
|
27
|
+
from euporie.core.layout.screen import BoundedWritePosition, Screen
|
28
28
|
from euporie.core.utils import run_in_thread_with_context
|
29
29
|
|
30
30
|
if TYPE_CHECKING:
|
31
|
-
from typing import Callable,
|
31
|
+
from typing import Callable, Literal, Sequence
|
32
32
|
|
33
33
|
from prompt_toolkit.key_binding.key_bindings import (
|
34
34
|
KeyBindingsBase,
|
@@ -36,6 +36,8 @@ if TYPE_CHECKING:
|
|
36
36
|
)
|
37
37
|
from prompt_toolkit.layout.containers import AnyContainer
|
38
38
|
from prompt_toolkit.layout.dimension import AnyDimension
|
39
|
+
from prompt_toolkit.layout.screen import Screen as PtkScreen
|
40
|
+
from prompt_toolkit.layout.screen import WritePosition
|
39
41
|
from prompt_toolkit.utils import Event
|
40
42
|
|
41
43
|
MouseHandler = Callable[[MouseEvent], object]
|
@@ -43,31 +45,6 @@ if TYPE_CHECKING:
|
|
43
45
|
log = logging.getLogger(__name__)
|
44
46
|
|
45
47
|
|
46
|
-
class BoundedWritePosition(WritePosition):
|
47
|
-
"""A write position which also hold bounding box information."""
|
48
|
-
|
49
|
-
def __init__(
|
50
|
-
self,
|
51
|
-
xpos: int,
|
52
|
-
ypos: int,
|
53
|
-
width: int,
|
54
|
-
height: int,
|
55
|
-
bbox: DiInt,
|
56
|
-
) -> None:
|
57
|
-
"""Create a new instance of the write position."""
|
58
|
-
super().__init__(xpos, ypos, width, height)
|
59
|
-
self.bbox = bbox
|
60
|
-
|
61
|
-
def __repr__(self) -> str:
|
62
|
-
"""Return a string representation of the write position."""
|
63
|
-
return (
|
64
|
-
f"{self.__class__.__name__}("
|
65
|
-
f"x={self.xpos}, y={self.ypos}, "
|
66
|
-
f"w={self.width}, h={self.height}, "
|
67
|
-
f"bbox={self.bbox})"
|
68
|
-
)
|
69
|
-
|
70
|
-
|
71
48
|
class ChildRenderInfo:
|
72
49
|
"""A class which holds information about a :py:class:`ScrollingContainer` child."""
|
73
50
|
|
@@ -83,15 +60,16 @@ class ChildRenderInfo:
|
|
83
60
|
self.child = weakref.proxy(child)
|
84
61
|
self.container = to_container(child)
|
85
62
|
|
86
|
-
self.screen = Screen(
|
63
|
+
self.screen = Screen()
|
87
64
|
self.mouse_handlers = MouseHandlers()
|
88
65
|
|
89
|
-
self.render_counter = -1
|
90
66
|
self._invalid = True
|
91
|
-
self._invalidate_events:
|
67
|
+
self._invalidate_events: set[Event[object]] = set()
|
92
68
|
self._layout_hash = 0
|
93
69
|
self.height = 0
|
94
70
|
self.width = 0
|
71
|
+
self._rendered_lines: set[int] = set()
|
72
|
+
self._rowcols_to_yx: dict[Window, dict[tuple[int, int], tuple[int, int]]] = {}
|
95
73
|
|
96
74
|
def invalidate(self) -> None:
|
97
75
|
"""Flag the child's rendering as out-of-date."""
|
@@ -110,6 +88,8 @@ class ChildRenderInfo:
|
|
110
88
|
available_width: int,
|
111
89
|
available_height: int,
|
112
90
|
style: str = "",
|
91
|
+
start: int | None = None,
|
92
|
+
end: int | None = None,
|
113
93
|
) -> None:
|
114
94
|
"""Render the child container at a given size.
|
115
95
|
|
@@ -117,74 +97,103 @@ class ChildRenderInfo:
|
|
117
97
|
available_width: The height available for rendering
|
118
98
|
available_height: The width available for rendering
|
119
99
|
style: The parent style to apply when rendering
|
100
|
+
bbox: The bounding box for the content to render
|
101
|
+
start: Rows between top of output and top of scrollable pane
|
102
|
+
end: Rows between top of output and bottom of scrollable pane
|
120
103
|
|
121
104
|
"""
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
if self.render_counter != (new_render_counter := get_app().render_counter) and (
|
126
|
-
self._invalid
|
105
|
+
if (
|
106
|
+
self._layout_hash != (new_layout_hash := self.layout_hash)
|
107
|
+
or self._invalid
|
127
108
|
or self.width != available_width
|
128
|
-
or self._layout_hash != (new_layout_hash := self.layout_hash)
|
129
109
|
):
|
130
|
-
self.render_counter = new_render_counter
|
131
|
-
refresh = True
|
132
|
-
|
133
|
-
# Refresh if needed
|
134
|
-
if refresh:
|
135
|
-
self._invalid = False
|
136
110
|
self._layout_hash = new_layout_hash
|
137
|
-
|
138
|
-
self.
|
139
|
-
self.
|
111
|
+
self._rowcols_to_yx.clear()
|
112
|
+
self._rendered_lines.clear()
|
113
|
+
self.mouse_handlers.mouse_handlers.clear()
|
114
|
+
self.screen = Screen()
|
115
|
+
|
116
|
+
# Recalculate child height if this child has been invalidated
|
117
|
+
height = self.height = self.container.preferred_height(
|
140
118
|
available_width, available_height
|
141
119
|
).preferred
|
120
|
+
|
121
|
+
else:
|
122
|
+
height = self.height
|
123
|
+
|
124
|
+
# Calculate which lines are visible and which need rendering
|
125
|
+
if start is not None:
|
126
|
+
skip_top = max(0, -start)
|
127
|
+
elif end is not None:
|
128
|
+
skip_top = max(0, -end + height)
|
129
|
+
else:
|
130
|
+
skip_top = 0
|
131
|
+
skip_bottom = max(0, height - available_height - skip_top)
|
132
|
+
|
133
|
+
visible_lines = set(range(skip_top, height - skip_bottom))
|
134
|
+
required_lines = visible_lines - self._rendered_lines
|
135
|
+
|
136
|
+
# Refresh if needed
|
137
|
+
if required_lines:
|
138
|
+
screen = self.screen
|
142
139
|
# TODO - allow horizontal scrolling too
|
143
140
|
# self.width = self.container.preferred_width(available_width).width
|
144
141
|
self.width = available_width
|
145
142
|
|
146
|
-
|
147
|
-
screen = self.screen
|
148
|
-
screen.data_buffer.clear()
|
149
|
-
screen.zero_width_escapes.clear()
|
150
|
-
screen.cursor_positions.clear()
|
151
|
-
screen.menu_positions.clear()
|
152
|
-
screen.visible_windows_to_write_positions.clear()
|
153
|
-
screen._draw_float_functions.clear()
|
154
|
-
screen.width = screen.height = 0
|
143
|
+
self._invalid = False
|
155
144
|
|
156
145
|
self.container.write_to_screen(
|
157
146
|
screen,
|
158
147
|
self.mouse_handlers,
|
159
|
-
|
160
|
-
0,
|
161
|
-
0,
|
162
|
-
self.width,
|
163
|
-
self.height,
|
148
|
+
BoundedWritePosition(
|
149
|
+
xpos=0,
|
150
|
+
ypos=0,
|
151
|
+
width=self.width,
|
152
|
+
height=self.height,
|
153
|
+
# Only render lines not already on the current screen
|
154
|
+
bbox=DiInt(
|
155
|
+
top=min(required_lines),
|
156
|
+
right=0,
|
157
|
+
bottom=height - max(required_lines) - 1,
|
158
|
+
left=0,
|
159
|
+
),
|
164
160
|
),
|
165
161
|
style,
|
166
162
|
erase_bg=True,
|
167
163
|
z_index=0,
|
168
164
|
)
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
165
|
+
screen.draw_all_floats()
|
166
|
+
|
167
|
+
events = set()
|
168
|
+
rowcols_to_yx = self._rowcols_to_yx
|
169
|
+
|
170
|
+
for container in walk(self.container):
|
171
|
+
if isinstance(container, Window):
|
172
|
+
# Collect invalidation events
|
173
|
+
for event in container.content.get_invalidate_events():
|
174
|
+
event += self._invalidate_handler
|
175
|
+
events.add(event)
|
176
|
+
|
177
|
+
if (render_info := container.render_info) is not None:
|
178
|
+
# Update row/col to x/y mapping based on the lines we just rendereed
|
179
|
+
if container not in rowcols_to_yx:
|
180
|
+
rowcols_to_yx[container] = {}
|
181
|
+
|
182
|
+
rowcols_to_yx[container].update(render_info._rowcol_to_yx)
|
183
|
+
render_info._rowcol_to_yx = rowcols_to_yx[container]
|
184
|
+
|
185
|
+
# Update the record of lines that've been rendered to the temporary screen
|
186
|
+
self._rendered_lines |= required_lines
|
187
|
+
|
188
|
+
# Remove handler from old invalidation events
|
189
|
+
for event in events - set(self._invalidate_events):
|
181
190
|
event -= self._invalidate_handler
|
182
191
|
# Update the list of handlers
|
183
|
-
self._invalidate_events =
|
192
|
+
self._invalidate_events = events
|
184
193
|
|
185
194
|
def blit(
|
186
195
|
self,
|
187
|
-
screen:
|
196
|
+
screen: PtkScreen,
|
188
197
|
mouse_handlers: MouseHandlers,
|
189
198
|
left: int,
|
190
199
|
top: int,
|
@@ -241,11 +250,10 @@ class ChildRenderInfo:
|
|
241
250
|
window_width=info.window_width,
|
242
251
|
window_height=info.window_height,
|
243
252
|
configured_scroll_offsets=info.configured_scroll_offsets,
|
244
|
-
# visible_line_to_row_col=info.visible_line_to_row_col,
|
245
253
|
visible_line_to_row_col=visible_line_to_row_col,
|
246
254
|
rowcol_to_yx={
|
247
|
-
(row, col): (y + top, x + left)
|
248
|
-
for (row, col), (y, x) in info._rowcol_to_yx.items()
|
255
|
+
# (row, col): (y + top, x + left)
|
256
|
+
# for (row, col), (y, x) in info._rowcol_to_yx.items()
|
249
257
|
},
|
250
258
|
x_offset=info._x_offset + left,
|
251
259
|
y_offset=info._y_offset + top,
|
@@ -272,30 +280,38 @@ class ChildRenderInfo:
|
|
272
280
|
mouse_event: MouseEvent,
|
273
281
|
) -> NotImplementedOrNone:
|
274
282
|
response: NotImplementedOrNone = NotImplemented
|
275
|
-
new_event = MouseEvent(
|
276
|
-
position=Point(
|
277
|
-
x=mouse_event.position.x - left,
|
278
|
-
y=mouse_event.position.y - top,
|
279
|
-
),
|
280
|
-
event_type=mouse_event.event_type,
|
281
|
-
button=mouse_event.button,
|
282
|
-
modifiers=mouse_event.modifiers,
|
283
|
-
)
|
284
283
|
|
285
|
-
|
284
|
+
if mouse_event.event_type == MouseEventType.SCROLL_DOWN:
|
285
|
+
response = self.parent.scroll(-1)
|
286
|
+
elif mouse_event.event_type == MouseEventType.SCROLL_UP:
|
287
|
+
response = self.parent.scroll(1)
|
288
|
+
|
289
|
+
else:
|
290
|
+
new_event = MouseEvent(
|
291
|
+
position=Point(
|
292
|
+
x=mouse_event.position.x - left,
|
293
|
+
y=mouse_event.position.y - top,
|
294
|
+
),
|
295
|
+
event_type=mouse_event.event_type,
|
296
|
+
button=mouse_event.button,
|
297
|
+
modifiers=mouse_event.modifiers,
|
298
|
+
)
|
299
|
+
response = handler(new_event)
|
300
|
+
# Request a re-render of the child for non-scroll events
|
301
|
+
if response is None:
|
302
|
+
self.invalidate()
|
286
303
|
|
287
304
|
# Refresh the child if there was a response
|
288
305
|
if response is None:
|
289
|
-
self.invalidate()
|
290
306
|
return response
|
291
307
|
|
292
308
|
# This relies on windows returning NotImplemented when scrolled
|
293
309
|
# to the start or end
|
294
|
-
if response is NotImplemented:
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
310
|
+
# if response is NotImplemented:
|
311
|
+
# if mouse_event.event_type == MouseEventType.SCROLL_DOWN:
|
312
|
+
# response = self.parent.scroll(-1)
|
313
|
+
# elif mouse_event.event_type == MouseEventType.SCROLL_UP:
|
314
|
+
# response = self.parent.scroll(1)
|
299
315
|
|
300
316
|
# Select the clicked child if clicked
|
301
317
|
if mouse_event.event_type == MouseEventType.MOUSE_DOWN:
|
@@ -357,6 +373,7 @@ class ChildRenderInfo:
|
|
357
373
|
x=left + point.x, y=top + point.y
|
358
374
|
)
|
359
375
|
screen.show_cursor = True
|
376
|
+
|
360
377
|
# Copy menu positions
|
361
378
|
for window, point in self.screen.menu_positions.items():
|
362
379
|
screen.menu_positions[window] = Point(x=left + point.x, y=top + point.y)
|
@@ -373,6 +390,7 @@ class ScrollingContainer(Container):
|
|
373
390
|
height: AnyDimension = None,
|
374
391
|
width: AnyDimension = None,
|
375
392
|
style: str | Callable[[], str] = "",
|
393
|
+
scroll_offsets: ScrollOffsets | None = None,
|
376
394
|
) -> None:
|
377
395
|
"""Initiate the `ScrollingContainer`."""
|
378
396
|
if callable(children):
|
@@ -401,11 +419,14 @@ class ScrollingContainer(Container):
|
|
401
419
|
self.visible_indicies: set[int] = {0}
|
402
420
|
self.index_positions: dict[int, int | None] = {}
|
403
421
|
|
404
|
-
self.last_write_position =
|
422
|
+
self.last_write_position: WritePosition = BoundedWritePosition(0, 0, 0, 0)
|
405
423
|
|
406
424
|
self.width = to_dimension(width).preferred
|
407
425
|
self.height = to_dimension(height).preferred
|
408
426
|
self.style = style
|
427
|
+
self.scroll_offsets = scroll_offsets or ScrollOffsets(
|
428
|
+
top=1, bottom=1, left=1, right=1
|
429
|
+
)
|
409
430
|
|
410
431
|
self.scroll_to_cursor = False
|
411
432
|
self.scrolling = 0
|
@@ -413,7 +434,7 @@ class ScrollingContainer(Container):
|
|
413
434
|
def pre_render_children(self, width: int, height: int) -> None:
|
414
435
|
"""Render all unrendered children in a background thread."""
|
415
436
|
# Prevent multiple calls
|
416
|
-
self.pre_rendered = 0.
|
437
|
+
self.pre_rendered = 0.000000001
|
417
438
|
|
418
439
|
def render_in_thread() -> None:
|
419
440
|
"""Render children in thread."""
|
@@ -427,6 +448,7 @@ class ScrollingContainer(Container):
|
|
427
448
|
get_app().invalidate()
|
428
449
|
|
429
450
|
run_in_thread_with_context(render_in_thread)
|
451
|
+
# render_in_thread()
|
430
452
|
|
431
453
|
def reset(self) -> None:
|
432
454
|
"""Reet the state of this container and all the children."""
|
@@ -627,8 +649,11 @@ class ScrollingContainer(Container):
|
|
627
649
|
return NotImplemented
|
628
650
|
|
629
651
|
# Very basic scrolling acceleration
|
630
|
-
|
631
|
-
|
652
|
+
if n:
|
653
|
+
self.scrolling += n
|
654
|
+
return None
|
655
|
+
else:
|
656
|
+
return NotImplemented
|
632
657
|
|
633
658
|
def mouse_scroll_handler(self, mouse_event: MouseEvent) -> NotImplementedOrNone:
|
634
659
|
"""Mouse handler to scroll the pane."""
|
@@ -642,7 +667,7 @@ class ScrollingContainer(Container):
|
|
642
667
|
|
643
668
|
def write_to_screen(
|
644
669
|
self,
|
645
|
-
screen:
|
670
|
+
screen: PtkScreen,
|
646
671
|
mouse_handlers: MouseHandlers,
|
647
672
|
write_position: WritePosition,
|
648
673
|
parent_style: str,
|
@@ -706,17 +731,22 @@ class ScrollingContainer(Container):
|
|
706
731
|
available_width=available_width,
|
707
732
|
available_height=available_height,
|
708
733
|
style=f"{parent_style} {self.style}",
|
734
|
+
start=self.selected_child_position,
|
709
735
|
)
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
736
|
+
if (
|
737
|
+
cursor_position
|
738
|
+
:= selected_child_render_info.screen.cursor_positions.get(
|
739
|
+
layout.current_window
|
740
|
+
)
|
741
|
+
):
|
715
742
|
cursor_row = self.selected_child_position + cursor_position.y
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
743
|
+
scroll_offsets = self.scroll_offsets
|
744
|
+
if cursor_row < scroll_offsets.top + scroll_offsets.top:
|
745
|
+
self.selected_child_position -= cursor_row - scroll_offsets.top
|
746
|
+
elif cursor_row >= available_height - (scroll_offsets.bottom + 1):
|
747
|
+
self.selected_child_position -= (
|
748
|
+
cursor_row - available_height + (scroll_offsets.bottom + 1)
|
749
|
+
)
|
720
750
|
# Set this to false again, allowing us to scroll the cursor out of view
|
721
751
|
self.scroll_to_cursor = False
|
722
752
|
|
@@ -756,6 +786,7 @@ class ScrollingContainer(Container):
|
|
756
786
|
available_width=available_width,
|
757
787
|
available_height=available_height,
|
758
788
|
style=f"{parent_style} {self.style}",
|
789
|
+
start=line,
|
759
790
|
)
|
760
791
|
if line + child_render_info.height > 0 and line < available_height:
|
761
792
|
self.index_positions[i] = line
|
@@ -779,7 +810,7 @@ class ScrollingContainer(Container):
|
|
779
810
|
Window(char=" ").write_to_screen(
|
780
811
|
screen,
|
781
812
|
mouse_handlers,
|
782
|
-
|
813
|
+
BoundedWritePosition(
|
783
814
|
xpos, ypos + line, available_width, available_height - line
|
784
815
|
),
|
785
816
|
parent_style,
|
@@ -797,7 +828,9 @@ class ScrollingContainer(Container):
|
|
797
828
|
available_width=available_width,
|
798
829
|
available_height=available_height,
|
799
830
|
style=f"{parent_style} {self.style}",
|
831
|
+
end=line,
|
800
832
|
)
|
833
|
+
# TODOD - prevent lagged child height
|
801
834
|
line -= child_render_info.height
|
802
835
|
if line + child_render_info.height > 0 and line < available_height:
|
803
836
|
self.index_positions[i] = line
|
@@ -820,7 +853,7 @@ class ScrollingContainer(Container):
|
|
820
853
|
Window(char=" ").write_to_screen(
|
821
854
|
screen,
|
822
855
|
mouse_handlers,
|
823
|
-
|
856
|
+
BoundedWritePosition(xpos, ypos, available_width, line),
|
824
857
|
parent_style,
|
825
858
|
erase_bg,
|
826
859
|
z_index,
|
@@ -918,6 +951,7 @@ class ScrollingContainer(Container):
|
|
918
951
|
"""
|
919
952
|
if index is None:
|
920
953
|
index = self.selected_slice.start
|
954
|
+
index = max(0, min(len(self.children) - 1, index))
|
921
955
|
return self.children[index]
|
922
956
|
|
923
957
|
def scroll_to(
|
@@ -937,7 +971,6 @@ class ScrollingContainer(Container):
|
|
937
971
|
new_top = self.index_positions[index]
|
938
972
|
else:
|
939
973
|
if index < self._selected_slice.start:
|
940
|
-
# new_top = 0
|
941
974
|
new_top = max(
|
942
975
|
0, child_render_info.height - self.last_write_position.height
|
943
976
|
)
|
@@ -956,6 +989,13 @@ class ScrollingContainer(Container):
|
|
956
989
|
0,
|
957
990
|
)
|
958
991
|
|
992
|
+
# If the child height is not know, we'll need to render it to determine its height
|
993
|
+
if not child_render_info.height:
|
994
|
+
child_render_info.render(
|
995
|
+
self.last_write_position.width,
|
996
|
+
self.last_write_position.height,
|
997
|
+
)
|
998
|
+
|
959
999
|
if new_top is None or new_top < 0:
|
960
1000
|
if anchor == "top":
|
961
1001
|
self.selected_child_position = 0
|
@@ -974,6 +1014,21 @@ class ScrollingContainer(Container):
|
|
974
1014
|
else:
|
975
1015
|
self.selected_child_position = new_top
|
976
1016
|
|
1017
|
+
heights = [
|
1018
|
+
self.get_child_render_info(index).height or 1
|
1019
|
+
for index in range(len(self._children))
|
1020
|
+
]
|
1021
|
+
# Do not allow bottom child to scroll above screen bottom
|
1022
|
+
self.selected_child_position += max(
|
1023
|
+
0,
|
1024
|
+
self.last_write_position.height
|
1025
|
+
- (self.selected_child_position + sum(heights[index:])),
|
1026
|
+
)
|
1027
|
+
# Do not allow top child to scroll below screen top
|
1028
|
+
self.selected_child_position -= max(
|
1029
|
+
0, self.selected_child_position - sum(heights[:index])
|
1030
|
+
)
|
1031
|
+
|
977
1032
|
@property
|
978
1033
|
def known_sizes(self) -> dict[int, int]:
|
979
1034
|
"""A dictionary mapping child indices to height values."""
|
@@ -1026,7 +1081,7 @@ class PrintingContainer(Container):
|
|
1026
1081
|
|
1027
1082
|
def write_to_screen(
|
1028
1083
|
self,
|
1029
|
-
screen:
|
1084
|
+
screen: PtkScreen,
|
1030
1085
|
mouse_handlers: MouseHandlers,
|
1031
1086
|
write_position: WritePosition,
|
1032
1087
|
parent_style: str,
|
@@ -1060,7 +1115,7 @@ class PrintingContainer(Container):
|
|
1060
1115
|
child.write_to_screen(
|
1061
1116
|
screen,
|
1062
1117
|
mouse_handlers,
|
1063
|
-
|
1118
|
+
BoundedWritePosition(xpos, ypos, write_position.width, height),
|
1064
1119
|
parent_style,
|
1065
1120
|
erase_bg,
|
1066
1121
|
z_index,
|
euporie/core/log.py
CHANGED
@@ -371,7 +371,7 @@ def setup_logs(config: Config | None = None) -> None:
|
|
371
371
|
"disable_existing_loggers": False,
|
372
372
|
"formatters": {
|
373
373
|
"file_format": {
|
374
|
-
"format": "{asctime} {levelname:<7} [{name}.{funcName}:{lineno}] {message}",
|
374
|
+
"format": "{asctime}.{msecs:03.0f} {levelname:<7} [{name}.{funcName}:{lineno}] {message}",
|
375
375
|
"style": "{",
|
376
376
|
"datefmt": "%Y-%m-%d %H:%M:%S",
|
377
377
|
},
|
euporie/core/margins.py
CHANGED
@@ -9,7 +9,6 @@ from typing import TYPE_CHECKING, cast
|
|
9
9
|
|
10
10
|
from prompt_toolkit.data_structures import Point
|
11
11
|
from prompt_toolkit.filters import FilterOrBool, to_filter
|
12
|
-
from prompt_toolkit.layout.containers import Container, Window
|
13
12
|
from prompt_toolkit.layout.controls import FormattedTextControl
|
14
13
|
from prompt_toolkit.layout.dimension import Dimension
|
15
14
|
from prompt_toolkit.layout.margins import Margin
|
@@ -18,6 +17,7 @@ from prompt_toolkit.mouse_events import MouseEvent as PtkMouseEvent
|
|
18
17
|
|
19
18
|
from euporie.core.current import get_app
|
20
19
|
from euporie.core.key_binding.bindings.mouse import MouseEvent, RelativePosition
|
20
|
+
from euporie.core.layout.containers import Window
|
21
21
|
|
22
22
|
if TYPE_CHECKING:
|
23
23
|
from typing import Callable, Protocol
|
@@ -27,7 +27,7 @@ if TYPE_CHECKING:
|
|
27
27
|
KeyBindingsBase,
|
28
28
|
NotImplementedOrNone,
|
29
29
|
)
|
30
|
-
from prompt_toolkit.layout.containers import WindowRenderInfo
|
30
|
+
from prompt_toolkit.layout.containers import Container, WindowRenderInfo
|
31
31
|
from prompt_toolkit.layout.controls import UIContent
|
32
32
|
from prompt_toolkit.layout.mouse_handlers import MouseHandlers
|
33
33
|
from prompt_toolkit.layout.screen import Screen, WritePosition
|
@@ -61,6 +61,7 @@ class MarginContainer(Window):
|
|
61
61
|
self.target = target
|
62
62
|
self.render_info: WindowRenderInfo | None = None
|
63
63
|
self.content = FormattedTextControl(self.create_fragments)
|
64
|
+
self.always_hide_cursor = to_filter(True)
|
64
65
|
|
65
66
|
def create_fragments(self) -> StyleAndTextTuples:
|
66
67
|
"""Generate text fragments to display."""
|
@@ -430,8 +431,9 @@ class ScrollbarMargin(ClickableMargin):
|
|
430
431
|
func = window._scroll_down if delta < 0 else window._scroll_up
|
431
432
|
for _ in range(abs(delta)):
|
432
433
|
func()
|
434
|
+
# Hack to speed up scrolling on the :py:`ScrollingContainer`
|
433
435
|
elif hasattr(window, "scrolling"):
|
434
|
-
render_info.window
|
436
|
+
setattr(render_info.window, "scrolling", delta) # noqa: B010
|
435
437
|
|
436
438
|
# Mouse down events
|
437
439
|
elif mouse_event.event_type == MouseEventType.MOUSE_DOWN:
|
@@ -522,8 +524,11 @@ class NumberedDiffMargin(Margin):
|
|
522
524
|
pass
|
523
525
|
linestr = str(lineno + 1).rjust(width - 2)
|
524
526
|
style = self.style
|
525
|
-
if
|
526
|
-
|
527
|
+
if (
|
528
|
+
lineno == current_lineno
|
529
|
+
and get_app().layout.has_focus(window_render_info.window)
|
530
|
+
# Only highlight line number if there are multiple lines
|
531
|
+
and len(window_render_info.displayed_lines) > 1
|
527
532
|
):
|
528
533
|
style = f"{style} class:line-number.current"
|
529
534
|
result.extend(
|
@@ -535,6 +540,7 @@ class NumberedDiffMargin(Margin):
|
|
535
540
|
)
|
536
541
|
last_lineno = lineno
|
537
542
|
result.append(("", "\n"))
|
543
|
+
|
538
544
|
return result
|
539
545
|
|
540
546
|
|