The diff cookbook

“Diffing” refers to the process of comparing one thing against another, viewing the “differences” between the two.

Because comparing text is a common occurrence when writing code, there exists an entire category of tools dedicated to helping developers compare text snippets. In this post, I go over some of the tools I rely on for this task.

Who is this post for?

  1. You use neovim (or considering it).
  2. You have yet to go down the diff rabbit hole yourself.
  3. You’re (at least mildly) interested in improving your diff workflow.

On the other hand, if you’ve already been down this path and have found better methods, do consider sharing them.

Table of Contents

Neovim: diffing while editing code

The following examples require diffview.nvim and gitsigns.nvim to be installed.

Deep diffing: comparing against one or more commits


Trace the history of the entire git repository.

After the command is invoked, use tab to cycle to the next change in chronological order. shift+tab will take you in the opposite direction.
	{ desc = 'Repo history' }


This can be used to step through the changes of a single file (the current file). The --follow flag tells git to trace a file’s history through renames.

It opens in the same view as the previous example, except that the commits listed are limited to those affecting the current file.

	'<cmd>DiffviewFileHistory --follow %<cr>',
	{ desc = 'File history' }

Visual selection

Trace the history of the current visual selection. Use this command if you’re only interested in tracing changes affecting a specific section of a file.

	"<Esc><Cmd>'<,'>DiffviewFileHistory --follow<CR>",
	{ desc = 'Range history' }


Finally, the most granular form is tracing the history of a single line.

	'<Cmd>.DiffviewFileHistory --follow<CR>',
	{ desc = 'Line history' }

Shallow diffing: comparing against HEAD


Diff the working directory against HEAD (and staging area). This will open a new tab.

screenshot of diffview.nvim
Image source:
vim.keymap.set('n', ',d', '<cmd>DiffviewOpen<cr>', { desc = 'Repo diff' })

We can slightly alter the previous command to serve another common use case: diffing the working directory against the master branch. This is useful before merging or when first continuing work on an existing feature branch.

local function get_default_branch_name()
	local res = vim
		.system({ 'git', 'rev-parse', '--verify', 'main' }, { capture_output = true })
	return res.code == 0 and 'main' or 'master'

-- Diff against local master branch
	function() vim.cmd('DiffviewOpen ' .. get_default_branch_name()) end,
	{ desc = 'Diff against master' }

-- Diff against remote master branch
	function() vim.cmd('DiffviewOpen HEAD..origin/' .. get_default_branch_name()) end,
	{ desc = 'Diff against origin/master' }


The following commands use highlighting and virtual text to visualize changes without leaving the current buffer.

-- Highlight changed words.
	{ desc = 'Toggle word diff' }

-- Highlight added lines.
	{ desc = 'Toggle line highlight' }

-- Highlight removed lines.
	{ desc = 'Toggle deleted (all)' }


Diff the current hunk in a floating window (against the staging area).

	{ desc = 'Preview hunk' }


Clipboard vs. current file

Diff the current buffer against the clipboard contents.

My clipboard contains "yodafied" text. Here, I compare that "yodafied" text to the current buffer by invoking :CompareClipboard. Finally, I close the generated tab to return back to my original tab by invoking :tabclose.
-- Create a new scratch buffer
		execute 'vsplit | enew'
		setlocal buftype=nofile
		setlocal bufhidden=hide
		setlocal noswapfile
	{ nargs = 0 }

-- Compare clipboard to current buffer
vim.api.nvim_create_user_command('CompareClipboard', function()
	local ftype = vim.api.nvim_eval('&filetype') -- original filetype
		tabnew %
		normal! P
		windo diffthis
	vim.cmd('set filetype=' .. ftype)
end, { nargs = 0 })

-- Assign it to a keymap
	{ desc = 'Compare Clipboard', silent = true }

Clipboard vs. visual selection

Diff the current visual selection against the clipboard contents.

My clipboard contains "yodafied" text. Here, I highlight two lines, then compare the "yodafied" text to the two highlighted lines by invoking :CompareClipboardSelection.
-- Create a new scratch buffer (see previous example)
-- ...

-- Compare clipboard to visual selection
		" yank visual selection to z register
		normal! gv"zy
		" open new tab, set options to prevent save prompt when closing
		execute 'tabnew | setlocal buftype=nofile bufhidden=hide noswapfile'
		" paste z register into new buffer
		normal! V"zp
		normal! Vp
		windo diffthis
		nargs = 0,
		range = true,

-- Assign it to a keymap
	{ desc = 'Compare Clipboard Selection' }

Shell: diffing the output of two shell commands

You can use process substitution to diff the output of any two shell commands. The following works for bash and zsh.

nvim -d <(ls -l) <(ls -la)

# you can also use another difftool
delta <(ls -l) <(ls -la)

Filesize: recursively diffing file sizes in a directory

This bash function uses dust to diff the sizes of two directories. I don’t use this much, but it comes in handy when I’m experimenting with different bundler options (such as in a tsconfig.json or vite.config.js) and want to know more about how the changes I’m making affect the size of the generated output.

screenshot of diffing directory sizes
The total size (bottom line) is reduced by 1.6K when we instruct the typescript compiler to not output declaration files (*.d.ts).
# diff directory size
# Usage:   diff-dirs DIR1 DIR2 [dust options]
# Example: diff-dirs dir1 dir2 --depth=1 --no-progress --apparent-size
diff-dirs() {
  local dir1="$1"
  local dir2="$2"
  if [[ ! -d "$dir1" ]] || [[ ! -d "$dir2" ]]; then
    echo "Error: both directories must exist"
    return 1
  shift 2
  delta --side-by-side <(dust --screen-reader --no-colors --full-paths --only-file --reverse "$@" "$dir1" | sed 's/\r//g' | sed 's|^[^/]*/||') <(dust --screen-reader --no-colors --full-paths --only-file --reverse "$@" "$dir2" | sed 's/\r//g' | sed 's|^[^/]*/||')

Depending on the output, it can be tricky to accurately detect and properly highlight moved vs changed lines. Delta has some options that you can tweak such as --word-diff-regex.

Misc: quality of life tips for working with diffs

Syntax highlighting for code snippets

- my original line
+ my updated line
my unchanged line

You can enable this form of syntax highlighting for a markdown code block by specifying diff as the language. Then, prefix any line with either a - or a + to highlight it.

This works automatically on In neovim, you might need to explicitly add diff to the list of languages in your treesitter config (in the ensure_installed table).


Delta makes diffs easier to read.

screenshot of delta diff
screenshot of delta
Image source:

Using delta on the command line

# delta can be used to diff any two files
delta file1 file2

# or directories.
delta dir1 dir2

Using delta as the default git pager

Delta can be set as git’s default pager. See delta’s documentation for how to do so in your .gitconfig. Additionally, below are some extra delta options I’ve added to my .gitconfig:

# .gitconfig
	pager = delta
	features = decorations
	light = false
	tabs = 2
	line-numbers = true
	navigate = true
	hyperlinks = true
	# side-by-side = true

Hint: For even greater readability, set gruvmax-fang as your custom theme.

You can find out more about these options by running delta --help or visiting delta’s docs.

Delta can also be integrated into other tools such as lazygit.


If you’d rather work with a mouse, or simply prefer a GUI, meld can compare files or entire directories recursively.

screenshot of meld
Image source:


comm can print common or unique lines between two files. A helpful cheat sheet can be found here.