425 lines
10 KiB
Lua
425 lines
10 KiB
Lua
local api = vim.api
|
|
local helper = require("sighelp.helper")
|
|
|
|
local M = {}
|
|
|
|
local SignatureHelp = {}
|
|
SignatureHelp.__index = SignatureHelp
|
|
|
|
function SignatureHelp.new()
|
|
local instance = setmetatable({
|
|
_display_sig_win = function() end, -- function to open signatures window
|
|
_display_doc_win = function() end, -- function to open documentation window
|
|
sig_content = nil, -- current signature buffer content
|
|
sig_winnr = nil,
|
|
sig_bufnr = nil,
|
|
sig_win_height = nil,
|
|
sig_win_showing = false,
|
|
ns = nil,
|
|
markid = nil,
|
|
timer = nil,
|
|
current_signatures = nil,
|
|
current_signature_idx = nil,
|
|
|
|
doc_winnr = nil,
|
|
doc_bufnr = nil,
|
|
doc_win_showing = false,
|
|
doc_win_focused = false,
|
|
config = nil,
|
|
|
|
active_winnr = vim.api.nvim_get_current_win(),
|
|
}, SignatureHelp)
|
|
|
|
instance._default_config = {
|
|
silent = false,
|
|
icons = {
|
|
parameter = "",
|
|
method = " ",
|
|
documentation = " ",
|
|
},
|
|
colors = {
|
|
parameter = "#86e1fc",
|
|
method = "#c099ff",
|
|
documentation = "#4fd6be",
|
|
default_value = "#a80888",
|
|
},
|
|
active_parameter_colors = {
|
|
bg = "#86e1fc",
|
|
fg = "#1a1a1a",
|
|
},
|
|
hi_parameter = "LspSignatureActiveParameter",
|
|
win_opts = {
|
|
border = "rounded",
|
|
winblend = 10,
|
|
zindex = 200,
|
|
max_height = 20,
|
|
max_width = 80,
|
|
anchor_bias = "below", -- below|above|auto
|
|
offset_y = 0,
|
|
offset_x = 0,
|
|
},
|
|
|
|
trigger_chars = { "(", "," },
|
|
auto_close = true,
|
|
preview_parameters = true,
|
|
debounce_time = 100,
|
|
render_style = {
|
|
separator = true,
|
|
compact = true,
|
|
align_icons = true,
|
|
},
|
|
}
|
|
|
|
return instance
|
|
end
|
|
|
|
function SignatureHelp:hide_sig_win()
|
|
if self.sig_win_showing then
|
|
helper.close_float_window(self.sig_winnr)
|
|
end
|
|
helper.delete_buffer(self.sig_bufnr)
|
|
helper.delete_buffer(self.doc_bufnr)
|
|
|
|
self:hide_doc_win()
|
|
|
|
self.sig_winnr = nil
|
|
self.sig_bufnr = nil
|
|
self.doc_bufnr = nil -- we want to nil this here and not when we hide the doc win in case we want to reopen the doc win
|
|
self.current_signatures = nil
|
|
self.sig_win_showing = false
|
|
end
|
|
|
|
function SignatureHelp:hide_doc_win()
|
|
if self.doc_win_showing then
|
|
helper.close_float_window(self.doc_winnr)
|
|
self.doc_winnr = nil
|
|
self.doc_win_showing = false
|
|
end
|
|
end
|
|
|
|
function SignatureHelp:extract_default_value(param_info)
|
|
-- Check if parameter has documentation that might contain default value
|
|
if not param_info.documentation then
|
|
return nil
|
|
end
|
|
|
|
local doc = type(param_info.documentation) == "string" and param_info.documentation
|
|
or param_info.documentation.value
|
|
|
|
-- Look for common default value patterns
|
|
local patterns = {
|
|
"default:%s*([^%s]+)",
|
|
"defaults%s+to%s+([^%s]+)",
|
|
"%(default:%s*([^%)]+)%)",
|
|
}
|
|
|
|
for _, pattern in ipairs(patterns) do
|
|
local default = doc:match(pattern)
|
|
if default then
|
|
return default
|
|
end
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
function SignatureHelp:set_active_parameter_highlights(param_str, lines)
|
|
if not self.sig_bufnr or not api.nvim_buf_is_valid(self.sig_bufnr) then
|
|
return
|
|
end
|
|
|
|
self.ns = api.nvim_create_namespace("lsp_signature_hi_parameter")
|
|
local hi = self.config.hi_parameter
|
|
|
|
local s, e, line_num
|
|
for l, line in ipairs(lines) do
|
|
local ss, ee, _ = string.find(line, param_str, 1, true)
|
|
if ss ~= nil then
|
|
line_num = l - 1
|
|
s = ss
|
|
e = ee
|
|
end
|
|
end
|
|
|
|
if s and e and s > 0 then
|
|
self.markid = api.nvim_buf_set_extmark(
|
|
self.sig_bufnr,
|
|
self.ns,
|
|
line_num,
|
|
s,
|
|
{ id = self.markid, end_line = line_num, end_col = e, hl_group = hi, strict = false }
|
|
)
|
|
end
|
|
end
|
|
|
|
function SignatureHelp:display_sig_win(result)
|
|
if not result or not result.signatures or #result.signatures == 0 then
|
|
self:hide_sig_win()
|
|
if self.doc_win_showing then
|
|
self:hide_doc_win()
|
|
end
|
|
return
|
|
end
|
|
|
|
if not self.sig_win_showing then
|
|
local ft = vim.bo.ft
|
|
self._display_sig_win, self._display_doc_win = helper.create_float_windows(result, ft, self.config.win_opts)
|
|
self.sig_bufnr, self.sig_winnr, self.sig_content = self._display_sig_win()
|
|
|
|
local param_str = helper.get_parameter_label(result)
|
|
self:set_active_parameter_highlights(param_str, self.sig_content)
|
|
self.sig_win_showing = true
|
|
end
|
|
local param_str = helper.get_parameter_label(result)
|
|
self:set_active_parameter_highlights(param_str, self.sig_content)
|
|
end
|
|
|
|
function SignatureHelp:display_doc_win()
|
|
if self.doc_win_showing then
|
|
return
|
|
else
|
|
self.active_winnr = vim.api.nvim_get_current_win()
|
|
self.doc_bufnr, self.doc_winnr = self._display_doc_win()
|
|
self.doc_win_showing = true
|
|
self.doc_win_focused = true
|
|
vim.api.nvim_set_current_win(self.doc_winnr)
|
|
|
|
-- set window variable that can be checked externally for i.e. setting keymaps
|
|
local w = vim.w
|
|
w.sighelp_doc_win = true
|
|
vim.w = w
|
|
|
|
local exit_docs = function()
|
|
vim.api.nvim_set_current_win(self.active_winnr)
|
|
vim.fn.feedkeys("i", "t") --put us back into insert mode
|
|
self.doc_win_focused = false
|
|
end
|
|
|
|
if self.doc_bufnr ~= nil then
|
|
vim.keymap.set(
|
|
"n",
|
|
"q",
|
|
exit_docs,
|
|
{ silent = true, noremap = true, nowait = true, buffer = self.doc_bufnr }
|
|
)
|
|
vim.keymap.set(
|
|
"n",
|
|
"<esc>",
|
|
exit_docs,
|
|
{ silent = true, noremap = true, nowait = true, buffer = self.doc_bufnr }
|
|
)
|
|
end
|
|
end
|
|
end
|
|
|
|
function SignatureHelp:trigger()
|
|
if not vim.b.signatureHelp then
|
|
return
|
|
end
|
|
|
|
local params = vim.lsp.util.make_position_params()
|
|
vim.lsp.buf_request(0, "textDocument/signatureHelp", params, function(err, result, _, _)
|
|
if err then
|
|
if not self.config.silent then
|
|
vim.notify("Error in LSP Signature Help: " .. vim.inspect(err), vim.log.levels.ERROR)
|
|
end
|
|
self:hide_sig_win()
|
|
self:hide_doc_win()
|
|
return
|
|
end
|
|
|
|
if result and result.signatures and #result.signatures > 0 then
|
|
self.current_signatures = result.signatures
|
|
|
|
self:display_sig_win(result)
|
|
else
|
|
self:hide_sig_win()
|
|
self:hide_doc_win()
|
|
-- Only notify if not silent and if there was actually no signature help
|
|
if not self.config.silent and result then
|
|
vim.notify("No signature help available", vim.log.levels.INFO)
|
|
end
|
|
end
|
|
end)
|
|
end
|
|
|
|
function SignatureHelp:check_capability()
|
|
local bufnr = vim.api.nvim_get_current_buf()
|
|
local clients = vim.lsp.get_clients({ bufnr = bufnr })
|
|
for _, client in ipairs(clients) do
|
|
if client.server_capabilities.signatureHelpProvider then
|
|
vim.b.signatureHelp = true
|
|
return
|
|
end
|
|
end
|
|
end
|
|
|
|
function SignatureHelp:setup_autocmds()
|
|
api.nvim_create_autocmd({ "LspAttach", "BufEnter" }, {
|
|
-- group = group,
|
|
callback = function()
|
|
vim.defer_fn(function()
|
|
local group = api.nvim_create_augroup("LspSignatureHelp", { clear = true })
|
|
self:check_capability()
|
|
|
|
if not vim.b.signatureHelp then
|
|
return
|
|
end
|
|
|
|
local function debounced_trigger()
|
|
if self.timer then
|
|
vim.fn.timer_stop(self.timer)
|
|
end
|
|
self.timer = vim.fn.timer_start(self.debounce_time, function()
|
|
self:trigger()
|
|
end)
|
|
end
|
|
|
|
api.nvim_create_autocmd({ "InsertEnter", "CursorMovedI" }, {
|
|
group = group,
|
|
callback = function()
|
|
debounced_trigger()
|
|
end,
|
|
})
|
|
|
|
-- make sure signature help windows get closed if we switch out of the doc window
|
|
-- instead of exiting it using the exit keymaps
|
|
api.nvim_create_autocmd({ "WinEnter" }, {
|
|
group = group,
|
|
callback = function()
|
|
vim.defer_fn(function()
|
|
if
|
|
self.active_winnr == vim.api.nvim_get_current_win()
|
|
and vim.api.nvim_get_mode().mode == "n"
|
|
then
|
|
self:hide_sig_win()
|
|
self:hide_doc_win()
|
|
end
|
|
end, 30)
|
|
end,
|
|
})
|
|
|
|
api.nvim_create_autocmd({ "CursorMoved" }, {
|
|
group = group,
|
|
callback = function()
|
|
if self.normal_mode_active then
|
|
debounced_trigger()
|
|
end
|
|
end,
|
|
})
|
|
|
|
api.nvim_create_autocmd({ "InsertLeave" }, {
|
|
group = group,
|
|
callback = function()
|
|
if self.doc_win_focused == false then
|
|
self:hide_sig_win()
|
|
self:hide_doc_win()
|
|
self.normal_mode_active = false
|
|
end
|
|
end,
|
|
})
|
|
end, 100)
|
|
end,
|
|
})
|
|
end
|
|
|
|
-- Add navigation between multiple signatures
|
|
function SignatureHelp:next_signature()
|
|
if not self.current_signatures then
|
|
return
|
|
end
|
|
self.current_signature_idx = (self.current_signature_idx or 0) + 1
|
|
if self.current_signature_idx > #self.current_signatures then
|
|
self.current_signature_idx = 1
|
|
end
|
|
self:display_sig_win({
|
|
signatures = self.current_signatures,
|
|
activeParameter = self.current_active_parameter,
|
|
activeSignature = self.current_signature_idx - 1,
|
|
})
|
|
end
|
|
|
|
function SignatureHelp:prev_signature()
|
|
if not self.current_signatures then
|
|
return
|
|
end
|
|
self.current_signature_idx = (self.current_signature_idx or 1) - 1
|
|
if self.current_signature_idx < 1 then
|
|
self.current_signature_idx = #self.current_signatures
|
|
end
|
|
self:display_sig_win({
|
|
signatures = self.current_signatures,
|
|
activeParameter = self.current_active_parameter,
|
|
activeSignature = self.current_signature_idx - 1,
|
|
})
|
|
end
|
|
|
|
function SignatureHelp:toggle_docs()
|
|
if self.doc_win_showing then
|
|
self:hide_doc_win()
|
|
elseif self.sig_win_showing then
|
|
self:display_doc_win()
|
|
end
|
|
end
|
|
|
|
function M.setup(opts)
|
|
-- Ensure setup is called only once
|
|
if M._initialized then
|
|
return M._instance
|
|
end
|
|
|
|
opts = opts or {}
|
|
local signature_help = SignatureHelp.new()
|
|
|
|
-- Deep merge user config with defaults
|
|
signature_help.config = vim.tbl_deep_extend("force", signature_help._default_config, opts)
|
|
|
|
-- Setup highlights with user config
|
|
local function setup_highlights()
|
|
local colors = signature_help.config.colors
|
|
local highlights = {
|
|
SignatureHelpBorder = { link = "FloatBorder" },
|
|
SignatureHelpMethod = { fg = colors.method },
|
|
SignatureHelpParameter = { fg = colors.parameter },
|
|
SignatureHelpDocumentation = { fg = colors.documentation },
|
|
SignatureHelpDefaultValue = { fg = colors.default_value, italic = true },
|
|
LspSignatureActiveParameter = {
|
|
fg = signature_help.config.active_parameter_colors.fg,
|
|
bg = signature_help.config.active_parameter_colors.bg,
|
|
},
|
|
}
|
|
|
|
for group, hl_opts in pairs(highlights) do
|
|
vim.api.nvim_set_hl(0, group, hl_opts)
|
|
end
|
|
end
|
|
|
|
-- Setup highlights and ensure they persist across colorscheme changes
|
|
setup_highlights()
|
|
vim.api.nvim_create_autocmd("ColorScheme", {
|
|
group = vim.api.nvim_create_augroup("LspSignatureColors", { clear = true }),
|
|
callback = setup_highlights,
|
|
})
|
|
-- Setup autocmds
|
|
signature_help:setup_autocmds()
|
|
|
|
-- Store instance for potential reuse
|
|
M._initialized = true
|
|
M._instance = signature_help
|
|
|
|
-- Add API methods for external use
|
|
M.toggle_docs = function()
|
|
signature_help:toggle_docs()
|
|
end
|
|
|
|
return signature_help
|
|
end
|
|
|
|
-- Add version and metadata for lazy.nvim compatibility
|
|
M.version = "1.0.0"
|
|
M.dependencies = {
|
|
"nvim-treesitter/nvim-treesitter",
|
|
}
|
|
|
|
return M
|