Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP Arbitrary select #5409

Merged
merged 81 commits into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
5f018c9
offsets in styles
willmcgugan Dec 17, 2024
784a7c6
wip
willmcgugan Dec 17, 2024
88b2638
get_selection
willmcgugan Dec 17, 2024
82d0025
render selection
willmcgugan Dec 18, 2024
5364fc7
click and drag to select
willmcgugan Dec 19, 2024
cbe63ca
allow reverse order
willmcgugan Dec 19, 2024
117e390
click to select
willmcgugan Dec 22, 2024
ae5c4a0
update heuristic
willmcgugan Dec 23, 2024
9dd32eb
text align
willmcgugan Dec 24, 2024
6734f44
offset style
willmcgugan Dec 25, 2024
c242510
content line refactor
willmcgugan Dec 28, 2024
66d209b
Add ability to scroll Footer without holding shift
darrenburns Dec 17, 2024
39f06d1
Update changelog
darrenburns Dec 17, 2024
a393cf6
Enable animation
darrenburns Dec 17, 2024
e2fc3b9
Add simple "type to search" functionality to Select
darrenburns Dec 17, 2024
0fe6622
Favour match at start of string
darrenburns Dec 17, 2024
977f519
Snapshot test for Select.type_to_search
darrenburns Dec 17, 2024
29076b8
Update changelog
darrenburns Dec 17, 2024
cb22866
Add a docstring
darrenburns Dec 17, 2024
812c48c
Update Select documentation
darrenburns Dec 18, 2024
aa63d1d
Docstrings
darrenburns Dec 18, 2024
5b943c5
Refactor cursor movement logic in Input widget to handle selection st…
darrenburns Dec 16, 2024
0be63ef
Cursor left and right standardisation when theres an active selection
darrenburns Dec 16, 2024
d79e6a0
Improving logic
darrenburns Dec 16, 2024
24ff57f
Update CHANGELOG
darrenburns Dec 17, 2024
dc01d60
Update test to account for new interactions with selections and curso…
darrenburns Dec 17, 2024
0445416
Improve docstrings
darrenburns Dec 17, 2024
97d43cf
Fixing pilot times argument
darrenburns Dec 16, 2024
4110de7
Update CHANGELOG.md
darrenburns Dec 16, 2024
f52567d
fix(stylesheet): fix last-of-type
TomJGooding Dec 12, 2024
a4ad73a
Dont select the text in the command palette input on focus
darrenburns Dec 11, 2024
83cf184
Add missing changelog entry
darrenburns Dec 12, 2024
a46124f
Changelog formatting
darrenburns Dec 12, 2024
d72feea
Enhance Focus event with from_app_focus argument
darrenburns Dec 12, 2024
98432b2
Include a flag in Focus events to indicate whether they're due to the…
darrenburns Dec 12, 2024
15e77c8
Only apply select_on_focus in Input if the focus event wasn't produce…
darrenburns Dec 12, 2024
63030d2
Fix formatting
darrenburns Dec 12, 2024
4a989fb
Update changelog
darrenburns Dec 12, 2024
8329311
Test for Input.select_on_focus interaction with AppBlur and AppFocus
darrenburns Dec 12, 2024
0980fa0
Fix query_one docs mistake, and note that query_one searches under th…
darrenburns Dec 17, 2024
16591fb
Clarify
darrenburns Dec 17, 2024
0c6ed38
remove call_after_refresh
willmcgugan Dec 21, 2024
856ee90
added snapshot
willmcgugan Dec 21, 2024
83b62e1
snapshot
willmcgugan Dec 21, 2024
8bf172c
fix grid sizing
willmcgugan Dec 29, 2024
94b6c74
click to dismiss
willmcgugan Dec 29, 2024
c2a024d
somes docs
willmcgugan Dec 29, 2024
6d13abf
select justify
willmcgugan Dec 30, 2024
4fa861e
allow select below text
willmcgugan Dec 31, 2024
de5dc5c
fix center align
willmcgugan Dec 31, 2024
c46b906
merge
willmcgugan Dec 31, 2024
617f1bd
select mechanics
willmcgugan Jan 1, 2025
555fa62
fix padding style
willmcgugan Jan 1, 2025
16df382
select bottom righgt
willmcgugan Jan 1, 2025
ff0211f
docstring
willmcgugan Jan 1, 2025
e84eaf5
tidy
willmcgugan Jan 1, 2025
0e835a4
typing
willmcgugan Jan 1, 2025
3ac04f3
refresh option list
willmcgugan Jan 1, 2025
ccd54fd
fix ansi
willmcgugan Jan 2, 2025
273cc04
link style
willmcgugan Jan 2, 2025
05a7128
copy to clipboard
willmcgugan Jan 3, 2025
636f1c6
copy digits
willmcgugan Jan 3, 2025
5bdf1e4
last character
willmcgugan Jan 3, 2025
568152a
comments
willmcgugan Jan 3, 2025
49d2a85
test fix
willmcgugan Jan 3, 2025
458a8e5
test fixes
willmcgugan Jan 4, 2025
b38b9fc
snapshots
willmcgugan Jan 4, 2025
8fdb3ee
remove plus one
willmcgugan Jan 4, 2025
63ef97d
allow select
willmcgugan Jan 4, 2025
2386f00
added query_ancestor
willmcgugan Jan 4, 2025
32ea2a5
changelog typing fix
willmcgugan Jan 4, 2025
affe557
ignore bullets in markdown
willmcgugan Jan 4, 2025
27e92ff
snapshots
willmcgugan Jan 4, 2025
f7af710
fix snapshots
willmcgugan Jan 4, 2025
b8f3207
changelog
willmcgugan Jan 4, 2025
86e227c
typing fix
willmcgugan Jan 4, 2025
76d760d
snapshot test
willmcgugan Jan 6, 2025
7f8fdef
snapshot update
willmcgugan Jan 6, 2025
d791860
tests
willmcgugan Jan 6, 2025
11638d9
discard meta
willmcgugan Jan 6, 2025
8607933
discard meta
willmcgugan Jan 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
30 changes: 21 additions & 9 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,34 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

## Unreleased

### Changed

### Fixed

- Fixed `Pilot.click` not working with `times` parameter https://github.com/Textualize/textual/pull/5398
- Fixed select refocusing itself too late https://github.com/Textualize/textual/pull/5420
- Footer can now be scrolled horizontally without holding `shift` https://github.com/Textualize/textual/pull/5404
- The content of an `Input` will now only be automatically selected when the widget is focused by the user, not when the app itself has regained focus (similar to web browsers). https://github.com/Textualize/textual/pull/5379
- `Pilot.mouse_down` and `Pilot.mouse_up` now issue a prior `MouseMove` event, to more closely reflect real mouse actions. https://github.com/Textualize/textual/pull/5409
- Snapshots tests now discard meta, which should reduce test breaking with no visual differences https://github.com/Textualize/textual/pull/5409

### Added

- Added `Select.type_to_search` which allows you to type to move the cursor to a matching option https://github.com/Textualize/textual/pull/5403
- Updated `TextArea` and `Input` behavior when there is a selection and the user presses left or right https://github.com/Textualize/textual/pull/5400
- Added `from_app_focus` to `Focus` event to indicate if a widget is being focused because the app itself has regained focus or not https://github.com/Textualize/textual/pull/5379
- - Added `Select.type_to_search` which allows you to type to move the cursor to a matching option https://github.com/Textualize/textual/pull/5403
- Added `Offset.transpose` https://github.com/Textualize/textual/pull/5409
- Added `screen--selection` component class to define style for selection https://github.com/Textualize/textual/pull/5409
- Added `Widget.select_container` property https://github.com/Textualize/textual/pull/5409
- Added `Widget.select_all` https://github.com/Textualize/textual/pull/5409
- Added `Region.bottom_right_inclusive` https://github.com/Textualize/textual/pull/5409
- Added double click to select, triple click to select all in container https://github.com/Textualize/textual/pull/5409
- Added arbitrary text selection https://github.com/Textualize/textual/pull/5409
- Added Widget.ALLOW_SELECT classvar for a per-widget switch to disable text selection https://github.com/Textualize/textual/pull/5409
- Added Widget.allow_select method for programmatic control of text selection https://github.com/Textualize/textual/pull/5409
- Added App.ALLOW_SELECT for a global switch to disable text selection https://github.com/Textualize/textual/pull/5409
- Added `DOMNode.query_ancestor` https://github.com/Textualize/textual/pull/5409

### Changed
### Fixed

- The content of an `Input` will now only be automatically selected when the widget is focused by the user, not when the app itself has regained focus (similar to web browsers). https://github.com/Textualize/textual/pull/5379
- Updated `TextArea` and `Input` behavior when there is a selection and the user presses left or right https://github.com/Textualize/textual/pull/5400
- Footer can now be scrolled horizontally without holding `shift` https://github.com/Textualize/textual/pull/5404
- Fixed `Pilot.click` not working with `times` parameter https://github.com/Textualize/textual/pull/5398
- Fixed select refocusing itself too late https://github.com/Textualize/textual/pull/5420


## [1.0.0] - 2024-12-12
Expand Down
412 changes: 203 additions & 209 deletions poetry.lock

Large diffs are not rendered by default.

91 changes: 55 additions & 36 deletions src/textual/_ansi_theme.py
Original file line number Diff line number Diff line change
@@ -1,52 +1,71 @@
from __future__ import annotations

from rich.terminal_theme import TerminalTheme


def rgb(red: int, green: int, blue: int) -> tuple[int, int, int]:
"""Define an RGB color.

This exists mainly so that a VSCode extension can render the colors inline.

Args:
red: Red component.
green: Green component.
blue: Blue component.

Returns:
Color triplet.
"""
return red, green, blue


MONOKAI = TerminalTheme(
(12, 12, 12),
(217, 217, 217),
rgb(12, 12, 12),
rgb(217, 217, 217),
[
(26, 26, 26),
(244, 0, 95),
(152, 224, 36),
(253, 151, 31),
(157, 101, 255),
(244, 0, 95),
(88, 209, 235),
(196, 197, 181),
(98, 94, 76),
rgb(26, 26, 26),
rgb(244, 0, 95),
rgb(152, 224, 36),
rgb(253, 151, 31),
rgb(157, 101, 255),
rgb(244, 0, 95),
rgb(88, 209, 235),
rgb(196, 197, 181),
rgb(98, 94, 76),
],
[
(244, 0, 95),
(152, 224, 36),
(224, 213, 97),
(157, 101, 255),
(244, 0, 95),
(88, 209, 235),
(246, 246, 239),
rgb(244, 0, 95),
rgb(152, 224, 36),
rgb(224, 213, 97),
rgb(157, 101, 255),
rgb(244, 0, 95),
rgb(88, 209, 235),
rgb(246, 246, 239),
],
)

ALABASTER = TerminalTheme(
(247, 247, 247),
(0, 0, 0),
rgb(247, 247, 247),
rgb(0, 0, 0),
[
(0, 0, 0),
(170, 55, 49),
(68, 140, 39),
(203, 144, 0),
(50, 92, 192),
(122, 62, 157),
(0, 131, 178),
(247, 247, 247),
(119, 119, 119),
rgb(0, 0, 0),
rgb(170, 55, 49),
rgb(68, 140, 39),
rgb(203, 144, 0),
rgb(50, 92, 192),
rgb(122, 62, 157),
rgb(0, 131, 178),
rgb(247, 247, 247),
rgb(119, 119, 119),
],
[
(240, 80, 80),
(96, 203, 0),
(255, 188, 93),
(0, 122, 204),
(230, 76, 230),
(0, 170, 203),
(247, 247, 247),
rgb(240, 80, 80),
rgb(96, 203, 0),
rgb(255, 188, 93),
rgb(0, 122, 204),
rgb(230, 76, 230),
rgb(0, 170, 203),
rgb(247, 247, 247),
],
)

Expand Down
68 changes: 64 additions & 4 deletions src/textual/_compositor.py
Original file line number Diff line number Diff line change
Expand Up @@ -844,11 +844,11 @@ def get_style_at(self, x: int, y: int) -> Style:
"""Get the Style at the given cell or Style.null()

Args:
x: X position within the Layout
y: Y position within the Layout
x: X position within the Layout.
y: Y position within the Layout.

Returns:
The Style at the cell (x, y) within the Layout
The Style at the cell (x, y) within the Layout.
"""
try:
widget, region = self.get_widget_at(x, y)
Expand All @@ -866,12 +866,70 @@ def get_style_at(self, x: int, y: int) -> Style:
if not lines:
return Style.null()
end = 0

for segment in lines[0]:
end += segment.cell_length
if x < end:
return segment.style or Style.null()

return Style.null()

def get_widget_and_offset_at(
self, x: int, y: int
) -> tuple[Widget | None, Offset | None]:
"""Get the Style at the given cell, the offset within the content.

Args:
x: X position within the Layout.
y: Y position within the Layout.

Returns:
A tuple of the widget at (x, y) and the offset within the widget.
"""
try:
widget, region = self.get_widget_at(x, y)
except errors.NoWidget:
return None, None
if widget not in self.visible_widgets:
return None, None

if y >= widget.content_region.bottom:
x, y = widget.content_region.bottom_right_inclusive

x -= region.x
y -= region.y

visible_screen_stack.set(widget.app._background_screens)
lines = widget.render_lines(Region(0, y, region.width, 1))

if not lines:
return widget, None
end = 0
start = 0

offset_y: int | None = None
offset_x = 0
offset_x2 = 0

for segment in lines[0]:
end += segment.cell_length
style = segment.style
if style is not None and style._meta is not None:
meta = style.meta
if "offset" in meta:
offset_x, offset_y = style.meta["offset"]
offset_x2 = offset_x + segment.cell_length

if x <= end:
return widget, (
None
if offset_y is None
else Offset(offset_x + (x - start), offset_y)
)
start = end

return widget, (None if offset_y is None else Offset(offset_x2, offset_y))

def find_widget(self, widget: Widget) -> MapGeometry:
"""Get information regarding the relative position of a widget in the Compositor.

Expand Down Expand Up @@ -1056,7 +1114,9 @@ def render_full_update(self, simplify: bool = False) -> LayoutUpdate:
crop = screen_region
chops = self._render_chops(crop, lambda y: True)
if simplify:
render_strips = [Strip.join(chop.values()).simplify() for chop in chops]
render_strips = [
Strip.join(chop.values()).simplify().discard_meta() for chop in chops
]
else:
render_strips = [Strip.join(chop.values()) for chop in chops]

Expand Down
9 changes: 8 additions & 1 deletion src/textual/_styles_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from rich.console import Console
from rich.segment import Segment
from rich.style import Style
from rich.terminal_theme import TerminalTheme
from rich.text import Text

from textual import log
Expand Down Expand Up @@ -144,6 +145,7 @@ def render_widget(self, widget: Widget, crop: Region) -> list[Strip]:
crop=crop,
filters=widget.app._filters,
opacity=widget.opacity,
ansi_theme=widget.app.ansi_theme,
)
if widget.auto_links:
hover_style = widget.hover_style
Expand Down Expand Up @@ -176,6 +178,7 @@ def render(
crop: Region | None = None,
filters: Sequence[LineFilter] | None = None,
opacity: float = 1.0,
ansi_theme: TerminalTheme = DEFAULT_TERMINAL_THEME,
) -> list[Strip]:
"""Render a widget content plus CSS styles.

Expand Down Expand Up @@ -231,6 +234,7 @@ def render(
border_title,
border_subtitle,
opacity,
ansi_theme,
)
self._cache[y] = strip
else:
Expand Down Expand Up @@ -267,6 +271,7 @@ def render_line(
border_title: tuple[Text, Color, Color, Style] | None,
border_subtitle: tuple[Text, Color, Color, Style] | None,
opacity: float,
ansi_theme: TerminalTheme,
) -> Strip:
"""Render a styled line.

Expand Down Expand Up @@ -447,7 +452,9 @@ def post(segments: Iterable[Segment]) -> Iterable[Segment]:
if inner:
line = Segment.apply_style(line, inner)
if styles.text_opacity != 1.0:
line = TextOpacity.process_segments(line, styles.text_opacity)
line = TextOpacity.process_segments(
line, styles.text_opacity, ansi_theme
)
line = line_post(line_pad(line, pad_left, pad_right, inner))

if border_left or border_right:
Expand Down
7 changes: 6 additions & 1 deletion src/textual/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,12 @@ class MyApp(App[None]):
Setting to `None` or `""` disables auto focus.
"""

ALLOW_SELECT: ClassVar[bool] = True
"""A switch to toggle arbitrary text selection for the app.

Note that this doesn't apply to Input and TextArea which have builtin support for selection.
"""

_BASE_PATH: str | None = None
CSS_PATH: ClassVar[CSSPathType | None] = None
"""File paths to load CSS from."""
Expand Down Expand Up @@ -1531,7 +1537,6 @@ def copy_to_clipboard(self, text: str) -> None:
self._clipboard = text
if self._driver is None:
return

import base64

base64_text = base64.b64encode(text.encode("utf-8")).decode("utf-8")
Expand Down
5 changes: 4 additions & 1 deletion src/textual/color.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ def automatic(cls, alpha_percentage: float = 100.0) -> Color:
return cls(0, 0, 0, alpha_percentage / 100.0, auto=True)

@classmethod
@lru_cache(maxsize=1024)
def from_rich_color(
cls, rich_color: RichColor | None, theme: TerminalTheme | None = None
) -> Color:
Expand All @@ -192,7 +193,9 @@ def from_rich_color(
if rich_color is None:
return TRANSPARENT
r, g, b = rich_color.get_truecolor(theme)
return cls(r, g, b)
return cls(
r, g, b, ansi=rich_color.number if rich_color.is_system_defined else None
)

@classmethod
def from_hsl(cls, h: float, s: float, l: float) -> Color:
Expand Down
Loading
Loading