Skip to content

Virtual padding ignores render_offset_top, image overlaps buffer content below #355

@Haegin

Description

@Haegin

Problem

When an image is rendered with both with_virtual_padding = true and render_offset_top > 0, the virtual padding reservation does not include the offset. The image visually shifts down by render_offset_top cells (per lua/image/renderer.lua:200), but lua/image/image.lua:125 reserves only height virtual lines instead of height + render_offset_top. The image therefore overlaps the next render_offset_top lines of real buffer content immediately below the reserved area.

This is most visible with diagram.nvim, which always sets render_offset_top = 1 — every rendered diagram covers the first line of buffer content following the diagram block.

Steps to reproduce

/tmp/repro-init.lua:

local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not vim.uv.fs_stat(lazypath) then
  vim.fn.system({ "git", "clone", "--filter=blob:none",
    "https://github.com/folke/lazy.nvim.git", "--branch=stable", lazypath })
end
vim.opt.rtp:prepend(lazypath)

require("lazy").setup({
  { "3rd/image.nvim",
    dependencies = { "nvim-lua/plenary.nvim" },
    opts = {
      backend = "kitty",
      kitty_method = "normal",
      max_width_window_percentage = 100,
      max_height_window_percentage = 100,
    },
  },
})

vim.defer_fn(function()
  local image = require("image")
  local img = image.from_file(vim.env.IMG_PATH, {
    buffer = vim.api.nvim_get_current_buf(),
    window = vim.api.nvim_get_current_win(),
    with_virtual_padding = true,
    inline = true,
    x = 0,
    y = 0,
    render_offset_top = 1,
  })
  img:render()
end, 200)

/tmp/repro.txt:

ANCHOR (image renders here, render_offset_top = 1)
THIS LINE IS OVERLAPPED BY THE BOTTOM ROW OF THE IMAGE

Run in a Kitty-graphics-capable terminal (Kitty, Ghostty, etc.):

IMG_PATH=/path/to/any.png nvim --clean -u /tmp/repro-init.lua /tmp/repro.txt

The second line is partially or fully covered by the bottom row of the image. With render_offset_top = 0 (or by patching total_lines per the suggested fix below), it stays visible.

Expected behavior

When render_offset_top = N, the reservation should cover image_rows + N lines so the rendered image (occupying extmark_row + N + 1 through extmark_row + N + image_rows) fits inside the reserved area without overlapping subsequent buffer lines.

Suggested fix

lua/image/image.lua:111 already computes total_height = height + (self.render_offset_top or 0) for the cache-invalidation key. Use the same value for the actual reservation on line 125:

 if self.with_virtual_padding then
   -- only reserve real height for the extmark, padding is applied during rendering
-  local total_lines = height
+  local total_lines = total_height
   for _ = 0, total_lines - 1 do
     filler[#filler + 1] = { { " ", "" } }
   end
   extmark_opts.virt_lines = filler
 end

Environment

  • image.nvim: v1.5.1 (commit da2be65)
  • Surfaced via diagram.nvim's mermaid integration on Ghostty

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions