From bac1a3ca5934e3140d405663d2984278b984e43e Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 27 May 2026 15:49:14 +0700 Subject: [PATCH] ansi shades --- examples/tdiff.py | 9 ++++ pyproject.toml | 2 +- src/textual_diff_view/_diff_view.py | 65 +++++++++++++++++++++++++---- uv.lock | 37 ++++++++++++---- 4 files changed, 98 insertions(+), 15 deletions(-) diff --git a/examples/tdiff.py b/examples/tdiff.py index 4fdd849..fa9bf08 100644 --- a/examples/tdiff.py +++ b/examples/tdiff.py @@ -1,5 +1,9 @@ +from rich.terminal_theme import TerminalTheme + +from textual import on from textual.app import App, ComposeResult from textual import containers +from textual import messages from textual.reactive import var from textual import widgets @@ -22,12 +26,17 @@ class DiffApp(App): def __init__(self, original: str, modified: str) -> None: self.original = original self.modified = modified + self._terminal_theme: TerminalTheme | None = None super().__init__() def compose(self) -> ComposeResult: yield containers.VerticalScroll(id="diff-container") yield widgets.Footer() + @on(messages.TerminalThemeReport) + def on_terminal_theme_report(self, event: messages.TerminalThemeReport) -> None: + self.query_one(DiffView).terminal_theme = event.theme + async def on_mount(self) -> None: try: diff_view = await DiffView.load( diff --git a/pyproject.toml b/pyproject.toml index 913f959..afd5b0f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,7 @@ dependencies = [ exclude = ["tests/__snapshots__"] [tool.uv.sources] -#textual = { path = "../textual", editable = true } +textual = { path = "../textual", editable = true } [dependency-groups] dev = [ diff --git a/src/textual_diff_view/_diff_view.py b/src/textual_diff_view/_diff_view.py index 2546a48..861b2b3 100644 --- a/src/textual_diff_view/_diff_view.py +++ b/src/textual_diff_view/_diff_view.py @@ -11,6 +11,7 @@ from rich.segment import Segment from rich.style import Style as RichStyle +from rich.terminal_theme import TerminalTheme from textual.app import ComposeResult from textual.content import Content, Span @@ -18,6 +19,7 @@ from textual import highlight from textual import events +from textual.color import Color from textual.css.styles import RulesMap from textual.selection import Selection from textual.strip import Strip @@ -382,11 +384,13 @@ class DiffView(containers.VerticalGroup): """Automaticallly enable split view if there is enough space?""" wrap: reactive[bool] = reactive(False, recompose=True) """Wrap long lines (rather than horizontal scroll).""" + terminal_theme: reactive[TerminalTheme | None] = reactive(None, recompose=True) + """System terminal theme, used in ANSI themes if supplied.""" DEFAULT_CSS = """ DiffView { width: 1fr; - height: auto; + height: auto; .diff-group { height: auto; background: $foreground 4%; @@ -515,10 +519,19 @@ class DiffView(containers.VerticalGroup): & > .diff-view--annotation-unmodified { background: ansi_default; } - & > .diff-view--annotation-hatch { - color: $ansi-foreground; - text-style: dim; + &:dark { + & > .diff-view--annotation-hatch { + color: ansi_bright_black; + text-style: dim; + } } + &:light { + & > .diff-view--annotation-hatch { + color: ansi_bright_white; + text-style: dim; + } + } + & > .diff-view--line-added { background: ansi_default; @@ -532,9 +545,17 @@ class DiffView(containers.VerticalGroup): background: ansi_default; } - & > .diff-view--line-hatch { - color: $ansi-foreground; - text-style: dim; + &:dark { + & > .diff-view--line-hatch { + color: ansi_bright_black; + text-style: dim; + } + } + &:light { + & > .diff-view--line-hatch { + color: ansi_bright_white; + text-style: dim; + } } & > .diff-view--edge-added { @@ -593,6 +614,7 @@ def __init__( name: str | None = None, id: str | None = None, classes: str | None = None, + terminal_theme: TerminalTheme | None = None, ): """Initialize diff view. @@ -619,6 +641,7 @@ def __init__( self.set_reactive(DiffView.annotations, annotations) self.set_reactive(DiffView.auto_split, auto_split) self.set_reactive(DiffView.wrap, wrap) + self.set_reactive(DiffView.terminal_theme, terminal_theme) self._grouped_opcodes: list[list[tuple[str, int, int, int, int]]] | None = None self._highlighted_code_lines: tuple[list[Content], list[Content]] | None = None @@ -734,6 +757,30 @@ def _update_styles(self) -> None: "-": self.get_visual_style("diff-view--edge-removed", partial=True), " ": self.get_visual_style("diff-view--edge-unmodified", partial=True), } + if self.app.native_ansi_color and self.terminal_theme is not None: + terminal_theme = self.terminal_theme + background = Color(*terminal_theme.background_color) + red = Color(*terminal_theme.ansi_colors[2]) + green = Color(*terminal_theme.ansi_colors[1]) + + added = background + red.with_alpha(0.15) + removed = background + green.with_alpha(0.15) + + number_styles = self._number_styles + number_styles["+"] = number_styles["+"].with_background(added) + number_styles["-"] = number_styles["-"].with_background(removed) + + annotation_styles = self._annotation_styles + annotation_styles["+"] = annotation_styles["+"].with_background(added) + annotation_styles["-"] = annotation_styles["-"].with_background(removed) + + line_styles = self._line_styles + line_styles["+"] = line_styles["+"].with_background(added) + line_styles["-"] = line_styles["-"].with_background(removed) + + edge_styles = self._edge_styles + edge_styles["+"] = edge_styles["+"].with_background(added) + edge_styles["-"] = edge_styles["-"].with_background(removed) async def prepare(self) -> None: """Do CPU work in a thread. @@ -915,6 +962,10 @@ def get_title(self) -> Content: def compose(self) -> ComposeResult: """Compose either split or unified view.""" + self._number_styles.clear() + self._annotation_styles.clear() + self._line_styles.clear() + self._edge_styles.clear() yield Static(self.get_title(), classes="title") if self.split: yield from self.compose_split() diff --git a/uv.lock b/uv.lock index 7443e52..666a561 100644 --- a/uv.lock +++ b/uv.lock @@ -805,8 +805,8 @@ wheels = [ [[package]] name = "textual" -version = "8.2.5" -source = { registry = "https://pypi.org/simple" } +version = "8.2.7" +source = { editable = "../textual" } dependencies = [ { name = "markdown-it-py", extra = ["linkify"] }, { name = "mdit-py-plugins" }, @@ -815,10 +815,33 @@ dependencies = [ { name = "rich" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/62/1e/1eedc5bac184d00aaa5f9a99095f7e266af3ec46fa926c1051be5d358da1/textual-8.2.5.tar.gz", hash = "sha256:6c894e65a879dadb4f6cf46ddcfedb0173ff7e0cb1fe605ff7b357a597bdbc90", size = 1851596, upload-time = "2026-04-30T08:02:58.956Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cd/01/c4555f9c8a692ff83d84930150540f743ce94c89234f9e9a15ff4baba3a8/textual-8.2.5-py3-none-any.whl", hash = "sha256:247d2aa2faf222749c321f88a736247f37ee2c023604079c7490bfacddfcd4b2", size = 727050, upload-time = "2026-04-30T08:03:01.421Z" }, + +[package.metadata] +requires-dist = [ + { name = "markdown-it-py", extras = ["linkify"], specifier = ">=2.1.0" }, + { name = "mdit-py-plugins" }, + { name = "platformdirs", specifier = ">=3.6.0,<5" }, + { name = "pygments", specifier = ">=2.19.2,<3.0.0" }, + { name = "rich", specifier = ">=14.2.0" }, + { name = "tree-sitter", marker = "python_full_version >= '3.10' and extra == 'syntax'", specifier = ">=0.25.0" }, + { name = "tree-sitter-bash", marker = "python_full_version >= '3.10' and extra == 'syntax'", specifier = ">=0.23.0" }, + { name = "tree-sitter-css", marker = "python_full_version >= '3.10' and extra == 'syntax'", specifier = ">=0.23.0" }, + { name = "tree-sitter-go", marker = "python_full_version >= '3.10' and extra == 'syntax'", specifier = ">=0.23.0" }, + { name = "tree-sitter-html", marker = "python_full_version >= '3.10' and extra == 'syntax'", specifier = ">=0.23.0" }, + { name = "tree-sitter-java", marker = "python_full_version >= '3.10' and extra == 'syntax'", specifier = ">=0.23.0" }, + { name = "tree-sitter-javascript", marker = "python_full_version >= '3.10' and extra == 'syntax'", specifier = ">=0.23.0" }, + { name = "tree-sitter-json", marker = "python_full_version >= '3.10' and extra == 'syntax'", specifier = ">=0.24.0" }, + { name = "tree-sitter-markdown", marker = "python_full_version >= '3.10' and extra == 'syntax'", specifier = ">=0.3.0" }, + { name = "tree-sitter-python", marker = "python_full_version >= '3.10' and extra == 'syntax'", specifier = ">=0.23.0" }, + { name = "tree-sitter-regex", marker = "python_full_version >= '3.10' and extra == 'syntax'", specifier = ">=0.24.0" }, + { name = "tree-sitter-rust", marker = "python_full_version >= '3.10' and extra == 'syntax'", specifier = ">=0.23.0" }, + { name = "tree-sitter-sql", marker = "python_full_version >= '3.10' and extra == 'syntax'", specifier = ">=0.3.11" }, + { name = "tree-sitter-toml", marker = "python_full_version >= '3.10' and extra == 'syntax'", specifier = ">=0.6.0" }, + { name = "tree-sitter-xml", marker = "python_full_version >= '3.10' and extra == 'syntax'", specifier = ">=0.7.0" }, + { name = "tree-sitter-yaml", marker = "python_full_version >= '3.10' and extra == 'syntax'", specifier = ">=0.6.0" }, + { name = "typing-extensions", specifier = ">=4.4.0,<5.0.0" }, ] +provides-extras = ["syntax"] [[package]] name = "textual-dev" @@ -839,7 +862,7 @@ wheels = [ [[package]] name = "textual-diff-view" -version = "0.1.4" +version = "0.1.5" source = { editable = "." } dependencies = [ { name = "textual" }, @@ -854,7 +877,7 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "textual", specifier = ">=8.2.5" }] +requires-dist = [{ name = "textual", editable = "../textual" }] [package.metadata.requires-dev] dev = [