local nvim_on = require('vim._core.util').nvim_on local M = {} ---@brief --- ---EXPERIMENTAL: This API may change in the future. Its semantics are not yet finalized. --- ---This provides a functional API for displaying images in Nvim. ---Currently supports PNG images via the Kitty graphics protocol. --- ---To override the image backend, replace `vim.ui.img` with your own ---implementation providing set/get/del. --- ---Examples: --- ---```lua ----- Load image bytes from disk and display at row 5, column 10 ---local id = vim.ui.img.set( --- vim.fn.readblob('/path/to/img.png'), --- { row = 5, col = 10, width = 40, height = 20, zindex = 50 } ---) --- ----- Update the image position ---vim.ui.img.set(id, { row = 8, col = 12 }) --- ----- Retrieve the current image opts ---local opts = vim.ui.img.get(id) --- ----- Remove the image ---vim.ui.img.del(id) --- ----- Remove all images ---vim.ui.img.del(math.huge) ---``` ---@class vim.ui.img.Opts ---@inlinedoc ---@field row? integer starting row (1-indexed) ---@field col? integer starting column (1-indexed) ---@field width? integer width in cells ---@field height? integer height in cells ---@field zindex? integer stacking order (higher = on top) --- Maps user-facing ID to internal tracking info. ---@type table local state = {} ---Display an image or update an existing one. --- ---When {data_or_id} is a string, displays the image bytes at the position ---given by {opts}. Returns an integer id for later use. --- ---When {data_or_id} is an integer (a previously returned id), updates ---the image with new {opts}. --- ---@param data_or_id string|integer image bytes (string) or existing id (integer) ---@param opts? vim.ui.img.Opts ---@return integer id function M.set(data_or_id, opts) opts = opts or {} vim.validate('data_or_id', data_or_id, { 'string', 'number' }) vim.validate('opts', opts, 'table') local kitty = require('vim.ui.img._kitty') -- If given a string, this should be the bytes of a new image to display if type(data_or_id) == 'string' then local img_id, placement_id = kitty.set(data_or_id, opts) state[placement_id] = { img_id = img_id, opts = vim.deepcopy(opts) } return placement_id end -- Otherwise, we update an existing image that is actively displayed local id = data_or_id local entry = state[id] assert(entry, 'invalid image id: ' .. tostring(id)) -- We always want to have a full set of options when passing to kitty local merged = vim.tbl_extend('force', entry.opts, opts) kitty.update(entry.img_id, id, merged) entry.opts = merged return id end ---Get the opts for an image. --- ---@param id integer ---@return vim.ui.img.Opts? opts copy of image opts, or nil if not found function M.get(id) vim.validate('id', id, 'number') -- Grab a copy of the most recent opts used for the image local entry = state[id] if not entry then return nil end return vim.deepcopy(entry.opts) end ---Delete an image, or all images if `math.huge` is given as the id. --- ---@param id integer image id, or `math.huge` to delete all images ---@return boolean found true if any image existed function M.del(id) vim.validate('id', id, 'number') -- Check if we have images to delete, and clear them out of our memory -- being tracked here and also send a request to delete them all from -- the terminal if id == math.huge then local has_ids = next(state) ~= nil state = {} if has_ids then local kitty = require('vim.ui.img._kitty') kitty.delete(math.huge) end return has_ids end -- Skip performing the deletion if we don't have an active image with the id local entry = state[id] if not entry then return false end local kitty = require('vim.ui.img._kitty') kitty.delete(entry.img_id) state[id] = nil return true end ---@private --- Query whether the host terminal supports displaying images. --- Blocks until the terminal responds or times out. --- ---@param opts? {timeout?: integer} timeout in milliseconds (default: 1000) ---@return boolean supported true if the terminal supports image display ---@return string? msg error detail if the terminal responded but not with OK function M._supported(opts) return require('vim.ui.img._kitty').supported(opts) end nvim_on('VimLeavePre', nil, function() M.del(math.huge) end) return M