---@brief [[ --- glaze.nvim text rendering (based on lazy.nvim) --- Handles buffered text with highlight segments ---@brief ]] local M = {} ---@class GlazeTextSegment ---@field str string ---@field hl? string|GlazeExtmark ---@class GlazeExtmark ---@field hl_group? string ---@field col? number ---@field end_col? number ---@field virt_text? table ---@field virt_text_win_col? number ---@class GlazeText ---@field _lines GlazeTextSegment[][] ---@field padding number ---@field wrap number local Text = {} function Text.new() local self = setmetatable({}, { __index = Text }) self._lines = {} self.padding = 2 self.wrap = 80 return self end ---@param str string ---@param hl? string|GlazeExtmark ---@param opts? { indent?: number, wrap?: boolean } ---@return GlazeText function Text:append(str, hl, opts) opts = opts or {} if #self._lines == 0 then self:nl() end local lines = vim.split(str, "\n") for i, line in ipairs(lines) do if opts.indent then line = string.rep(" ", opts.indent) .. line end if i > 1 then self:nl() end -- Handle wrap if opts.wrap and str ~= "" and self:col() > 0 and self:col() + vim.fn.strwidth(line) + self.padding > self.wrap then self:nl() end table.insert(self._lines[#self._lines], { str = line, hl = hl }) end return self end ---@return GlazeText function Text:nl() table.insert(self._lines, {}) return self end ---@return number function Text:row() return #self._lines == 0 and 1 or #self._lines end ---@return number function Text:col() if #self._lines == 0 then return 0 end local width = 0 for _, segment in ipairs(self._lines[#self._lines]) do width = width + vim.fn.strwidth(segment.str) end return width end function Text:trim() while #self._lines > 0 and #self._lines[#self._lines] == 0 do table.remove(self._lines) end end ---@param buf number ---@param ns number function Text:render(buf, ns) local lines = {} for _, line in ipairs(self._lines) do local str = string.rep(" ", self.padding) for _, segment in ipairs(line) do str = str .. segment.str end if str:match("^%s*$") then str = "" end table.insert(lines, str) end vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines) vim.api.nvim_buf_clear_namespace(buf, ns, 0, -1) for l, line in ipairs(self._lines) do if lines[l] ~= "" then local col = self.padding for _, segment in ipairs(line) do local width = vim.fn.strwidth(segment.str) local extmark = segment.hl if extmark then if type(extmark) == "string" then extmark = { hl_group = extmark, end_col = col + width } end ---@cast extmark GlazeExtmark local extmark_col = extmark.col or col extmark.col = nil pcall(vim.api.nvim_buf_set_extmark, buf, ns, l - 1, extmark_col, extmark) end col = col + width end end end end M.Text = Text return M