NeoVim: Normalize and Copy Code Block

Published: Jan 29, 2025

Let's say I wanted to copy this block of code so that I can paste it into a github comment:

Copy block If I just press y to yank the block and paste it into a lua block on github, it will look like this:

Bad copy block

If I use my function (see below), it looks like this when pasted:

Good copy block

Much better!

Here's the function:

local function copy_normalized_block()
  local mode = vim.fn.mode()
  if mode ~= "v" and mode ~= "V" then
    return
  end

  vim.cmd([[silent normal! "xy]])
  local text = vim.fn.getreg("x")
  local lines = vim.split(text, "\n", { plain = true })

  local converted = {}
  for _, line in ipairs(lines) do
    local l = line:gsub("\t", "  ")
    table.insert(converted, l)
  end

  local min_indent = math.huge
  for _, line in ipairs(converted) do
    if line:match("[^%s]") then
      local indent = #(line:match("^%s*"))
      min_indent = math.min(min_indent, indent)
    end
  end
  min_indent = min_indent == math.huge and 0 or min_indent

  local result = {}
  for _, line in ipairs(converted) do
    if line:match("^%s*$") then
      table.insert(result, "")
    else
      local processed = line:sub(min_indent + 1)
      processed = processed:gsub("^%s+", function(spaces)
        return string.rep("  ", math.floor(#spaces / 2))
      end)
      table.insert(result, processed)
    end
  end

  local normalized = table.concat(result, "\n")
  vim.fn.setreg("+", normalized)
  vim.notify("Copied normalized text to clipboard")
end

And then I bind it to a key like so:

vim.keymap.set(
  "v",
  "<leader>y",
  copy_normalized_block,
  { desc = "Copy normalized" },
)

Filed Under: