The difftastic
Emacs package is designed to integrate
difftastic - a structural diff
tool - into your Emacs workflow, enhancing your code review and comparison
experience. This package automatically displays difftastic
’s output within
Emacs using faces from your user theme, ensuring consistency with your overall
coding environment.
- Configure faces to your likening. By default
magit-diff-*
faces from your user them are used for consistent visual experience. - Chunks and file navigation using
n
/N
(orM-n
) andp
/P
(orM-p
), as well asC-M-f
,C-M-b
, andC-M-SPC
in generated diffs. - Hide chunks and files with
TAB
when on a chunk/file header. UseC-u TAB
to hide whole file. - DWIM workflows from
magit
. - Use difftastic do compare files and buffers (also directly from
dired
). - Rerun
difftastic
withg
to use current window width to “reflow” content and/or to force language change (when called with prefix). - Use double prefix argument to specify all
difftastic
arguments.
difftastic
up-to-date is using Emacs’
built-in package manager. difftastic
is available in the MELPA
repository. Refer to https://melpa.org/#/getting-started for how to install a
package from MELPA.
Please see Configuration section for example configuration.
You can use any of the package managers that supports installation from MELPA.
It can be one of (but not limited to): one of the built-in package
,
use-package
, or any other package manger that handles autoloads generation,
for example (in alphabetical order)
Borg,
Elpaca,
Quelpa, or
straight.el.
use-package
. Add the following to
your Emacs configuration file (usually ~/.emacs
or ~/.emacs.d/init.el
):
(use-package difftastic
:defer t
:vc (:url "https://github.com/pkryger/difftastic.el.git"
:rev :newest)))
Alternatively, you can do a manual checkout and install it from there, for example:
- Clone this repository to a directory of your choice, for example
~/src/difftastic
. - Add the following lines to your Emacs configuration file:
(use-package difftastic
:defer t
:vc t
:load-path "~/src/difftastic")
Yet another option is to use any of the package managers that supports
installation from GitHub or a an existing checkout. That could be
package-vc
, or any of package managers listed in
Installing from MELPA.
magit
) to be loaded
at startup. If you want to avoid this, ensure autoloads set up on Emacs
startup. See Installing from MELPA a few package
managers that can generate autoloads when package is installed.
- Clone this repository to a directory of your choice, for example
~/src/difftastic
. - Add the following line to your Emacs configuration file:
(add-to-list 'load-path "~/src/difftastic") (require 'difftastic) (require 'difftastic-bindings)
difftastic
’s autoloads set up at Emacs startup.
If you have installed difftastic
using built-in package
or use-package
then you should be all set.
To configure difftastic
commands in relevant magit
prefixes and keymaps,
use the following code snippet in your Emacs configuration:
(difftastic-bindings-mode)
Or, if you use use-package
:
(use-package difftastic-bindings
:ensure difftastic ;; or nil if you prefer manual installation
:config (difftastic-bindings-mode))
By default this will bind:
M-d
todifftastic-magit-diff
andM-c
todifftastic-magit-show
inmagit-diff
prefix,M-RET
todifftastic-magit-show
inmagit-blame
transient prefix and inmagit-blame-read-only-map
keymap,M-d
todifftastic-magit-diff-buffer-file
inmagit-file-dispatch
prefix,M-\=
todifftastic-dired-diff
indired-mode-map
.
Please refer to difftastic-bindings-alist
documentation to see how to change
default bindings. You need to toggle the difftastic-bindings-mode
off and on
again to apply the changes.
The difftastic-bindings=mode
was designed to have minimal dependencies and be
reasonably fast to load, while providing a mechanism to bind difftastic
commands, such that they are available in relevant contexts.
If you don’t want to use mechanism delivered by difftastic-bindings-mode
you
can write your own configuration. As a starting point the following snippets
demonstrate how to achieve partial effect similar to the one provided by
difftastic-bindings-mode
in default configuration:
(require 'difftastic)
(require 'transient)
(let ((suffix [("D" "Difftastic diff (dwim)" difftastic-magit-diff)
("S" "Difftastic show" difftastic-magit-show)]))
(with-eval-after-load 'magit-diff
(unless (equal (transient-parse-suffix 'magit-diff suffix)
(transient-get-suffix 'magit-diff '(-1 -1)))
(transient-append-suffix 'magit-diff '(-1 -1) suffix))))
(let ((suffix '("M-RET" "Difftastic show" difftastic-magit-show)))
(with-eval-after-load 'magit-blame
(unless (equal (transient-parse-suffix 'magit-blame suffix)
(transient-get-suffix 'magit-blame "b"))
(transient-append-suffix 'magit-blame "b" suffix))
(keymap-set magit-blame-read-only-mode-map
"M-RET" #'difftastic-magit-show)))
Or, if you use use-package
:
(use-package difftastic
:defer t
:init
(use-package transient ; to silence compiler warnings
:autoload (transient-get-suffix
transient-parse-suffix))
(use-package magit-blame
:defer t :ensure magit
:bind
(:map magit-blame-read-only-mode-map
("M-RET" . #'difftastic-magit-show))
:config
(let ((suffix '("M-RET" "Difftastic show" difftastic-magit-show)))
(unless (equal (transient-parse-suffix 'magit-blame suffix)
(transient-get-suffix 'magit-blame "b"))
(transient-append-suffix 'magit-blame "b" suffix)))
(use-package magit-diff
:defer t :ensure magit
:config
(let ((suffix [("M-d" "Difftastic diff (dwim)" difftastic-magit-diff)
("M-c" "Difftastic show" difftastic-magit-show)]))
(unless (equal (transient-parse-suffix 'magit-diff suffix)
(transient-get-suffix 'magit-diff '(-1 -1)))
(transient-append-suffix 'magit-diff '(-1 -1) suffix)))))
The following commands and functions are meant to help invoking difftastic
depending on context and desired outcome.
difftastic-magit-diff
- show the result ofgit diff ARGS -- FILES
withdifftastic
. This is the main entry point for DWIM action, so it tries to guess revision or range.difftastic-magit-show
- show the result ofgit show ARG
withdifftastic
. It tries to guessARG
, and ask for it when can’t. When called with prefix argument it will ask forARG
.difftastic-magit-diff-buffer-file
- show diff for the blob or file visited in the current buffer withdifftastic
. When the buffer visits a blob, then show the respective commit. When the buffer visits a file, then show the differences betweenHEAD
and the working tree.difftastic-forge-pullreq-show-diff
- show the result ofgit diff BASE...HEAD
withdifftastic
. When buffer is aforge
pull request buffer, of point is at a pull-request, then show diff for that pull request. Otherwise, ask for pull request branches to compare.difftastic-forge-create-pulreq-show-diff
- show diff for a new pull request. This has been designed to be used inforge-edit-post-hook
.difftastic-files
- show the result ofdifft FILE-A FILE-B
. When called with prefix argument it will ask for language to use, instead of relaying ondifftastic
’s detection mechanism.difftastic-buffers
- show the result ofdifft BUFFER-A BUFFER-B
. Language is guessed based on buffers modes. When called with prefix argument it will ask for language to use.difftastic-file-bufer
- show the result of =difft BUFFER BUFFER-FILE. Language is guessed based on buffer mode. When called with prefix argument it will ask for language to use.difftastic-dired-diff
- same asdired-diff
, but withdifftastic-files
instead of the built-indiff
.difftastic-git-diff-range
- transformARGS
for difftastic and show the result ofgit diff ARGS REV-OR-RANGE -- FILES
withdifftastic
.
All above commands (and difftastic-rerun
described
below) support specification of difft
arguments. When a command is called with a double prefix argument a popup is
presented allowing to specify desired arguments. This is in addition to a
command specific handling of a single prefix argument.
In order to aid arguments entry and provide similarity to workflows in magit
and forge
, a transient
prefix is used for the popup. For example, some -
less commonly used - arguments are not visible in default configuration. Type
C-x l
in the menu to make them visible. Type C-h C-h
for difftastic
help
(man difft
). Any other transient
commands should work as well.
When difftastic-use-transient-arguments
is non-nil, and extra difftastic
arguments were saved in transient
(as described in Info node
(transient)Saving Values
), these values will be used for future difft
invocations. However, when there’s a need to tune these arguments for a
specific difft
call, a difftastic command can be called with double prefix
argument to bring a popup allowing to specify arguments. Any arguments changed
in the popup (that were not saved) will be used for the following difft
invocation only.
Note that in some cases arguments will take precedence over standard and
computed values, for example --width
is one such a argument.
difftastic
output the following commands can be
used. Commands are followed by their default keybindings (in parenthesis).
difftastic-rerun
(g
) - rerun difftastic for the current buffer. It runs difftastic again in the current buffer, but respects the window configuration. It usesdifftastic-rerun-requested-window-width-function
which, by default, returns current window width (instead ofdifftastic-requested-window-width-function
). It will also reuse current buffer and will not calldifftastic-display-buffer-function
. When called with prefix argument it will ask for language to use.difftastic-next-chunk
(n
),difftastic-next-file
(N
orM-n
) - move point to a next logical chunk or a next file respectively.difftastic-previous-chunk
(p
),difftastic-previous-file
(P
orM-p
) - move point to a previous logical chunk or a previous file respectively.difftastic-toggle-chunk
(TAB
orC-i
) - toggle visibility of a chunk at point. The point has to be in a chunk header. When called with a prefix toggle all file chunks from the header to the end of the file. See alsodifftastic-hide-chunk
anddifftastic-show-chunk
.forward-sexp
(C-M-f
) - move point to end of current chunk or to an end of next chunk when point is already at the end of the chunk. When called with argument move by that many chunks. Binding is from a defaultglobal-map
.backward-sexp
(C-M-b
) - move point to beginning of current chunk or to a beginning of previous chunk when point is already at the beginning of the chunk. When called with argument move by that many chunks. Binding is from a defaultglobal-map
.mark-sexp
(C-M-SPC
) - set mark and move point to end of current chunk or to an end of next chunk when point is already at the end of the chunk. When called with argument move by that many chunks. Binding is from a defaultglobal-map
.difftastic-diff-visit-file
(RET
),difftastic-diff-visit-file-other-window
,difftastic-diff-visit-file-other-frame
- from a diff visit appropriate version of a chunk file. This has been modeled aftermagit-diff-visit-file
, but there are some differences, please see documentation fordifftastic-diff-visit-file
.difftastic-diff-visit-worktree-file
(C-RET
,C-j
),difftastic-diff-visit-worktree-file-other-window
,difftastic-diff-visit-worktree-file-other-frame
- from a diff visit appropriate version of a chunk file. This has been modeled aftermagit-diff-visit-worktree-file
, but there are some differences, please see documentation fordifftastic-diff-visit-worktree-file
.
difftastic
output by adjusting the faces
used for highlighting. To customize a faces, use the following code snippet in
your configuration:
;; Customize faces used to display difftastic output.
(setq difftastic-normal-colors-vector
(vector
;; use black face from `ansi-color'
(aref ansi-color-normal-colors-vector 0)
;; use face for removed marker from `difftastic'
(aref difftastic-normal-colors-vector 1)
;; use face for added marker from `difftastic'
(aref difftastic-normal-colors-vector 2)
'my-section-face
'my-comment-face
'my-string-face
'my-warning-face
;; use white face from `ansi-color'
(aref ansi-color-normal-colors-vector 7)))
;; Customize highlight faces
(setq difftastic-highlight-alist
`((,(aref difftastic-normal-colors-vector 2) . my-added-highlight)
(,(aref difftastic-normal-colors-vector 1) . my-removed-highlight)))
;; Disable highlight faces (use difftastic's default)
(setq difftastic-highlight-alist nil)
difftastic
relies on the difft
command line tool to produce an output
that can be displayed in an Emacs buffer window. In short: it runs the
difft
, converts ANSI codes into user defined colors and displays it in
window. The difft
can be instructed with a hint to help it produce a content
that can fit into user output, by specifying a requested width. However, the
latter is not always respected.
The difftastic
provides a few variables to let you customize these aspects of
interaction with difft
:
difftastic-requested-window-width-function
- this function is called for a first (i.e., not a rerun) call todifft
. It shall return the requested width of the output. For example this can be a half of a current frame (or a window) if the output is meant to be presented side by side.difftastic-rerun-requested-window-width-function
- this function is called for a rerun (i.e., not a first) call todifft
. It shall return requested window width of the output. For example this can be a current window width if the output is meant to fill the whole window.difftastic-display-buffer-function
- this function is called after a first call todifft
. It is meant to select an appropriate Emacs mechanism to display thedifft
output.
difftastic-visibility-indicator
- controls whether and how to show hidden/visible chunk/files.difftastic-buttonize-urls
- controls whether to transform URLs into buttons in difftastic buffers.difftastic-diff-visit-avoid-head-blob
- controls whether to avoid visiting blob of aHEAD
revision when visiting file form adifftastic-mode
buffer.
To run tests interactively:
- open the test/difftastic.t.el
- type
M-x eval-buffer <RET>
- open the test/difftastic-bindings.t.el
- type
M-x eval-buffer <RET>
- type
M-x ert <RET> t <RET>
Alternatively you can use Cask to run tests
in batch mode. There’s a convenience Makefile with a test
target,
so you can just type make test
.
It seems that byte compilation interferres with el-mock. In order to get the tests to pass you may need to:
- type
M-x eval-buffer <RET>
in difftastic.el and in difftastic-bindings.el when running test interactively withM-x <RET> ert <RET>
, - remove all
.elc
files in the development directory when running tests in batch mode.
This repository uses Coveralls to track test coverage. After a PR has been approved for a Gighub Action run, a report will be published Coveralls difftastic repo. Please check it out if there’s no outstanding relevant lines.
You can run all checks performed by Github Actions, by typing: make
bytecompile lint relint checkdoc commentary test
.
- org-commentary.el (which is
different from the one available on MELPA!) to generate and validate
commentary section in
difftastic.el
. Please see the package documentation for usage instructions. - org-make-toc to generate and
validate table of contents in the
README.org
file.
Appropriate functions from both of these packages are added to
after-save-hook
and before-save-hook
respectively, when packages are
available in user’s Emacs (see dir-locals.el
).
difftastic
within
Emacs, enhancing the code review process for developers.
This work is based on Tassilo Horn’s blog entry.
magit-diff
keybindings and a concept of updating faces comes from a Shiv
Jha-Mathur’s blog entry.
This all has been strongly influenced by - a class in itself - Magit and Transient Emacs packages by Jonas Bernoulli.
There’s a diff-ansi package available. I haven’t spent much time on it, but at a first glance it doesn’t seem that it supportsdifftastic
out of box. Perhaps it is possible
to configure it to support difftastic
as a custom tool.
This package is licensed under the
GPLv3 License.
Happy coding! If you encounter any issues or have suggestions for improvements, please don’t hesitate to reach out on the GitHub repository. Your feedback is highly appreciated.