Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 37 additions & 112 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,16 @@
# diffr

Structural diffs with a streaming API and an interactive terminal frontend.
diffr is derived from [difftastic](https://github.com/Wilfred/difftastic)
(MIT, Wilfred Hughes) and its terminal UI from
[hunk](https://github.com/modem-dev/hunk) (MIT, Modem Labs). See `NOTICE`
for every upstream and its license.
> diffr is experimental and in alpha. API breakages are possible at any time, although we will do our best to warn you of them.

## Installation

CLI and terminal UI (Apple Silicon macOS and x64 Linux):

```sh
brew install devdotfast/tap/diffr
```

The CLI, from crates.io (prebuilt via [cargo-binstall](https://github.com/cargo-bins/cargo-binstall), or compiled):
diffr is a Rust-based structural diffing (AST-aware) library. It is also packaged as a CLI with a built-in TUI, and it has a WASM plugin system for extensibility.

```sh
cargo binstall diffr-cli
cargo install diffr-cli --locked
```
## Features

From source: `cargo xtask install` (requires Rust and [Bun](https://bun.sh)).

## Configuration

To turn on semantic diff summarization:

1. Run `diffr config`
2. Search for 'summarization'
- Enable in the dropdown
- Add your API key for Gemini if you don't already have on your path
1. Interactive TUI with AST-aware code folding
3. Sane defaults for AI coding
- Summarize long changes as pseudocode
- Collapse tests and docs
3. WASM-based plugin system

## Usage

Expand All @@ -42,105 +22,50 @@ diffr --cached # staged changes
diffr main...HEAD -- src/ # merge-base comparison
```

You can also use `diffr` in streaming mode, which is useful for TUI or GUI applications:
## Installation

CLI and terminal UI (Apple Silicon macOS and x64 Linux):

```sh
diffr main HEAD --format ndjson
brew install devdotfast/tap/diffr
```

## Architecture

When you run `diffr ${commit_range_exp}`, the following happens:

1. Commits loaded from git
2. Plugins (explained in more detail later) load
3. Each file is parsed via tree-sitter & diffed using difftastic's ast/ast diffing algorithm
- This produces an alignment of file / file
- Note: because of known upstream limitations, the diffing algorithm is quite CPU/Mem intensive.
We fall back to a textual diffing algorithm in case of issue
4. Plugins define which AST nodes are present in the API + folded by default.

### Plugin API

`diffr` has a powerful, wasm-based plugin API which customizes how it presents changed files.
The CLI, from crates.io (prebuilt via [cargo-binstall](https://github.com/cargo-bins/cargo-binstall), or compiled):

For example, the following are all implemented as plugins:
```sh
cargo binstall diffr-cli
cargo install diffr-cli --locked
```

- Context Folding: showing relevant context, like a function signature + closing brace (if applicable)
- Algorithm Summarization: using an LLM to summarize long algorithms into pseudocode
- Comment collapsing: collapsing long LLM comments + function bodies & expanding both at once
- Collapsing tests by default
From source: `cargo xtask install` (requires Rust and [Bun](https://bun.sh)).

The [Rust SDK's `Plugin` trait](crates/diffr-plugin-sdk/src/lib.rs) exposes
four important methods:
## History

```rust
// rust bindings of underlying WASM plugin API
use diffr_plugin_sdk::{FileEntry, Move, Pairing, QuerySource, Source};
diffr is a fork of the lovely [difftastic](https://github.com/Wilfred/difftastic)(MIT, Wilfred Hughes) and its terminal UI from
[hunk](https://github.com/modem-dev/hunk) (MIT, Modem Labs).

pub trait Plugin: Sized {
/// Options are configuration for the plugin
type Options: serde::de::DeserializeOwned;
We forked [difftastic](https://github.com/Wilfred/difftastic)(MIT, Wilfred Hughes) because of the following 3 technical reasons:

/// new loads the plugin from its configuration; this is to allow plugins to fail
/// early if user config isn't set correctly
fn new(options: Self::Options) -> anyhow::Result<Self>;
1. TUI affordance
2. AST-based fold matching
3. Plugin System

/// queries return tree-sitter queries to add metadata to the tree-sitter tree
/// this means that the plugins can backpack off of the tree-sitter parse that the
/// diffing algorithm does.
fn queries(&self) -> anyhow::Result<Vec<QuerySource>>;
We hope in the future that we can find some way to upstream some / all of these features. Obviously everyone works differently and this is quite an opinionated stance on things, so we imagine this may take time.

/// classify (bad name lol) runs classification of files into generated, test, etc.
/// Useful to prevent wasteful semantic diffing for things users will skip.
/// emits tags that clients can make use of
fn classify(&self, file: &FileEntry) -> anyhow::Result<Vec<String>>;
## Known Limitations

/// mutate emits a series of structured mutations ('Moves') to the parsed diff type
/// (e.g., fold X function body, show Y lines of context around it, etc.)
fn mutate(&self, file: &FileEntry, sides: &Pairing<Source>)
-> anyhow::Result<Vec<Move>>;
}
```
1. Semantic diff summarization is only supported for the Gemini class of models right now. Turn it on via:
a. Through the TUI:
- Run `diffr config`
- Search for 'summarization'
- Enable in the dropdown
- Add your API key for Gemini if you don't already have on your path
b. Through the [Whiteboard app](https://github.com/devdotfast/whiteboard).
2. The plugin API is a bit awkward and will be simplified radically in the coming releases.

```mermaid
sequenceDiagram
participant D as diffr
participant P as Plugins
participant G as Git
participant E as Diff engine
participant C as UI / API consumer

D->>P: new(options), queries()
P-->>D: Plugin instances and query sources
D->>G: Load changed files for comparison
G-->>D: Before and after versions
loop Each changed file
D->>P: classify(file)
P-->>D: File tags
D->>E: Compare versions using tags and queries
alt Structural comparison available
E->>E: Parse with tree-sitter and diff with difftastic
else Generated file or structural fallback
E->>E: Compute line diff
end
E-->>D: Aligned regions and folds
loop Each enabled plugin in order
D->>P: mutate(file, sides)
P-->>D: Presentation moves
D->>D: Apply moves to regions and fold state
end
D-->>C: Diff with initial fold state
end
```
### Plugin API

The WASM interface is defined in [`wit/plugin.wit`](wit/plugin.wit).
The SDK's [`export!` macro](crates/diffr-plugin-sdk/src/lib.rs) and
[guest adapter](crates/diffr-plugin-sdk/src/guest.rs) expose a Rust plugin
as a WASM component; the [Wasmtime runner](src/plugin/wasm.rs) loads and
calls it. See the [context plugin](plugins/context/src/lib.rs) and its
[Rust query](plugins/context/queries/rust.scm) for a concrete implementation,
or [Writing plugins](docs/plugins.md) for the full guide.
`diffr` has a powerful, wasm-based plugin API which customizes how it presents changed files. For more details, read the [docs](./docs/plugin.md).

## License

Expand Down
48 changes: 0 additions & 48 deletions docs/cli.md

This file was deleted.

Loading
Loading