mirror of
https://github.com/SpaceVim/SpaceVim.git
synced 2025-02-03 12:30:05 +08:00
396 lines
9.8 KiB
Lua
Vendored
396 lines
9.8 KiB
Lua
Vendored
local Border = require("nui.popup.border")
|
|
local Object = require("nui.object")
|
|
local buf_storage = require("nui.utils.buf_storage")
|
|
local autocmd = require("nui.utils.autocmd")
|
|
local keymap = require("nui.utils.keymap")
|
|
|
|
local utils = require("nui.utils")
|
|
local _ = utils._
|
|
local defaults = utils.defaults
|
|
local is_type = utils.is_type
|
|
|
|
local layout_utils = require("nui.layout.utils")
|
|
local u = {
|
|
clear_namespace = _.clear_namespace,
|
|
get_next_id = _.get_next_id,
|
|
size = layout_utils.size,
|
|
position = layout_utils.position,
|
|
update_layout_config = layout_utils.update_layout_config,
|
|
}
|
|
|
|
-- luacov: disable
|
|
-- @deprecated
|
|
---@param opacity number
|
|
---@deprecated
|
|
local function calculate_winblend(opacity)
|
|
assert(0 <= opacity, "opacity must be equal or greater than 0")
|
|
assert(opacity <= 1, "opacity must be equal or lesser than 0")
|
|
return 100 - (opacity * 100)
|
|
end
|
|
-- luacov: enable
|
|
|
|
local function merge_default_options(options)
|
|
options.relative = defaults(options.relative, "win")
|
|
|
|
options.enter = defaults(options.enter, false)
|
|
options.zindex = defaults(options.zindex, 50)
|
|
|
|
options.buf_options = defaults(options.buf_options, {})
|
|
options.win_options = defaults(options.win_options, {})
|
|
|
|
options.border = defaults(options.border, "none")
|
|
|
|
return options
|
|
end
|
|
|
|
local function normalize_options(options)
|
|
options = _.normalize_layout_options(options)
|
|
|
|
if is_type("string", options.border) then
|
|
options.border = {
|
|
style = options.border,
|
|
}
|
|
end
|
|
|
|
return options
|
|
end
|
|
|
|
--luacheck: push no max line length
|
|
|
|
---@alias nui_popup_internal_position { relative: "'cursor'"|"'editor'"|"'win'", win: number, bufpos?: number[], row: number, col: number }
|
|
---@alias nui_popup_internal_size { height: number, width: number }
|
|
---@alias nui_popup_win_config { focusable: boolean, style: "'minimal'", zindex: number, relative: "'cursor'"|"'editor'"|"'win'", win?: number, bufpos?: number[], row: number, col: number, width: number, height: number, border?: table, anchor?: "NW"|"NE"|"SW"|"SE" }
|
|
---@alias nui_popup_internal { layout: nui_layout_config, layout_ready: boolean, loading: boolean, mounted: boolean, position: nui_popup_internal_position, size: nui_popup_internal_size, win_enter: boolean, unmanaged_bufnr?: boolean, buf_options: table<string,any>, win_options: table<string,any>, win_config: nui_popup_win_config }
|
|
|
|
--luacheck: pop
|
|
|
|
---@class NuiPopup
|
|
---@field border NuiPopupBorder
|
|
---@field bufnr integer
|
|
---@field ns_id integer
|
|
---@field private _ nui_popup_internal
|
|
---@field win_config nui_popup_win_config
|
|
---@field winid number
|
|
local Popup = Object("NuiPopup")
|
|
|
|
function Popup:init(options)
|
|
local id = u.get_next_id()
|
|
|
|
options = merge_default_options(options)
|
|
options = normalize_options(options)
|
|
|
|
self._ = {
|
|
id = id,
|
|
buf_options = options.buf_options,
|
|
layout = {},
|
|
layout_ready = false,
|
|
loading = false,
|
|
mounted = false,
|
|
win_enter = options.enter,
|
|
win_options = options.win_options,
|
|
win_config = {
|
|
focusable = options.focusable,
|
|
style = "minimal",
|
|
anchor = options.anchor,
|
|
zindex = options.zindex,
|
|
},
|
|
augroup = {
|
|
hide = string.format("%s_hide", id),
|
|
unmount = string.format("%s_unmount", id),
|
|
},
|
|
}
|
|
|
|
self.win_config = self._.win_config
|
|
|
|
self.ns_id = _.normalize_namespace_id(options.ns_id)
|
|
|
|
if options.bufnr then
|
|
self.bufnr = options.bufnr
|
|
self._.unmanaged_bufnr = true
|
|
else
|
|
self:_buf_create()
|
|
end
|
|
|
|
-- luacov: disable
|
|
-- @deprecated
|
|
if not self._.win_options.winblend and is_type("number", options.opacity) then
|
|
self._.win_options.winblend = calculate_winblend(options.opacity)
|
|
end
|
|
|
|
-- @deprecated
|
|
if not self._.win_options.winhighlight and not is_type("nil", options.highlight) then
|
|
self._.win_options.winhighlight = options.highlight
|
|
end
|
|
-- luacov: enable
|
|
|
|
self.border = Border(self, options.border)
|
|
self.win_config.border = self.border:get()
|
|
|
|
if options.position and options.size then
|
|
self:update_layout(options)
|
|
end
|
|
end
|
|
|
|
function Popup:_open_window()
|
|
if self.winid or not self.bufnr then
|
|
return
|
|
end
|
|
|
|
self.win_config.noautocmd = true
|
|
self.winid = vim.api.nvim_open_win(self.bufnr, self._.win_enter, self.win_config)
|
|
self.win_config.noautocmd = nil
|
|
|
|
vim.api.nvim_win_call(self.winid, function()
|
|
autocmd.exec("BufWinEnter", {
|
|
buffer = self.bufnr,
|
|
modeline = false,
|
|
})
|
|
end)
|
|
|
|
assert(self.winid, "failed to create popup window")
|
|
|
|
_.set_win_options(self.winid, self._.win_options)
|
|
end
|
|
|
|
function Popup:_close_window()
|
|
if not self.winid then
|
|
return
|
|
end
|
|
|
|
if vim.api.nvim_win_is_valid(self.winid) then
|
|
vim.api.nvim_win_close(self.winid, true)
|
|
end
|
|
|
|
self.winid = nil
|
|
end
|
|
|
|
function Popup:_buf_create()
|
|
if not self.bufnr then
|
|
self.bufnr = vim.api.nvim_create_buf(false, true)
|
|
assert(self.bufnr, "failed to create buffer")
|
|
end
|
|
end
|
|
|
|
function Popup:mount()
|
|
if not self._.layout_ready then
|
|
return error("layout is not ready")
|
|
end
|
|
|
|
if self._.loading or self._.mounted then
|
|
return
|
|
end
|
|
|
|
self._.loading = true
|
|
|
|
autocmd.create_group(self._.augroup.hide, { clear = true })
|
|
autocmd.create_group(self._.augroup.unmount, { clear = true })
|
|
autocmd.create("QuitPre", {
|
|
group = self._.augroup.unmount,
|
|
buffer = self.bufnr,
|
|
callback = vim.schedule_wrap(function()
|
|
self:unmount()
|
|
end),
|
|
}, self.bufnr)
|
|
autocmd.create("BufWinEnter", {
|
|
group = self._.augroup.unmount,
|
|
buffer = self.bufnr,
|
|
callback = function()
|
|
-- When two popup using the same buffer and both of them
|
|
-- are hiddden, calling `:show` for one of them fires
|
|
-- `BufWinEnter` for both of them. And in that scenario
|
|
-- one of them will not have `self.winid`.
|
|
if self.winid then
|
|
-- @todo skip registering `WinClosed` multiple times
|
|
-- for the same popup
|
|
autocmd.create("WinClosed", {
|
|
group = self._.augroup.hide,
|
|
pattern = tostring(self.winid),
|
|
callback = function()
|
|
self:hide()
|
|
end,
|
|
}, self.bufnr)
|
|
end
|
|
end,
|
|
}, self.bufnr)
|
|
|
|
self.border:mount()
|
|
|
|
self:_buf_create()
|
|
|
|
_.set_buf_options(self.bufnr, self._.buf_options)
|
|
|
|
self:_open_window()
|
|
|
|
self._.loading = false
|
|
self._.mounted = true
|
|
end
|
|
|
|
function Popup:hide()
|
|
if self._.loading or not self._.mounted then
|
|
return
|
|
end
|
|
|
|
self._.loading = true
|
|
|
|
pcall(autocmd.delete_group, self._.augroup.hide)
|
|
|
|
self.border:_close_window()
|
|
|
|
self:_close_window()
|
|
|
|
self._.loading = false
|
|
end
|
|
|
|
function Popup:show()
|
|
if self._.loading or not self._.mounted then
|
|
return
|
|
end
|
|
|
|
self._.loading = true
|
|
|
|
autocmd.create_group(self._.augroup.hide, { clear = true })
|
|
|
|
self.border:_open_window()
|
|
|
|
self:_open_window()
|
|
|
|
self._.loading = false
|
|
end
|
|
|
|
function Popup:_buf_destory()
|
|
if not self.bufnr then
|
|
return
|
|
end
|
|
|
|
if vim.api.nvim_buf_is_valid(self.bufnr) then
|
|
u.clear_namespace(self.bufnr, self.ns_id)
|
|
if not self._.unmanaged_bufnr then
|
|
vim.api.nvim_buf_delete(self.bufnr, { force = true })
|
|
end
|
|
end
|
|
|
|
buf_storage.cleanup(self.bufnr)
|
|
|
|
if not self._.unmanaged_bufnr then
|
|
self.bufnr = nil
|
|
end
|
|
end
|
|
|
|
function Popup:unmount()
|
|
if self._.loading or not self._.mounted then
|
|
return
|
|
end
|
|
|
|
self._.loading = true
|
|
|
|
pcall(autocmd.delete_group, self._.augroup.hide)
|
|
pcall(autocmd.delete_group, self._.augroup.unmount)
|
|
|
|
self.border:unmount()
|
|
|
|
self:_buf_destory()
|
|
|
|
self:_close_window()
|
|
|
|
self._.loading = false
|
|
self._.mounted = false
|
|
end
|
|
|
|
-- set keymap for this popup window
|
|
---@param mode string check `:h :map-modes`
|
|
---@param key string|string[] key for the mapping
|
|
---@param handler string | fun(): nil handler for the mapping
|
|
---@param opts table<"'expr'"|"'noremap'"|"'nowait'"|"'remap'"|"'script'"|"'silent'"|"'unique'", boolean>
|
|
---@return nil
|
|
function Popup:map(mode, key, handler, opts, force)
|
|
if not self.bufnr then
|
|
error("popup buffer not found.")
|
|
end
|
|
|
|
return keymap.set(self.bufnr, mode, key, handler, opts, force)
|
|
end
|
|
|
|
---@param mode string check `:h :map-modes`
|
|
---@param key string|string[] key for the mapping
|
|
---@return nil
|
|
function Popup:unmap(mode, key, force)
|
|
if not self.bufnr then
|
|
error("popup buffer not found.")
|
|
end
|
|
|
|
return keymap._del(self.bufnr, mode, key, force)
|
|
end
|
|
|
|
---@param event string | string[]
|
|
---@param handler string | function
|
|
---@param options nil | table<"'once'" | "'nested'", boolean>
|
|
function Popup:on(event, handler, options)
|
|
if not self.bufnr then
|
|
error("popup buffer not found.")
|
|
end
|
|
|
|
autocmd.buf.define(self.bufnr, event, handler, options)
|
|
end
|
|
|
|
---@param event nil | string | string[]
|
|
function Popup:off(event)
|
|
if not self.bufnr then
|
|
error("popup buffer not found.")
|
|
end
|
|
|
|
autocmd.buf.remove(self.bufnr, nil, event)
|
|
end
|
|
|
|
-- luacov: disable
|
|
-- @deprecated
|
|
-- Use `popup:update_layout`.
|
|
---@deprecated
|
|
function Popup:set_layout(config)
|
|
return self:update_layout(config)
|
|
end
|
|
-- luacov: enable
|
|
|
|
---@param config? nui_layout_config
|
|
function Popup:update_layout(config)
|
|
config = config or {}
|
|
|
|
u.update_layout_config(self._, config)
|
|
|
|
self.border:_relayout()
|
|
|
|
self._.layout_ready = true
|
|
|
|
if self.winid then
|
|
-- upstream issue: https://github.com/neovim/neovim/issues/20370
|
|
local win_config_style = self.win_config.style
|
|
---@diagnostic disable-next-line: assign-type-mismatch
|
|
self.win_config.style = ""
|
|
vim.api.nvim_win_set_config(self.winid, self.win_config)
|
|
self.win_config.style = win_config_style
|
|
end
|
|
end
|
|
|
|
-- luacov: disable
|
|
-- @deprecated
|
|
-- Use `popup:update_layout`.
|
|
---@deprecated
|
|
function Popup:set_size(size)
|
|
self:update_layout({ size = size })
|
|
end
|
|
-- luacov: enable
|
|
|
|
-- luacov: disable
|
|
-- @deprecated
|
|
-- Use `popup:update_layout`.
|
|
---@deprecated
|
|
function Popup:set_position(position, relative)
|
|
self:update_layout({ position = position, relative = relative })
|
|
end
|
|
-- luacov: enable
|
|
|
|
---@alias NuiPopup.constructor fun(options: table): NuiPopup
|
|
---@type NuiPopup|NuiPopup.constructor
|
|
local NuiPopup = Popup
|
|
|
|
return NuiPopup
|