blame.nvim is a Neovim plugin to interactively explore the git blame of the current file similar to Github's blame button.
Lua
4
81 commits
updated Oct 8, 2026
blame.nvim is a Neovim plugin to interactively explore the git blame of the current file similar to Github's blame button.
K), to answer why a line is there.<CR> blames the file as it was prior to the commit under the cursor, to dig deeper into the history of a line. <C-o> goes back again, like a tag stack.Requires Neovim >= 0.10 and git. Install blame.nvim using your favorite plugin manager.
-- plugins/blame.lua
return {
{
"roschaefer/blame.nvim",
cmd = "Blame",
},
}
:Blame.Navigate through the commit history:
<CR> (or <C-]>) on a committed line to view the blame prior to its commit, unless the commit is the first one in the history: the view shows the blame of the file as it was in the parent of the commit that last changed this line. The title of the blame window shows the hash of that parent, not of the commit under the cursor.<C-o> (or <C-t>, <BS>) to go back to the version you came from.<C-]> and popping the tag stack with <C-t>.j and k (or <Down> and <Up>) skip the empty lines and move from commit to commit, i.e. to the first line of the next or previous block. Inside a block, k first moves to the first line of that block. They take a count, e.g. 3j. In the file content window they move line by line as usual.<C-w>h and <C-w>l.K to open or close a panel at the bottom with the full commit message, author and date of the cursor line. It follows the cursor while it is open. Pressing K inside the panel closes it and returns to the window you came from, q closes the whole view. :q inside the panel closes only the panel.q or <C-c> to close the blame view. Closing the blame or the file content window (e.g. :q) closes the whole view.Both buffers are read-only, but you can select and yank text as usual, e.g. the commit hash in the commit message panel (K).
You can override the default configuration:
-- plugins/blame.lua
return {
{
"roschaefer/blame.nvim",
opts = {
keys = {
navigate_forward = { "<CR>", "<C-]>" },
navigate_backward = { "<C-o>", "<C-t>", "<BS>" },
close = { "q", "<C-c>" },
toggle_commit_message = "K",
next_block = { "j", "<Down>" },
previous_block = { "k", "<Up>" },
},
},
cmd = "Blame",
},
}
What to do with a commit depends on the tools you use, so blame.nvim leaves that to you: on_attach is called with the view whenever a blame view opens.
-- plugins/blame.lua
return {
{
"roschaefer/blame.nvim",
opts = {
on_attach = function(view)
-- Yank the hash of the commit of the cursor line
view.keymap.set("n", "yc", function()
local commit = view:commit()
if commit then
vim.fn.setreg(vim.v.register, commit.hash)
end
end, { desc = "Yank commit hash" })
-- Show the revision displayed in the view with your git plugin.
-- The working tree has no revision, so show HEAD instead.
view.keymap.set("n", "<leader>gr", function()
local revision = view:revision() or "HEAD"
-- vim.cmd("Gitsigns show_commit " .. revision) -- gitsigns.nvim
-- vim.cmd("DiffviewOpen " .. revision .. "^!") -- diffview.nvim
-- vim.cmd("Git show " .. revision) -- vim-fugitive
end, { desc = "Show the revision displayed in the view" })
end,
},
cmd = "Blame",
},
}
view | |
|---|---|
view.keymap.set(mode, lhs, rhs, opts) | Like vim.keymap.set(), but only in the buffers of the view. Keymaps set here take precedence over the keymaps of blame.nvim. |
view:commit() | The commit of the cursor line, { hash, author, time, summary }, or nil if the line is not committed yet. |
view:revision() | The hash of the commit whose version of the file is shown, or nil for the working tree. |
view:close() | Closes the view. |
view.buffers | The buffers of the blame window and the file content window. |
view.file, view.git_root | The path of the blamed file and the root of its repository. |
The windows of the view have the root of the repository as their local directory (:lcd), so git plugins find the repository even if Neovim was started outside of it.
The blame window has the filetype blame. You can customize it in after/ftplugin/blame.lua, the same way as any other filetype:
-- after/ftplugin/blame.lua
vim.opt_local.number = true
vim.opt_local.cursorline = false
Buffer-local keymaps defined there take precedence over the keymaps of blame.nvim.
The age stripe is drawn in the sign column of the blame window. Its colours are blended from the background of the sign column towards GitBlameAge (linked to DiagnosticWarn), so they follow your colour scheme; this needs termguicolors, otherwise all commits get the colour of GitBlameAge.
The bar next to the lines of the commit under the cursor is drawn in the sign column of the file content window and highlighted with GitBlameCursorCommit (linked to Special).
The columns of the blame window are highlighted with GitBlameDate (linked to Normal), GitBlameAuthor (linked to Comment) and GitBlameSubject (linked to Normal).
The view always sets these options in both windows, because it needs them to keep the lines of both windows aligned: scrollbind, cursorbind, nowrap, nofoldenable and nodiff.
The commit message panel has the filetype git.
The file content window has no filetype. It is highlighted with tree-sitter, or with regex syntax highlighting if no parser is installed. Your ftplugins, LSP clients and other filetype plugins do not run there.
The development tools (Neovim, git, stylua, lua-language-server and llscheck) are provided by a Nix dev shell:
nix develop
nix develop starts bash. To keep your shell, run e.g. nix develop --command fish, or use direnv with use flake.
The scripts below work the same with tools installed in any other way, as long as they are on your PATH.
To start Neovim with lazy.nvim and only this plugin activated:
./scripts/run [file...]
To start your own Neovim configuration with this working copy of the plugin
(requires lazy.nvim, your ~/.config/nvim stays untouched):
./scripts/run-user-config [file...]
This works through the project-local .lazy.lua spec, which
lazy.nvim merges into your configuration whenever Neovim starts inside this
repository. The first time, Neovim asks you to trust .lazy.lua: choose
(v)iew and run :trust.
The project-local spec also adds the keymaps from Custom keymaps as a demo of on_attach: yc yanks the commit of the cursor line and <leader>gr shows the revision displayed in the view.
To run the unit tests:
./scripts/test
To run diagnostics:
./scripts/llscheck
Code formatting is managed using stylua. To format the code, or to check the formatting the same way CI does:
stylua lua
stylua --check lua
blame.nvim is a Neovim plugin to interactively explore the git blame of the current file similar to Github's blame button.
Lua
4
81 commits
updated Oct 8, 2026
blame.nvim is a Neovim plugin to interactively explore the git blame of the current file similar to Github's blame button.
K), to answer why a line is there.<CR> blames the file as it was prior to the commit under the cursor, to dig deeper into the history of a line. <C-o> goes back again, like a tag stack.Requires Neovim >= 0.10 and git. Install blame.nvim using your favorite plugin manager.
-- plugins/blame.lua
return {
{
"roschaefer/blame.nvim",
cmd = "Blame",
},
}
:Blame.Navigate through the commit history:
<CR> (or <C-]>) on a committed line to view the blame prior to its commit, unless the commit is the first one in the history: the view shows the blame of the file as it was in the parent of the commit that last changed this line. The title of the blame window shows the hash of that parent, not of the commit under the cursor.<C-o> (or <C-t>, <BS>) to go back to the version you came from.<C-]> and popping the tag stack with <C-t>.j and k (or <Down> and <Up>) skip the empty lines and move from commit to commit, i.e. to the first line of the next or previous block. Inside a block, k first moves to the first line of that block. They take a count, e.g. 3j. In the file content window they move line by line as usual.<C-w>h and <C-w>l.K to open or close a panel at the bottom with the full commit message, author and date of the cursor line. It follows the cursor while it is open. Pressing K inside the panel closes it and returns to the window you came from, q closes the whole view. :q inside the panel closes only the panel.q or <C-c> to close the blame view. Closing the blame or the file content window (e.g. :q) closes the whole view.Both buffers are read-only, but you can select and yank text as usual, e.g. the commit hash in the commit message panel (K).
You can override the default configuration:
-- plugins/blame.lua
return {
{
"roschaefer/blame.nvim",
opts = {
keys = {
navigate_forward = { "<CR>", "<C-]>" },
navigate_backward = { "<C-o>", "<C-t>", "<BS>" },
close = { "q", "<C-c>" },
toggle_commit_message = "K",
next_block = { "j", "<Down>" },
previous_block = { "k", "<Up>" },
},
},
cmd = "Blame",
},
}
What to do with a commit depends on the tools you use, so blame.nvim leaves that to you: on_attach is called with the view whenever a blame view opens.
-- plugins/blame.lua
return {
{
"roschaefer/blame.nvim",
opts = {
on_attach = function(view)
-- Yank the hash of the commit of the cursor line
view.keymap.set("n", "yc", function()
local commit = view:commit()
if commit then
vim.fn.setreg(vim.v.register, commit.hash)
end
end, { desc = "Yank commit hash" })
-- Show the revision displayed in the view with your git plugin.
-- The working tree has no revision, so show HEAD instead.
view.keymap.set("n", "<leader>gr", function()
local revision = view:revision() or "HEAD"
-- vim.cmd("Gitsigns show_commit " .. revision) -- gitsigns.nvim
-- vim.cmd("DiffviewOpen " .. revision .. "^!") -- diffview.nvim
-- vim.cmd("Git show " .. revision) -- vim-fugitive
end, { desc = "Show the revision displayed in the view" })
end,
},
cmd = "Blame",
},
}
view | |
|---|---|
view.keymap.set(mode, lhs, rhs, opts) | Like vim.keymap.set(), but only in the buffers of the view. Keymaps set here take precedence over the keymaps of blame.nvim. |
view:commit() | The commit of the cursor line, { hash, author, time, summary }, or nil if the line is not committed yet. |
view:revision() | The hash of the commit whose version of the file is shown, or nil for the working tree. |
view:close() | Closes the view. |
view.buffers | The buffers of the blame window and the file content window. |
view.file, view.git_root | The path of the blamed file and the root of its repository. |
The windows of the view have the root of the repository as their local directory (:lcd), so git plugins find the repository even if Neovim was started outside of it.
The blame window has the filetype blame. You can customize it in after/ftplugin/blame.lua, the same way as any other filetype:
-- after/ftplugin/blame.lua
vim.opt_local.number = true
vim.opt_local.cursorline = false
Buffer-local keymaps defined there take precedence over the keymaps of blame.nvim.
The age stripe is drawn in the sign column of the blame window. Its colours are blended from the background of the sign column towards GitBlameAge (linked to DiagnosticWarn), so they follow your colour scheme; this needs termguicolors, otherwise all commits get the colour of GitBlameAge.
The bar next to the lines of the commit under the cursor is drawn in the sign column of the file content window and highlighted with GitBlameCursorCommit (linked to Special).
The columns of the blame window are highlighted with GitBlameDate (linked to Normal), GitBlameAuthor (linked to Comment) and GitBlameSubject (linked to Normal).
The view always sets these options in both windows, because it needs them to keep the lines of both windows aligned: scrollbind, cursorbind, nowrap, nofoldenable and nodiff.
The commit message panel has the filetype git.
The file content window has no filetype. It is highlighted with tree-sitter, or with regex syntax highlighting if no parser is installed. Your ftplugins, LSP clients and other filetype plugins do not run there.
The development tools (Neovim, git, stylua, lua-language-server and llscheck) are provided by a Nix dev shell:
nix develop
nix develop starts bash. To keep your shell, run e.g. nix develop --command fish, or use direnv with use flake.
The scripts below work the same with tools installed in any other way, as long as they are on your PATH.
To start Neovim with lazy.nvim and only this plugin activated:
./scripts/run [file...]
To start your own Neovim configuration with this working copy of the plugin
(requires lazy.nvim, your ~/.config/nvim stays untouched):
./scripts/run-user-config [file...]
This works through the project-local .lazy.lua spec, which
lazy.nvim merges into your configuration whenever Neovim starts inside this
repository. The first time, Neovim asks you to trust .lazy.lua: choose
(v)iew and run :trust.
The project-local spec also adds the keymaps from Custom keymaps as a demo of on_attach: yc yanks the commit of the cursor line and <leader>gr shows the revision displayed in the view.
To run the unit tests:
./scripts/test
To run diagnostics:
./scripts/llscheck
Code formatting is managed using stylua. To format the code, or to check the formatting the same way CI does:
stylua lua
stylua --check lua