Repository navigation
Conversation
Words that are the same on both sides of a changed comment, string or text line were listed as changes. Leave them out, as unchanged code tokens are.
jrosiek
added a commit
to jrosiek/difftastic.nvim
that referenced
this pull request
Oct 9, 2026
…oads (#4) A diff now runs in an explicit repository, is titled after what it shows, and has bars that say which file is shown and how it changed. Added and deleted files take a single pane, changed text stands out more, and a file difft fails on no longer fails the whole diff. The plugin downloads a library matching its version and a difft with word-level JSON output. ## Explicit repository directory - **`open(revset, opts)` takes `opts.cwd`**, a directory inside the repository to diff, so a caller (for example another process talking to Neovim over its socket) can name the repository instead of depending on the current window. Without it, the current window's working directory is used. - **`:Difft` takes the directory when it is run**, before the view opens, so `:lcd` / `:tcd` of the window it was run from are honoured even when the view opens after startup settles. - **Every git, jj and difft command runs in that repository's root.** Previously they inherited the process's current directory, which follows the current window: a diff could pick up the directory of the new diff tab, or of a window switched to while it was still loading. - **jj diffs from a subdirectory read the right files.** `jj diff` now runs from the repository root, so the paths it reports match the paths the file contents are read from. - **The diff tab works in the diffed directory** (`:tcd`), leaving the tab it was opened from alone. - **With `multiple_diffs`,** the same revset in a different directory opens its own tab instead of switching to the other repository's. - The library's diff functions take the directory as an optional last argument (`cwd` in the asynchronous spec); without it, the process's directory at the time of the call is used. ## Diff title - **The side panel's header starts with a title and a subtitle**, both derived by the library from the diff: - a commit diff: the title of its oldest commit, and `+N more` for the commits after it; - a staged or unstaged git diff, or the jj working copy: the kind of diff, and when its files were read (`as of 9 Oct 16:30:04`). - The title wraps to the panel width; when a resize changes its height, the tree is rebuilt below it, keeping collapsed directories and the cursor row. - Highlight groups `DifftDiffTitle` (links to `Title`) and `DifftDiffSubtitle` (the text colour blended into the background). ## Bars on top of the panel and the panes - **The side panel's bar** shows the shown file's status (`● modified`, `+ added`, `- deleted`, `➜ renamed`), right-aligned next to the panes. - **Each diff pane's bar** shows the file on its side: the file-type icon, the file name, its directory dimmed, and that side's line count on the right (removed lines in the base pane, added lines in the head pane). A renamed file's base pane shows its old path. - **Narrow panes** give up the directory first (cut from its left), then the count, then cut the file name; every cut uses `…`, and the bars never get wider than their window. - The bars share the panel's background and are underlined; the unfocused windows' bars are dimmed. Highlight groups `DifftBar`, `DifftBarNC` and `DifftBarMuted`. - **The view's buffers are named** `difftastic://<tab>/panel`, `difftastic://<tab>/base/<path>` and `difftastic://<tab>/head/<path>`, so status lines show what a window holds instead of `[Scratch]`. ## One pane for added and deleted files - A file that exists on one side only is shown in one pane that takes the width of both. The other pane comes back, on its side and in the panes' last ratio (also one the user dragged), for the next file with both sides. - Focus moves to the remaining pane when the focused one closes; `<Tab>` from the tree and remembered positions go to the pane that is shown. ## Changed text - **Changed text within a line** has a stronger background and an underline in the added or removed colour half blended into the background (in the text's own colour where terminals draw no underline colours); the text keeps its syntax colour. - **Secondary text** (the subtitle, the directory in the bars) is the text colour blended into the background (`DifftDiffSubtitle`, `DifftBarMuted`), readable with themes that give `NonText` the background colour. ## Header and loading window - **The header box is replaced by text with thin rules**, drawn as underlined blank rows like the bars' underlines (`DifftTreeRule`). - **A long range** in the header (e.g. `tags/v5.8.2-stable~227 → tags/v5.8.2-stable`) continues after its arrow on a second row; a side still too long for its row is cut at its start, keeping its end. - **The loading window** loses its border the same way: a bar on top with the share done (`DifftLoadingDone`), or a spinner while the number of files is unknown, then the range, the last message and a rule. - `DifftTreeTitle` and `DifftTreeDivider` are removed, as nothing uses them any more. ## Side panel width - A side panel narrowed by Neovim, for example in a window that opened small, grows back to its width when Neovim grows. A width the user gives the panel is kept through resizes. ## Robustness - **A file difft fails on** (e.g. crashes on its syntax) no longer fails the whole diff: for git it is compared again in difft's text mode, alone; for jj, which runs difft once over all files, all files are compared again as text. jj only warns when its diff tool fails and still exits successfully; that warning now counts as a failure. Such a file shows `≡ line by line` in the side panel's bar. - **The panes' split** gives an odd extra column to the base pane everywhere, so evening panes that are already even no longer moves the split. ## Downloads - **The prebuilt library** comes from the GitHub repository the plugin was cloned from (its origin), so a fork gets its own releases, else from clabby/difftastic.nvim: the newest release at or before the installed plugin commit, or the latest release when the plugin is not a git clone. A library from another repository, or one that no longer matches the installed plugin, is downloaded again at startup; `:DifftUpdate` downloads the matching release again instead of the latest. - **difft** is downloaded with `download = true` from the latest release of [jrosiek/difftastic](https://github.com/jrosiek/difftastic), whose JSON output lists only the words that changed in comments, strings and text (Wilfred/difftastic#1075), into the library's directory. The library runs the downloaded difft, for jj too, and difft from `PATH` until it is there or when it cannot be downloaded. - **Without any difft**, a diff stops with a message saying how to get one; a diff opened while a download it needs is still running waits for it in the loading window. - **`:checkhealth difftastic-nvim`** shows the library and the difft in use, where they come from and their versions, and whether nui.nvim is installed. ## Testing - End-to-end tests against scratch git and jj repositories: the given repository is diffed rather than the current one, an asynchronous diff keeps its directory when the current one changes while it runs, a directory outside a repository is reported, a jj diff from a subdirectory reads both sides, and the library's titles for single commits, ranges (`..` and `...`), staged, unstaged and jj diffs. - View tests for the title and subtitle (wrapping, rebuilding the tree on resize), the three bars (statuses, renames, counts, the narrowing order down to one column, colours), one pane for added and deleted files (also straight from an added to a deleted file), the ruled header and the loading window (spinner, percentage, re-centring). - Tests with a child Neovim and a real screen for the pane ratio through a one-pane file and the side panel width after resizes. - Download tests with GitHub stubbed (the matching release, a library from another repository, difft unpacked, made executable and recorded), the library running a given difft with none on `PATH` (git and jj), waiting for a download before a diff, and the health report. Tests use a data directory of their own. - Tests for the buffer names, the text fallback (a stand-in difft that fails unless asked for text mode, for git and jj), the colours, the range rows, and the split after a real mouse drag of the side panel's border. - The Rust tests, clippy, rustfmt and the Lua suite pass. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
In the JSON output, a changed line of code lists only the tokens that changed, but a changed comment, string or text line lists every word on it, the unchanged ones included. difft already knows which words really changed (
NovelWordvsUnchangedPartOfNovelItem) and uses that in its own display, emphasising only the changed words; the JSON drops the distinction, because every novel match kind is serialised the same way.This leaves those unchanged words out of
changes, as unchanged code tokens already are. JSON consumers can then show intraline changes for comments, strings and text the same way difft displays them, and the meaning ofchangesis the same for code and text: what changed on the line.It covers the line-oriented fallback too (unsupported languages,
DFT_BYTE_LIMIT,DFT_GRAPH_LIMIT,DFT_PARSE_ERROR_LIMIT), since that path produces the same match kinds.Example, a changed Rust comment:
changescontents before (each side lists all 21 tokens of the line):after:
A line inside a changed comment block that is the same on both sides now has an empty
changeslist, like an unchanged code line within a hunk.Addresses #658.
🤖 Generated with Claude Code