Skip to main content

Beautiful Diff view widget for Textual applications

Project description

Textual Diff View

Currently a WIP.

Textual Diff View is a Textual widget to display beautiful diffs in your terminal application. Originally built for Toad, this widget may be used standalone.

Diff Banner

Features

The DiffView widget displays two version of a file with syntax and changes clearly highlighted. Deleted lines / characters are shown with a red highlight. Added lines / characters are shown with a green highlight.

There are two layout options; a unified view which shows the two files top-to-bottom with highlights, and a split view which shoiws the two files next to each other.

DiffView can also display annotations ("+" and "-" for added and deleted), to improve readability for color blind users.

Textual's theming system provides a variety of themes for the diff view, both light and dark.

Example

The following is a simple app to display a diff between two files from the command line.

from textual.app import App, ComposeResult
from textual import containers
from textual.reactive import var
from textual import widgets

from textual_diff_view import DiffView, LoadError


class DiffApp(App):
    """Simple app to display a diff between two files."""

    BINDINGS = [
        ("space", "toggle('split')", "Toggle split"),
        ("a", "toggle('annotations')", "Toggle annotations"),
    ]

    split = var(True)
    annotations = var(True)

    def __init__(self, original: str, modified: str) -> None:
        self.original = original
        self.modified = modified
        super().__init__()

    def compose(self) -> ComposeResult:
        yield containers.VerticalScroll(id="diff-container")
        yield widgets.Footer()

    async def on_mount(self) -> None:
        try:
            diff_view = await DiffView.load(self.original, self.modified)
        except LoadError as error:
            self.notify(str(error), title="Failed to load code", severity="error")
        else:
            diff_view.data_bind(DiffApp.split, DiffApp.annotations)
            await self.query_one("#diff-container").mount(diff_view)


if __name__ == "__main__":
    import sys

    if len(sys.argv) != 3:
        print("Usage python tdiff.py PATH1 PATH2\nTry: python tdiff.py")
    else:
        app = DiffApp(sys.argv[1], sys.argv[2])
        app.run()

You can find this file in the examples/ directory. Run it with the following:

uv run python tdiff.py example1.rs example2.rs

Use space to toggle unified / split, and a to toggle annotations.

Screenshots

A few screenshots taken from the example app:

Split dark

Unified dark

spliut light

Unified light

Installing

Texual Diff View is on PyPI and may be installed with pip, or uv.

Here's how to install with uv:

uv add textual-diff-view

How to use

Import the widget with the following:

from textual_diff_view import DiffView

Then yield an instance of DiffView in your compose method.

The constructor accepts 4 positional arguments:

Argument type Purpose
path_original str A path to the original code
path_modified str A path to the modified code
code_original str The contents of the original code
code_modified str The contents of the modified code

Additionally, the constructor accepts the standard keyword arguments, name, id, and classes——which have the same meaning as Textual's built in widgets.

Here's a very simple example:

from textual.app import App, ComposeResult
from textual import containers

from textual_diff_view import DiffView

HELLO1 = """
def greet():
    print "Hello!"

greet()
"""

HELLO2 = """
def greet(name:str):
    print(f"Hello, {name}!")

greet('Will')
"""


class Hello(App):
    def compose(self) -> ComposeResult:
        with containers.VerticalScroll():
            yield DiffView("hello1.py", "hello2.py", HELLO1, HELLO2)


Hello().run()

Note that we put the DiffView within a VerticalScroll, so the user may scroll the container if the diff doesn't fit.

The above code will generate the following output:

simple example

Load constructor

DiffView provides an alternative constructor, DiffView.load, which also loads the code. If both your original code and modified code is on disk, this may be simpler than the standard constructor.

DiffView.load accepts the following positional arguments:

Argument type Purpose
path_original str or Path A path to the original code
path_modified str or Path A path to the modified code

Since load is a coroutine, you would typically call it from a message handler in another widget, or App, then mount it somewhere in the DOM.

The code would look something like the following:

diff_view = await DiffView.load("original.py", "modified.py")
await self.query_one("VerticalScroll").mount(diff_view)

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

textual_diff_view-0.1.1.tar.gz (8.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

textual_diff_view-0.1.1-py3-none-any.whl (9.8 kB view details)

Uploaded Python 3

File details

Details for the file textual_diff_view-0.1.1.tar.gz.

File metadata

  • Download URL: textual_diff_view-0.1.1.tar.gz
  • Upload date:
  • Size: 8.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.18 {"installer":{"name":"uv","version":"0.9.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for textual_diff_view-0.1.1.tar.gz
Algorithm Hash digest
SHA256 33cbfd550c8190eadd900aa1bb77e7aed8bec4b7c47be3253818d61e42edc8b8
MD5 15e6fb47f2d94d3a28d4672c01dc4081
BLAKE2b-256 68efa7a6f34a88586a68b7980913a9d65e5827b03b697985e8126f468f22a1a7

See more details on using hashes here.

File details

Details for the file textual_diff_view-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: textual_diff_view-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 9.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.18 {"installer":{"name":"uv","version":"0.9.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for textual_diff_view-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 f39358d284b52224dc73d772187788fff78f8cba7fe3b90efb465615e3001c72
MD5 3fd12a8e3469d584bf5ab503174c407f
BLAKE2b-256 f9e2062e838fb8db479685cd47715b41d780e08d40b8de08d19a4051342dc283

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page