A Chinese translation is available at README-cn.org.
Org renders diff source blocks with
Diff mode, but their fine-grained highlighting usually disappears. Org
copies font-lock face properties from the temporary buffer used to fontify a
block, while Diff mode stores its refinement in overlays. org-diff-src
provides org-diff-src-mode, a small diff-mode derivative that records the
same refinement as face text properties, so the changed words stay colored
inside the Org buffer.
The result is an ordinary Org source block: it can be folded, edited, and
exported, and no Ediff session is started. Diffs can come from two strings,
two files, two buffers, or two Org subtrees. A three-line configuration also
lets the package take over the stock display of every diff block.
For a behavior-only specification aimed at independent implementations, see CLEAN-ROOM.org (Chinese: CLEAN-ROOM-cn.org).
- Render any unified diff as an Org source block.
- Keep Diff mode’s refinement in the Org buffer, not only the line-level added and removed faces.
- Highlight the changed words, and the characters within them that differ,
with
diff-refine-removed,diff-refine-added, anddiff-refine-changed. - Work without setup through the built-in
org-diff-srclanguage. - Take over existing
diffblocks with a three-line configuration. - Compare two Org subtrees, two files, or two buffers.
- Copy subtrees without changing the kill ring or the system clipboard.
- Start no Ediff session and require no package beyond Org and Diff mode.
- Use standard completion; Consult is not required.
- Fall back to line-level highlighting if Diff mode’s refinement internals change.
- Emacs 27.1 or later. The Org version bundled with Emacs 27.1 (Org 9.3) is sufficient.
- A
diffexecutable. GNU diffutils is recommended. Labels use--labelwhen available and fall back to the portable-Loption.
TomoElpa is the recommended way to install
org-diff-src. It provides snapshots built from the latest commit and tagged
releases. The archive paths have no browsable directory listing; the landing
page above lists the packages, and the URLs below are the ones package.el
reads. Add the snapshots archive, refresh the package index, and let
use-package install and configure the package (use-package is bundled with
Emacs 29 and later; on Emacs 27–28, install it from GNU ELPA first):
(require 'package)
(add-to-list 'package-archives
'("tomoelpa-snapshots" .
"https://blog.prayhand13013.top/tomoelpa/snapshots/") t)
(package-initialize)
(package-refresh-contents) ; once, or after the archive changes
(require 'use-package)
(use-package org-diff-src
:ensure t
:pin tomoelpa-snapshots
:config
(org-diff-src-setup '("diff"))
(setq org-diff-src-default-language "diff"))The :config block is the optional
diff takeover described below.
Remove it to keep the package’s default org-diff-src language.
If you prefer tagged releases over snapshots, use the releases archive and pin to it instead:
(add-to-list 'package-archives
'("tomoelpa-releases" .
"https://blog.prayhand13013.top/tomoelpa/releases/") t)
(package-refresh-contents)
(use-package org-diff-src
:ensure t
:pin tomoelpa-releases
:config
(org-diff-src-setup '("diff"))
(setq org-diff-src-default-language "diff"))Without use-package, install the package once with
M-x package-install RET org-diff-src RET.
On Emacs 30.1 and later, the :vc keyword of use-package can download
org-diff-src directly from GitHub, without a package archive:
(use-package org-diff-src
:vc (:url "https://github.com/TomoeMami/org-diff-src" :rev :newest)
:config
(org-diff-src-setup '("diff"))
(setq org-diff-src-default-language "diff")):rev :newest checks out the latest commit. Without :rev, :vc defaults to
the last release; this repository has no release tags, so keep :rev :newest.
:vc needs git and network access. To update later, run
M-x package-vc-upgrade RET org-diff-src RET.
On older Emacs versions, use TomoElpa or straight.el instead.
(straight-use-package
'(org-diff-src :type git :host github :repo "TomoeMami/org-diff-src"))With use-package:
(use-package org-diff-src
:straight (:type git :host github :repo "TomoeMami/org-diff-src")
:commands (org-diff-src-setup
org-diff-src-compare-headings
org-diff-src-compare-files
org-diff-src-compare-buffers
org-diff-src-insert-diff-file
org-diff-src-insert-from-kill
org-diff-src-wrap-region
org-diff-src-refontify-block))Clone the repository and add it to load-path:
(add-to-list 'load-path "/path/to/org-diff-src")
(require 'org-diff-src)The package does not change diff blocks by default; it adds the
org-diff-src language. To make every diff block use org-diff-src-mode,
and to make newly inserted blocks use the diff language, add three lines to
your init file:
(require 'org-diff-src) ; load the package and Org
(org-diff-src-setup '("diff")) ; diff -> org-diff-src-mode
(setq org-diff-src-default-language "diff") ; insert new blocks as diffThe second line is the actual override. Org resolves diff to diff-mode by
default, which gives a diff block only the line-level added and removed
faces. Mapping diff to org-diff-src-mode adds Diff mode’s refinement. The
mapping applies to existing and new blocks; a block that is already displayed
is refined at the next fontification, or immediately with
M-x org-diff-src-refontify-block.
The third line changes only the language written by org-diff-src-insert and
the comparison commands. Omit it to keep using org-diff-src as the block
language; the examples in Usage assume that default.
This is an explicit opt-in: every diff block is affected, including blocks
created by other packages. org-diff-src-mode derives from diff-mode, so
C-c ' editing, hunk commands, navigation, and folding keep working. To
restore the stock diff-mode display, remove the second line and set
org-diff-src-default-language back to "org-diff-src". Only unified diffs
are refined.
Do not defer loading: the mapping must exist before Org first fontifies a
diff block.
(use-package org-diff-src
:straight (:type git :host github :repo "TomoeMami/org-diff-src")
:demand t
:config
(org-diff-src-setup '("diff"))
(setq org-diff-src-default-language "diff"))The built-in org-diff-src language needs no setup:
(require 'org-diff-src)
(org-diff-src-insert
(org-diff-src-diff-strings "one\ntwo\n" "one\nTWO\n"))Open an Org buffer and run:
M-x org-diff-src-compare-headings
Select the original heading, then the new heading. Both subtrees are copied
without touching the kill ring or clipboard, normalized to level 1, compared
with diff, and inserted at point as an org-diff-src source block.
For example, given:
* Original
Some text.
foo bar baz
* New
Some text.
foo BAR baz
selecting Original and then New produces:
--- original: Original +++ new: New @@ -1,3 +1,3 @@ -* Original +* New Some text. -foo bar baz +foo BAR baz
The changed words (Original / New and bar / BAR) are colored with Diff
mode’s refinement faces. The heading lines are part of the comparison, so a
changed title appears in the diff as well.
The default language is org-diff-src. Org resolves it to
org-diff-src-mode automatically, so newly inserted org-diff-src blocks
need no setup.
If you have blocks written as ediff (for example from the original snippet),
register the alias once:
(require 'org-diff-src)
(org-diff-src-setup)org-diff-src-setup maps each language in org-diff-src-extra-languages
(default ("ediff")) to org-diff-src-mode. Run it before Org fontifies
those blocks.
To make newly inserted blocks use ediff as well:
(setq org-diff-src-default-language "ediff")You can register more aliases explicitly:
(org-diff-src-setup '("ediff" "diff"))To take over diff blocks, see
Minimal configuration.
- Copy a diff (for example from Magit) and run
M-x org-diff-src-insert-from-kill. - Select the diff text and run
M-x org-diff-src-wrap-regionto replace the region with a fontified block. - Run
M-x org-diff-src-insert-diff-fileto insert the contents of a diff file.
M-x org-diff-src-compare-filesprompts for an original file and a new file.M-x org-diff-src-compare-buffersprompts for an original buffer and a new buffer.
Both commands insert the result at point in the current Org buffer.
Org normally refontifies source blocks after edits. If Lisp code changes a block without triggering fontification, run:
M-x org-diff-src-refontify-block
| Command | Purpose |
|---|---|
org-diff-src-setup | Register language aliases such as ediff. |
org-diff-src-compare-headings | Compare two subtrees in the current Org buffer. |
org-diff-src-compare-files | Compare two files. |
org-diff-src-compare-buffers | Compare two buffers. |
org-diff-src-insert-diff-file | Insert a diff file as a source block. |
org-diff-src-insert-from-kill | Insert the current kill as a source block. |
org-diff-src-wrap-region | Replace the region with a source block. |
org-diff-src-refontify-block | Refontify the block at point. |
The diff-generation and insertion functions are independent of the interactive commands, so they can be used from other packages.
(org-diff-src-insert DIFF &optional LANGUAGE)- Insert
DIFFas a fontified source block at point.LANGUAGEdefaults toorg-diff-src-default-languageand must map toorg-diff-src-mode; the built-inorg-diff-srclanguage does so automatically. Return the position after the block. IfDIFFis nil or empty, insertorg-diff-src-no-diff-message. (org-diff-src-setup &optional LANGUAGES)- Register
LANGUAGES(defaultorg-diff-src-extra-languages) fororg-diff-src-mode. Return the list of registered languages. (org-diff-src-diff-strings OLD NEW &optional OLD-LABEL NEW-LABEL CODING)-
Return a unified diff between the strings
OLDandNEW. (org-diff-src-diff-files OLD-FILE NEW-FILE &optional OLD-LABEL NEW-LABEL)- Return a unified diff between two files. Remote files are copied locally first.
(org-diff-src-diff-buffers OLD-BUFFER NEW-BUFFER &optional OLD-LABEL NEW-LABEL)- Return a unified diff between the entire contents of two buffers.
org-diff-src-mode- The major mode used for the block. It derives from
diff-mode, setsdiff-refinetofont-lock, and records refinement as text properties.
Example:
(with-current-buffer (get-buffer-create "*diff-demo*")
(org-mode)
(org-diff-src-insert
(org-diff-src-diff-strings
"The quick brown fox\n"
"The quick red fox\n"
"before" "after")))To compare two buffers programmatically:
(org-diff-src-insert
(org-diff-src-diff-buffers (get-buffer "notes-a")
(get-buffer "notes-b")
"before" "after"))The default source language is org-diff-src; Org resolves it to
org-diff-src-mode through org-src-get-lang-mode, so no entry in
org-src-lang-modes is needed. Aliases registered with org-diff-src-setup
are resolved the same way.
Org copies font-lock face properties, but not overlays, from the temporary
buffer it uses to fontify a source block. Diff mode implements its refinement
with overlays, so the refinement is normally lost when a diff is shown inside
Org.
org-diff-src-mode works around this:
- It derives from
diff-mode. - It sets
diff-refinetofont-lockso that refinement is computed by font-lock. - It replaces the use of
diff--font-lock-refinedindiff-font-lock-keywordswith a function that copies the refinement overlay faces intofacetext properties. - Org’s
org-src-font-lock-fontify-blockcan then copy those properties into the Org buffer.
If a future Emacs version removes the Diff mode refinement internals,
org-diff-src-mode keeps the normal line-level highlighting instead of
failing.
This is why the default block language is org-diff-src rather than diff.
Org resolves diff to plain diff-mode unless you register it as an alias;
see the minimal configuration.
org-diff-src-diff-switches- Switches passed to
diff-command. The default is("-u"). If you add switches, keep-uin the list because the refinement code expects a unified diff. org-diff-src-no-diff-message- Text inserted when the diff is empty. The
default is
# No differences. org-diff-src-default-language- Language used for inserted blocks. The
default is
"org-diff-src". org-diff-src-extra-languages- Languages registered by
org-diff-src-setup. The default is("ediff"). org-diff-src-fontify-immediately- Whether to fontify newly inserted
blocks immediately. The default is
t; turn it off for very large diffs. - Faces
- The refinement uses
diff-refine-removed,diff-refine-added, anddiff-refine-changed. Customize them withM-x customize-faceor incustom-set-faces.
The mode sets diff-refine buffer-locally. You do not need to change the
global value of diff-refine. Run M-x customize-group RET org-diff-src RET
to see all options.
- Only unified diffs are refined. Context diffs and normal diffs are displayed as plain text.
- The refinement colors are Emacs display properties. HTML or PDF export of the Org file shows the diff text, but not necessarily the refinement colors.
- Very large diffs can be slow to fontify because refinement is computed per changed hunk and word.
- Labels for the two diff sides are reduced to one line.
- If a future Emacs removes the Diff mode refinement internals, the package falls back to line-level highlighting.
org-diff-src-compare-headingscompares whole subtrees, including the heading lines. Useorg-diff-src-diff-stringsif you want to compare only selected body text.- This package renders static diffs. It is not a replacement for Ediff or Magit when you need interactive merging.
Bug reports, feature requests, and patches are welcome at the issue tracker.
Before sending a change, run:
make compile
make test
make checkdoc
make package-lintThe ERT suite is in test/org-diff-src-test.el.
The package is a single file, org-diff-src.el. It is distributed through
TomoElpa; the recipe and build
configuration live in the tomoelpa
repository.
A producer is simply a function that returns a unified diff string. For example:
(defun my/org-diff-src-from-git (rev-a rev-b)
"Return a unified diff between REV-A and REV-B."
(with-temp-buffer
(let ((status (call-process "git" nil t nil
"diff" "-u" (concat rev-a ".." rev-b))))
(unless (memq status '(0 1))
(error "git diff failed: %s" (buffer-string))))
(buffer-string)))
(org-diff-src-insert (my/org-diff-src-from-git "HEAD~1" "HEAD"))The rendering layer only cares about the unified diff text, so new producers
do not need to touch org-diff-src-mode.
A behavior-only specification for independent implementations is in CLEAN-ROOM.org (Chinese: CLEAN-ROOM-cn.org). It is written for implementers who do not want to read this implementation.
GPL-3.0-or-later. See LICENSE.
The original implementation was a personal Org configuration snippet for inserting Ediff-like diffs; this package generalizes its diff rendering and insertion layer.
