sighelp.nvim/lua/signup/helper.lua

312 lines
8.4 KiB
Lua

local api = vim.api
local function get_parameter_label(result)
local signatures = result.signatures
local activeSignature = result.activeSignature or 0
activeSignature = activeSignature + 1
local signature = signatures[activeSignature]
if signature == nil or signature.parameters == nil then -- no parameter
return ""
end
local activeParameter = signature.activeParameter or result.activeParameter
if activeParameter == nil or activeParameter < 0 then
return ""
end
if signature.parameters == nil then
return ""
end
if activeParameter > #signature.parameters then
activeParameter = 0
end
-- local nextParameter = signature.parameters[activeParameter + 1]
local param = signature.parameters[activeParameter + 1].label
local param_str
-- Handle both string and table parameter labels
if type(param) == "table" then
local s = param[1]
local e = param[2]
param_str = string.sub(signature.label, s, e)
elseif type(param) == "string" then
param_str = label
end
return param_str
end
---@class opts table
---@field brk_chars string string of characters to break lines at
---@field indent_str string string to indent wrapped lines with
---@param str string string to wrap into lines
---@param maxlen integer maximum length of lines
---@return table table of wrapped lines
local function _wrap_lines(str, maxlen, opts)
local atoms = {}
opts = opts or {}
local brk_chars = opts.brk_chars or ",()"
local indent_str = opts.indent_str or " "
local end_char = string.sub(brk_chars, 1, 1)
str = str .. end_char -- add break char at end of string, so we can match the end of the line
for w in string.gmatch(str, "[^" .. brk_chars .. "]*" .. "[" .. brk_chars .. "]") do
table.insert(atoms, w)
end
atoms[#atoms] = string.gsub(atoms[#atoms], end_char .. "$", "") -- remove break char at end
local lines = {}
lines[1] = ""
local line_num = 1
local padding = opts.padding or 2
local is = ""
for i, w in ipairs(atoms) do
if line_num == 1 then
is = ""
else
is = indent_str
end
if string.len(lines[line_num]) + string.len(w) + (padding * 2) <= maxlen then
lines[line_num] = lines[line_num] .. w
else
lines[line_num] = string.rep(" ", padding) .. is .. lines[line_num] .. string.rep(" ", padding) --add padding
line_num = line_num + 1
lines[line_num] = string.gsub(w, "^%s", "")
end
if i == #atoms then
lines[line_num] = string.rep(" ", padding) .. is .. lines[line_num] .. string.rep(" ", padding) --add padding to last line
end
end
return lines
end
---@param signature_help table
---@param maxlen integer
local function convert_signature_help_to_lines(signature_help, maxlen)
if not signature_help.signatures then
return
end
local lines = {}
local active_signature = signature_help.activeSignature or 0
if active_signature >= #signature_help.signatures or active_signature < 0 then
active_signature = 0
end
local signature = signature_help.signatures[active_signature + 1]
if not signature then
return
end
vim.list_extend(lines, vim.split(signature.label, "\n", { plain = true, trimempty = true }))
local wrapped_lines = {}
for _, ll in ipairs(lines) do
vim.list_extend(wrapped_lines, _wrap_lines(ll, maxlen))
end
return wrapped_lines
end
local function apply_treesitter_highlighting(buf, syntax)
if not api.nvim_buf_is_valid(buf) then
return
end
if not pcall(require, "nvim-treesitter") then
return
end
-- Store current window and buffer
local current_win = api.nvim_get_current_win()
local current_buf = api.nvim_get_current_buf()
-- Apply treesitter highlighting
pcall(function()
require("nvim-treesitter.highlight").attach(buf, syntax)
end)
-- Restore focus
api.nvim_set_current_win(current_win)
api.nvim_set_current_buf(current_buf)
end
local _make_floating_popup_size = function(contents, opts)
opts = opts or {}
local max_width = opts.max_width or 80
local max_height = opts.max_height or 10
local line_widths = {}
local width = 0
for i, line in ipairs(contents) do
line_widths[i] = vim.fn.strdisplaywidth(line:gsub("%z", "\n"))
width = math.max(line_widths[i], width)
end
-- local border_width = get_border_size(opts).width
local border_width = 2 --TODO: do we need to deal with this parametrically?
local screen_width = api.nvim_win_get_width(0)
width = math.min(width, screen_width)
width = math.min(width, max_width)
-- make sure borders are always inside the screen
if width + border_width > screen_width then
width = screen_width - border_width
end
local height = #contents
height = 0
if vim.tbl_isempty(line_widths) then
for _, line in ipairs(contents) do
local line_width = vim.fn.strdisplaywidth(line:gsub("%z", "\n"))
height = height + math.ceil(line_width / max_width)
end
else
for i = 1, #contents do
height = height + math.max(1, math.ceil(line_widths[i] / max_width))
end
end
if max_height then
height = math.min(height, max_height)
end
return width, height
end
---@param content table window content
---@param opts table window options
---@returns (table) Options
local function _make_floating_popup_options(content, opts)
local width, height = _make_floating_popup_size(content, opts)
local offset_y = opts.offset_y or 0
local offset_x = opts.offset_x or 0
local anchor_bias = opts.anchor_bias or "auto"
local relative = opts.relative or "cursor"
local anchor = ""
local row, col
local lines_above = opts.relative == "mouse" and vim.fn.getmousepos().line - 1 or vim.fn.winline() - 1
local lines_below = vim.fn.winheight(0) - lines_above
if
lines_above < height
or (anchor_bias == "below" and lines_below > height + offset_y)
or (anchor_bias == "auto" and lines_below > lines_above)
then
anchor = anchor .. "N"
height = math.min(lines_below - offset_y, height)
row = 1 + offset_y
else
anchor = anchor .. "S"
height = math.min(lines_above - offset_y, height)
row = 0 - offset_y
end
local wincol = opts.relative == "mouse" and vim.fn.getmousepos().column or vim.fn.wincol()
if wincol + width + offset_x <= vim.o.columns then
anchor = anchor .. "W"
col = 0 - wincol
else
anchor = anchor .. "E"
col = 1 - wincol
end
offset_x = opts.offset_x or 0
local first_col = vim.fn.getwininfo(vim.fn.win_getid())[1].textoff
return {
anchor = anchor,
row = row,
col = col + first_col + offset_x,
width = width,
height = height,
focusable = opts.focusable,
relative = relative,
style = "minimal",
border = opts.border or "rounded",
zindex = opts.zindex or 50,
}
end
local function create_float_window(content, syntax, opts)
local npcall = vim.F.npcall
vim.validate({
contents = { content, "t" },
syntax = { syntax, "s", true },
opts = { opts, "t", true },
})
opts = opts or {}
local bufnr = api.nvim_get_current_buf()
local existing_float = npcall(api.nvim_buf_get_var, bufnr, "lsp_floating_preview")
if existing_float and api.nvim_win_is_valid(existing_float) then
api.nvim_win_close(existing_float, true)
end
-- Create the buffer
local floating_bufnr = api.nvim_create_buf(false, true)
if syntax then
vim.bo[floating_bufnr].syntax = syntax
vim.bo[floating_bufnr].ft = syntax
vim.treesitter.start(floating_bufnr)
end
api.nvim_buf_set_lines(floating_bufnr, 0, -1, true, content)
local float_options = _make_floating_popup_options(content, opts)
local floating_winnr = api.nvim_open_win(floating_bufnr, false, float_options)
vim.wo[floating_winnr].conceallevel = 2
vim.wo[floating_winnr].foldenable = false
vim.bo[floating_bufnr].modifiable = false
vim.bo[floating_bufnr].bufhidden = "wipe"
api.nvim_buf_set_keymap(
floating_bufnr,
"n",
"q",
"<cmd>bdelete<cr>",
{ silent = true, noremap = true, nowait = true }
)
-- save focus_id
api.nvim_buf_set_var(bufnr, "lsp_floating_preview", floating_winnr)
return floating_bufnr, floating_winnr
end
local function close_float_window(win, buf)
-- Store current window and buffer
local current_win = api.nvim_get_current_win()
local current_buf = api.nvim_get_current_buf()
if win and api.nvim_win_is_valid(win) then
pcall(api.nvim_win_close, win, true)
end
if buf and api.nvim_buf_is_valid(buf) then
pcall(api.nvim_buf_delete, buf, { force = true })
end
win = nil
buf = nil
-- Restore focus
pcall(api.nvim_set_current_win, current_win)
pcall(api.nvim_set_current_buf, current_buf)
-- end
end
return {
get_parameter_label = get_parameter_label,
convert_signature_help_to_lines = convert_signature_help_to_lines,
apply_treesitter_highlighting = apply_treesitter_highlighting,
create_float_window = create_float_window,
close_float_window = close_float_window,
}