roschaefer/blame.nvim

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

See the code

See what people are saying

SourceMessageScoreDate

blame.nvim: GitHub-style blame for Neovim (r/neovim)

Hello people, When I want to understand why a certain line or block of code exists, I trace it back to the commit that introduced it. Often, the commit message sheds some light on the reasoning behind it. GitHub has a useful feature for this: the **Blame** button available on every file. I've…

1

Oct 8, 2026

README

blame.nvim

blame.nvim is a Neovim plugin to interactively explore the git blame of the current file similar to Github's blame button.

blame.nvim demo

Features

  • Git Blame Integration: Displays the date (relative to today), author and commit subject next to each block of lines from the same commit, like GitHub's blame view.
  • Commit Grouping: A bar in the sign column of the file content window marks all lines of the commit under the cursor, to show which lines belong together.
  • Commit Age: A coloured stripe in the blame window shows the age of each commit relative to the other commits of the file, faint for older and strong for newer, like GitHub's blame view. A legend in the title of the blame window explains the colours, if there is room for it (Neovim 0.12 or newer).
  • Window Synchronization: Keeps the blame window synchronized with the original file's cursor position and scroll view.
  • Commit Message Panel: Shows the full commit message of the cursor line on demand (K), to answer why a line is there.
  • Blame Prior: Like "View blame prior to this change" on GitHub, <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.
  • Custom Keymaps: Configurable keybindings for navigation and closing.

Installation

Requires Neovim >= 0.10 and git. Install blame.nvim using your favorite plugin manager.

lazy.nvim

-- plugins/blame.lua
return {
  {
    "roschaefer/blame.nvim",
    cmd = "Blame",
  },
}

Usage

  1. Open a file in a Git repository.
  2. Run the command :Blame.
  3. A new tab page opens with the blame information on the left and the file content on the right. Both windows scroll together.

Navigate through the commit history:

  • Press <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.
  • Press <C-o> (or <C-t>, <BS>) to go back to the version you came from.
  • Both work in the blame window and in the file content window, similar to following a tag with <C-]> and popping the tag stack with <C-t>.
  • In the blame window, 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.
  • Switch between the blame window and the file content window with the usual window commands, e.g. <C-w>h and <C-w>l.
  • Press 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.
  • Press q or <C-c> to close the blame view. Closing the blame or the file content window (e.g. :q) closes the whole view.
  • Opening another file in one of the windows of the view, e.g. from a file explorer, closes the rest of the view. The file stays open in that window.

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).

Configuration

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",
  },
}

Custom keymaps

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.buffersThe buffers of the blame window and the file content window.
view.file, view.git_rootThe 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.

Blame window

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.

Development

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

License

MIT

git
git-blame
lua
neovim
neovim-plugin
nvim

roschaefer/blame.nvim

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

See the code

See what people are saying

SourceMessageScoreDate

blame.nvim: GitHub-style blame for Neovim (r/neovim)

Hello people, When I want to understand why a certain line or block of code exists, I trace it back to the commit that introduced it. Often, the commit message sheds some light on the reasoning behind it. GitHub has a useful feature for this: the **Blame** button available on every file. I've…

1

Oct 8, 2026

README

blame.nvim

blame.nvim is a Neovim plugin to interactively explore the git blame of the current file similar to Github's blame button.

blame.nvim demo

Features

  • Git Blame Integration: Displays the date (relative to today), author and commit subject next to each block of lines from the same commit, like GitHub's blame view.
  • Commit Grouping: A bar in the sign column of the file content window marks all lines of the commit under the cursor, to show which lines belong together.
  • Commit Age: A coloured stripe in the blame window shows the age of each commit relative to the other commits of the file, faint for older and strong for newer, like GitHub's blame view. A legend in the title of the blame window explains the colours, if there is room for it (Neovim 0.12 or newer).
  • Window Synchronization: Keeps the blame window synchronized with the original file's cursor position and scroll view.
  • Commit Message Panel: Shows the full commit message of the cursor line on demand (K), to answer why a line is there.
  • Blame Prior: Like "View blame prior to this change" on GitHub, <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.
  • Custom Keymaps: Configurable keybindings for navigation and closing.

Installation

Requires Neovim >= 0.10 and git. Install blame.nvim using your favorite plugin manager.

lazy.nvim

-- plugins/blame.lua
return {
  {
    "roschaefer/blame.nvim",
    cmd = "Blame",
  },
}

Usage

  1. Open a file in a Git repository.
  2. Run the command :Blame.
  3. A new tab page opens with the blame information on the left and the file content on the right. Both windows scroll together.

Navigate through the commit history:

  • Press <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.
  • Press <C-o> (or <C-t>, <BS>) to go back to the version you came from.
  • Both work in the blame window and in the file content window, similar to following a tag with <C-]> and popping the tag stack with <C-t>.
  • In the blame window, 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.
  • Switch between the blame window and the file content window with the usual window commands, e.g. <C-w>h and <C-w>l.
  • Press 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.
  • Press q or <C-c> to close the blame view. Closing the blame or the file content window (e.g. :q) closes the whole view.
  • Opening another file in one of the windows of the view, e.g. from a file explorer, closes the rest of the view. The file stays open in that window.

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).

Configuration

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",
  },
}

Custom keymaps

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.buffersThe buffers of the blame window and the file content window.
view.file, view.git_rootThe 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.

Blame window

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.

Development

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

License

MIT

git
git-blame
lua
neovim
neovim-plugin
nvim