summaryrefslogtreecommitdiff
path: root/start/cmp/lua/cmp/utils/window.lua
diff options
context:
space:
mode:
Diffstat (limited to 'start/cmp/lua/cmp/utils/window.lua')
-rw-r--r--start/cmp/lua/cmp/utils/window.lua313
1 files changed, 313 insertions, 0 deletions
diff --git a/start/cmp/lua/cmp/utils/window.lua b/start/cmp/lua/cmp/utils/window.lua
new file mode 100644
index 0000000..a8a271e
--- /dev/null
+++ b/start/cmp/lua/cmp/utils/window.lua
@@ -0,0 +1,313 @@
+local cache = require('cmp.utils.cache')
+local misc = require('cmp.utils.misc')
+local buffer = require('cmp.utils.buffer')
+local api = require('cmp.utils.api')
+
+---@class cmp.WindowStyle
+---@field public relative string
+---@field public row number
+---@field public col number
+---@field public width number
+---@field public height number
+---@field public border string|string[]|nil
+---@field public zindex number|nil
+
+---@class cmp.Window
+---@field public name string
+---@field public win number|nil
+---@field public thumb_win number|nil
+---@field public sbar_win number|nil
+---@field public style cmp.WindowStyle
+---@field public opt table<string, any>
+---@field public buffer_opt table<string, any>
+---@field public cache cmp.Cache
+local window = {}
+
+---new
+---@return cmp.Window
+window.new = function()
+ local self = setmetatable({}, { __index = window })
+ self.name = misc.id('cmp.utils.window.new')
+ self.win = nil
+ self.sbar_win = nil
+ self.thumb_win = nil
+ self.style = {}
+ self.cache = cache.new()
+ self.opt = {}
+ self.buffer_opt = {}
+ return self
+end
+
+---Set window option.
+---NOTE: If the window already visible, immediately applied to it.
+---@param key string
+---@param value any
+window.option = function(self, key, value)
+ if vim.fn.exists('+' .. key) == 0 then
+ return
+ end
+
+ if value == nil then
+ return self.opt[key]
+ end
+
+ self.opt[key] = value
+ if self:visible() then
+ vim.api.nvim_win_set_option(self.win, key, value)
+ end
+end
+
+---Set buffer option.
+---NOTE: If the buffer already visible, immediately applied to it.
+---@param key string
+---@param value any
+window.buffer_option = function(self, key, value)
+ if vim.fn.exists('+' .. key) == 0 then
+ return
+ end
+
+ if value == nil then
+ return self.buffer_opt[key]
+ end
+
+ self.buffer_opt[key] = value
+ local existing_buf = buffer.get(self.name)
+ if existing_buf then
+ vim.api.nvim_buf_set_option(existing_buf, key, value)
+ end
+end
+
+---Set style.
+---@param style cmp.WindowStyle
+window.set_style = function(self, style)
+ self.style = style
+ local info = self:info()
+
+ if vim.o.lines and vim.o.lines <= info.row + info.height + 1 then
+ self.style.height = vim.o.lines - info.row - info.border_info.vert - 1
+ end
+
+ self.style.zindex = self.style.zindex or 1
+end
+
+---Return buffer id.
+---@return number
+window.get_buffer = function(self)
+ local buf, created_new = buffer.ensure(self.name)
+ if created_new then
+ for k, v in pairs(self.buffer_opt) do
+ vim.api.nvim_buf_set_option(buf, k, v)
+ end
+ end
+ return buf
+end
+
+---Open window
+---@param style cmp.WindowStyle
+window.open = function(self, style)
+ if style then
+ self:set_style(style)
+ end
+
+ if self.style.width < 1 or self.style.height < 1 then
+ return
+ end
+
+ if self.win and vim.api.nvim_win_is_valid(self.win) then
+ vim.api.nvim_win_set_config(self.win, self.style)
+ else
+ local s = misc.copy(self.style)
+ s.noautocmd = true
+ self.win = vim.api.nvim_open_win(self:get_buffer(), false, s)
+ for k, v in pairs(self.opt) do
+ vim.api.nvim_win_set_option(self.win, k, v)
+ end
+ end
+ self:update()
+end
+
+---Update
+window.update = function(self)
+ local info = self:info()
+ if info.scrollable then
+ -- Draw the background of the scrollbar
+
+ if not info.border_info.visible then
+ local style = {
+ relative = 'editor',
+ style = 'minimal',
+ width = 1,
+ height = self.style.height,
+ row = info.row,
+ col = info.col + info.width - info.scrollbar_offset, -- info.col was already contained the scrollbar offset.
+ zindex = (self.style.zindex and (self.style.zindex + 1) or 1),
+ }
+ if self.sbar_win and vim.api.nvim_win_is_valid(self.sbar_win) then
+ vim.api.nvim_win_set_config(self.sbar_win, style)
+ else
+ style.noautocmd = true
+ self.sbar_win = vim.api.nvim_open_win(buffer.ensure(self.name .. 'sbar_buf'), false, style)
+ vim.api.nvim_win_set_option(self.sbar_win, 'winhighlight', 'EndOfBuffer:PmenuSbar,NormalFloat:PmenuSbar')
+ end
+ end
+
+ -- Draw the scrollbar thumb
+ local thumb_height = math.floor(info.inner_height * (info.inner_height / self:get_content_height()) + 0.5)
+ local thumb_offset = math.floor(info.inner_height * (vim.fn.getwininfo(self.win)[1].topline / self:get_content_height()))
+
+ local style = {
+ relative = 'editor',
+ style = 'minimal',
+ width = 1,
+ height = math.max(1, thumb_height),
+ row = info.row + thumb_offset + (info.border_info.visible and info.border_info.top or 0),
+ col = info.col + info.width - 1, -- info.col was already added scrollbar offset.
+ zindex = (self.style.zindex and (self.style.zindex + 2) or 2),
+ }
+ if self.thumb_win and vim.api.nvim_win_is_valid(self.thumb_win) then
+ vim.api.nvim_win_set_config(self.thumb_win, style)
+ else
+ style.noautocmd = true
+ self.thumb_win = vim.api.nvim_open_win(buffer.ensure(self.name .. 'thumb_buf'), false, style)
+ vim.api.nvim_win_set_option(self.thumb_win, 'winhighlight', 'EndOfBuffer:PmenuThumb,NormalFloat:PmenuThumb')
+ end
+ else
+ if self.sbar_win and vim.api.nvim_win_is_valid(self.sbar_win) then
+ vim.api.nvim_win_hide(self.sbar_win)
+ self.sbar_win = nil
+ end
+ if self.thumb_win and vim.api.nvim_win_is_valid(self.thumb_win) then
+ vim.api.nvim_win_hide(self.thumb_win)
+ self.thumb_win = nil
+ end
+ end
+
+ -- In cmdline, vim does not redraw automatically.
+ if api.is_cmdline_mode() then
+ vim.api.nvim_win_call(self.win, function()
+ misc.redraw()
+ end)
+ end
+end
+
+---Close window
+window.close = function(self)
+ if self.win and vim.api.nvim_win_is_valid(self.win) then
+ if self.win and vim.api.nvim_win_is_valid(self.win) then
+ vim.api.nvim_win_hide(self.win)
+ self.win = nil
+ end
+ if self.sbar_win and vim.api.nvim_win_is_valid(self.sbar_win) then
+ vim.api.nvim_win_hide(self.sbar_win)
+ self.sbar_win = nil
+ end
+ if self.thumb_win and vim.api.nvim_win_is_valid(self.thumb_win) then
+ vim.api.nvim_win_hide(self.thumb_win)
+ self.thumb_win = nil
+ end
+ end
+end
+
+---Return the window is visible or not.
+window.visible = function(self)
+ return self.win and vim.api.nvim_win_is_valid(self.win)
+end
+
+---Return win info.
+window.info = function(self)
+ local border_info = self:get_border_info()
+ local info = {
+ row = self.style.row,
+ col = self.style.col,
+ width = self.style.width + border_info.left + border_info.right,
+ height = self.style.height + border_info.top + border_info.bottom,
+ inner_width = self.style.width,
+ inner_height = self.style.height,
+ border_info = border_info,
+ scrollable = false,
+ scrollbar_offset = 0,
+ }
+
+ if self:get_content_height() > info.inner_height then
+ info.scrollable = true
+ if not border_info.visible then
+ info.scrollbar_offset = 1
+ info.width = info.width + 1
+ end
+ end
+
+ return info
+end
+
+---Return border information.
+---@return { top: number, left: number, right: number, bottom: number, vert: number, horiz: number, visible: boolean }
+window.get_border_info = function(self)
+ local border = self.style.border
+ if not border or border == 'none' then
+ return {
+ top = 0,
+ left = 0,
+ right = 0,
+ bottom = 0,
+ vert = 0,
+ horiz = 0,
+ visible = false,
+ }
+ end
+ if type(border) == 'string' then
+ if border == 'shadow' then
+ return {
+ top = 0,
+ left = 0,
+ right = 1,
+ bottom = 1,
+ vert = 1,
+ horiz = 1,
+ visible = false,
+ }
+ end
+ return {
+ top = 1,
+ left = 1,
+ right = 1,
+ bottom = 1,
+ vert = 2,
+ horiz = 2,
+ visible = true,
+ }
+ end
+
+ local new_border = {}
+ while #new_border <= 8 do
+ for _, b in ipairs(border) do
+ table.insert(new_border, type(b) == 'string' and b or b[1])
+ end
+ end
+ local info = {}
+ info.top = new_border[2] == '' and 0 or 1
+ info.right = new_border[4] == '' and 0 or 1
+ info.bottom = new_border[6] == '' and 0 or 1
+ info.left = new_border[8] == '' and 0 or 1
+ info.vert = info.top + info.bottom
+ info.horiz = info.left + info.right
+ info.visible = not (vim.tbl_contains({ '', ' ' }, new_border[2]) and vim.tbl_contains({ '', ' ' }, new_border[4]) and vim.tbl_contains({ '', ' ' }, new_border[6]) and vim.tbl_contains({ '', ' ' }, new_border[8]))
+ return info
+end
+
+---Get scroll height.
+---NOTE: The result of vim.fn.strdisplaywidth depends on the buffer it was called in (see comment in cmp.Entry.get_view).
+---@return number
+window.get_content_height = function(self)
+ if not self:option('wrap') then
+ return vim.api.nvim_buf_line_count(self:get_buffer())
+ end
+ local height = 0
+ vim.api.nvim_buf_call(self:get_buffer(), function()
+ for _, text in ipairs(vim.api.nvim_buf_get_lines(self:get_buffer(), 0, -1, false)) do
+ height = height + math.max(1, math.ceil(vim.fn.strdisplaywidth(text) / self.style.width))
+ end
+ end)
+ return height
+end
+
+return window