|
| 1 | +local Config = require("lazy.core.config") |
| 2 | +local ViewConfig = require("lazy.view.config") |
| 3 | + |
| 4 | +---@class LazyViewOptions |
| 5 | +---@field buf? number |
| 6 | +---@field file? string |
| 7 | +---@field margin? {top?:number, right?:number, bottom?:number, left?:number} |
| 8 | +---@field win_opts LazyViewWinOpts |
| 9 | +local defaults = { |
| 10 | + win_opts = {}, |
| 11 | +} |
| 12 | + |
| 13 | +---@class LazyFloat |
| 14 | +---@field buf number |
| 15 | +---@field win number |
| 16 | +---@field opts LazyViewOptions |
| 17 | +---@overload fun(opts?:LazyViewOptions):LazyFloat |
| 18 | +local M = {} |
| 19 | + |
| 20 | +setmetatable(M, { |
| 21 | + __call = function(_, ...) |
| 22 | + return M.new(...) |
| 23 | + end, |
| 24 | +}) |
| 25 | + |
| 26 | +---@param opts? LazyViewOptions |
| 27 | +function M.new(opts) |
| 28 | + local self = setmetatable({}, { __index = M }) |
| 29 | + return self:init(opts) |
| 30 | +end |
| 31 | + |
| 32 | +---@param opts? LazyViewOptions |
| 33 | +function M:init(opts) |
| 34 | + self.opts = vim.tbl_deep_extend("force", defaults, opts or {}) |
| 35 | + self:mount() |
| 36 | + self:on_key(ViewConfig.keys.close, self.close) |
| 37 | + self:on({ "BufDelete", "BufLeave", "BufHidden" }, self.close, { once = true }) |
| 38 | + return self |
| 39 | +end |
| 40 | + |
| 41 | +function M:layout() |
| 42 | + local function size(max, value) |
| 43 | + return value > 1 and math.min(value, max) or math.floor(max * value) |
| 44 | + end |
| 45 | + self.opts.win_opts.width = size(vim.o.columns, Config.options.ui.size.width) |
| 46 | + self.opts.win_opts.height = size(vim.o.lines, Config.options.ui.size.height) |
| 47 | + self.opts.win_opts.row = math.floor((vim.o.lines - self.opts.win_opts.height) / 2) |
| 48 | + self.opts.win_opts.col = math.floor((vim.o.columns - self.opts.win_opts.width) / 2) |
| 49 | + |
| 50 | + if self.opts.margin then |
| 51 | + if self.opts.margin.top then |
| 52 | + self.opts.win_opts.height = self.opts.win_opts.height - self.opts.margin.top |
| 53 | + self.opts.win_opts.row = self.opts.win_opts.row + self.opts.margin.top |
| 54 | + end |
| 55 | + if self.opts.margin.right then |
| 56 | + self.opts.win_opts.width = self.opts.win_opts.width - self.opts.margin.right |
| 57 | + end |
| 58 | + if self.opts.margin.bottom then |
| 59 | + self.opts.win_opts.height = self.opts.win_opts.height - self.opts.margin.bottom |
| 60 | + end |
| 61 | + if self.opts.margin.left then |
| 62 | + self.opts.win_opts.width = self.opts.win_opts.width - self.opts.margin.left |
| 63 | + self.opts.win_opts.col = self.opts.win_opts.col + self.opts.margin.left |
| 64 | + end |
| 65 | + end |
| 66 | +end |
| 67 | + |
| 68 | +function M:mount() |
| 69 | + if self.opts.file then |
| 70 | + self.buf = vim.fn.bufadd(self.opts.file) |
| 71 | + vim.fn.bufload(self.buf) |
| 72 | + vim.bo[self.buf].modifiable = false |
| 73 | + elseif self.opts.buf then |
| 74 | + self.buf = self.opts.buf |
| 75 | + else |
| 76 | + self.buf = vim.api.nvim_create_buf(false, false) |
| 77 | + end |
| 78 | + |
| 79 | + ---@class LazyViewWinOpts |
| 80 | + local win_opts = { |
| 81 | + relative = "editor", |
| 82 | + style = "minimal", |
| 83 | + border = Config.options.ui.border, |
| 84 | + noautocmd = true, |
| 85 | + zindex = 50, |
| 86 | + } |
| 87 | + self.opts.win_opts = vim.tbl_extend("force", win_opts, self.opts.win_opts) |
| 88 | + if self.opts.win_opts.style == "" then |
| 89 | + self.opts.win_opts.style = nil |
| 90 | + end |
| 91 | + |
| 92 | + self:layout() |
| 93 | + self.win = vim.api.nvim_open_win(self.buf, true, self.opts.win_opts) |
| 94 | + self:focus() |
| 95 | + |
| 96 | + vim.bo[self.buf].buftype = "nofile" |
| 97 | + if vim.bo[self.buf].filetype == "" then |
| 98 | + vim.bo[self.buf].filetype = "lazy" |
| 99 | + end |
| 100 | + vim.bo[self.buf].bufhidden = "wipe" |
| 101 | + vim.wo[self.win].conceallevel = 3 |
| 102 | + vim.wo[self.win].spell = false |
| 103 | + vim.wo[self.win].wrap = true |
| 104 | + vim.wo[self.win].winhighlight = "Normal:LazyNormal" |
| 105 | + |
| 106 | + vim.api.nvim_create_autocmd("VimResized", { |
| 107 | + callback = function() |
| 108 | + if not self.win then |
| 109 | + return true |
| 110 | + end |
| 111 | + self:layout() |
| 112 | + local config = {} |
| 113 | + for _, key in ipairs({ "relative", "width", "height", "col", "row" }) do |
| 114 | + config[key] = self.opts.win_opts[key] |
| 115 | + end |
| 116 | + vim.api.nvim_win_set_config(self.win, config) |
| 117 | + end, |
| 118 | + }) |
| 119 | +end |
| 120 | + |
| 121 | +---@param events string|string[] |
| 122 | +---@param fn fun(self?):boolean? |
| 123 | +---@param opts? table |
| 124 | +function M:on(events, fn, opts) |
| 125 | + if type(events) == "string" then |
| 126 | + events = { events } |
| 127 | + end |
| 128 | + for _, e in ipairs(events) do |
| 129 | + local event, pattern = e:match("(%w+) (%w+)") |
| 130 | + event = event or e |
| 131 | + vim.api.nvim_create_autocmd( |
| 132 | + event, |
| 133 | + vim.tbl_extend("force", { |
| 134 | + pattern = pattern, |
| 135 | + buffer = (not pattern) and self.buf or nil, |
| 136 | + callback = function() |
| 137 | + return fn(self) |
| 138 | + end, |
| 139 | + }, opts or {}) |
| 140 | + ) |
| 141 | + end |
| 142 | +end |
| 143 | + |
| 144 | +---@param key string |
| 145 | +---@param fn fun(self?) |
| 146 | +---@param desc? string |
| 147 | +function M:on_key(key, fn, desc) |
| 148 | + vim.keymap.set("n", key, function() |
| 149 | + fn(self) |
| 150 | + end, { |
| 151 | + nowait = true, |
| 152 | + buffer = self.buf, |
| 153 | + desc = desc, |
| 154 | + }) |
| 155 | +end |
| 156 | + |
| 157 | +function M:close() |
| 158 | + local buf = self.buf |
| 159 | + local win = self.win |
| 160 | + self.win = nil |
| 161 | + self.buf = nil |
| 162 | + vim.diagnostic.reset(Config.ns, buf) |
| 163 | + vim.schedule(function() |
| 164 | + if win and vim.api.nvim_win_is_valid(win) then |
| 165 | + vim.api.nvim_win_close(win, true) |
| 166 | + end |
| 167 | + if buf and vim.api.nvim_buf_is_valid(buf) then |
| 168 | + vim.api.nvim_buf_delete(buf, { force = true }) |
| 169 | + end |
| 170 | + end) |
| 171 | +end |
| 172 | + |
| 173 | +function M:focus() |
| 174 | + vim.api.nvim_set_current_win(self.win) |
| 175 | + |
| 176 | + -- it seems that setting the current win doesn't work before VimEnter, |
| 177 | + -- so do that then |
| 178 | + if vim.v.vim_did_enter ~= 1 then |
| 179 | + vim.api.nvim_create_autocmd("VimEnter", { |
| 180 | + once = true, |
| 181 | + callback = function() |
| 182 | + if self.win and vim.api.nvim_win_is_valid(self.win) then |
| 183 | + pcall(vim.api.nvim_set_current_win, self.win) |
| 184 | + end |
| 185 | + return true |
| 186 | + end, |
| 187 | + }) |
| 188 | + end |
| 189 | +end |
| 190 | + |
| 191 | +return M |
0 commit comments