lots of reworking. Seperate out signature help from documentation.

main
Bryan 2024-12-21 15:54:00 -06:00
parent 867ac5b1df
commit c291a7df66
2 changed files with 479 additions and 486 deletions

310
lua/signup/helper.lua Normal file
View File

@ -0,0 +1,310 @@
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
return {
anchor = anchor,
row = row,
col = 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,
}

View File

@ -1,4 +1,5 @@
local api = vim.api local api = vim.api
local helper = require("signup.helper")
local M = {} local M = {}
@ -7,11 +8,13 @@ SignatureHelp.__index = SignatureHelp
function SignatureHelp.new() function SignatureHelp.new()
local instance = setmetatable({ local instance = setmetatable({
win = nil, sig_win = nil,
buf = nil, sig_buf = nil,
dock_win = nil, doc_win = nil,
dock_buf = nil, doc_buf = nil,
dock_win_id = "signature_help_dock_" .. vim.api.nvim_get_current_buf(), showing = false,
ns = nil,
markid = nil,
timer = nil, timer = nil,
visible = false, visible = false,
current_signatures = nil, current_signatures = nil,
@ -39,13 +42,22 @@ function SignatureHelp.new()
bg = "#86e1fc", bg = "#86e1fc",
fg = "#1a1a1a", fg = "#1a1a1a",
}, },
border = "solid", hi_parameter = "LspSignatureActiveParameter",
winblend = 10, win_opts = {
auto_close = true, border = "rounded",
winblend = 10,
zindex = 200,
focusable = false,
max_height = 10,
max_width = 80,
anchor_bias = "below", -- below|above|auto
relative = "cursor",
offset_y = 5,
offset_x = 5,
},
trigger_chars = { "(", "," }, trigger_chars = { "(", "," },
max_height = 10, auto_close = true,
max_width = 40,
floating_window_above_cur_line = true,
preview_parameters = true, preview_parameters = true,
debounce_time = 100, debounce_time = 100,
dock_toggle_key = "<Leader>sd", dock_toggle_key = "<Leader>sd",
@ -66,167 +78,122 @@ function SignatureHelp.new()
return instance return instance
end end
local function signature_index_comment(index) -- local function signature_index_comment(index)
if #vim.bo.commentstring ~= 0 then -- if #vim.bo.commentstring ~= 0 then
return vim.bo.commentstring:format(index) -- return vim.bo.commentstring:format(index)
else -- else
return "(" .. index .. ")" -- return "(" .. index .. ")"
end -- end
-- end
-- local function markdown_for_signature_list(signatures, config)
-- local lines, labels = {}, {}
-- local number = config.number and #signatures > 1
-- local max_method_len = 0
--
-- -- First pass to calculate alignment
-- if config.render_style.align_icons then
-- for _, signature in ipairs(signatures) do
-- max_method_len = math.max(max_method_len, #signature.label)
-- end
-- end
--
-- for index, signature in ipairs(signatures) do
-- if not config.render_style.compact then
-- table.insert(lines, "")
-- end
-- table.insert(labels, #lines + 1)
--
-- local suffix = number and (" " .. signature_index_comment(index)) or ""
-- local padding = config.render_style.align_icons and string.rep(" ", max_method_len - #signature.label) or " "
--
-- -- Method signature with syntax highlighting
-- table.insert(lines, string.format("```%s", vim.bo.filetype))
-- -- table.insert(lines, string.format("%s Method:", config.icons.method))
-- table.insert(lines, string.format("%s %s%s%s", config.icons.method, signature.label, padding, suffix))
-- table.insert(lines, "```")
--
-- -- -- Parameters section
-- -- if signature.parameters and #signature.parameters > 0 then
-- -- if config.render_style.separator then
-- -- table.insert(lines, string.rep("─", 40))
-- -- end
-- -- table.insert(lines, string.format("%s Parameters:", config.icons.parameter))
-- -- for _, param in ipairs(signature.parameters) do
-- -- local param_doc = param.documentation and string.format(" - %s", vim.inspect(param.documentation))
-- -- table.insert(lines, string.format(" • %s = %s", vim.inspect(param.label), param_doc))
-- -- end
-- -- end
--
-- -- Documentation section
-- if signature.documentation then
-- if config.render_style.separator then
-- table.insert(lines, string.rep("─", 40))
-- end
-- table.insert(lines, string.format("%s Documentation:", config.icons.documentation))
-- local doc_lines = vim.split(signature.documentation.value or signature.documentation, "\n")
-- for _, line in ipairs(doc_lines) do
-- table.insert(lines, " " .. line)
-- end
-- end
--
-- if index ~= #signatures and config.render_style.separator then
-- table.insert(lines, string.rep("═", 40))
-- end
-- end
-- return lines, labels
-- end
--
-- function SignatureHelp:create_float_window(contents)
-- local max_width = math.min(self.config.max_width, vim.o.columns)
-- local max_height = math.min(self.config.max_height, #contents)
--
-- -- Calculate optimal position
-- local cursor = api.nvim_win_get_cursor(0)
-- local cursor_line = cursor[1]
-- local screen_line = vim.fn.screenpos(0, cursor_line, 1).row
--
-- local row_offset = self.config.floating_window_above_cur_line and -max_height - 1 or 1
-- if screen_line + row_offset < 1 then
-- row_offset = 2 -- Show below if not enough space above
-- end
--
-- local win_config = {
-- relative = "cursor",
-- row = row_offset - 1,
-- col = 0,
-- width = max_width,
-- height = max_height,
-- style = "minimal",
-- border = self.config.border,
-- zindex = 50, -- Ensure it's above most other floating windows
-- }
--
-- if self.win and api.nvim_win_is_valid(self.win) then
-- api.nvim_win_set_config(self.win, win_config)
-- api.nvim_win_set_buf(self.win, self.buf)
-- else
-- self.buf = api.nvim_create_buf(false, true)
-- self.win = api.nvim_open_win(self.buf, false, win_config)
-- end
--
-- vim.bo[self.buf].modifiable = true
-- api.nvim_buf_set_lines(self.buf, 0, -1, false, contents)
-- vim.bo[self.buf].modifiable = false
-- vim.wo[self.win][self.buf].foldenable = false
-- vim.wo[self.win][self.buf].wrap = true
-- vim.wo[self.win][self.buf].winblend = self.config.winblend
-- self.visible = true
-- end
--
function SignatureHelp:hide_sig_win()
helper.close_float_window(self.sig_win, self.sig_buf)
self.current_signatures = nil
self.showing = false
end end
local function markdown_for_signature_list(signatures, config) function SignatureHelp:hide_doc_win()
local lines, labels = {}, {} helper.close_float_window(self.doc_win, self.doc_buf)
local number = config.number and #signatures > 1
local max_method_len = 0
-- First pass to calculate alignment
if config.render_style.align_icons then
for _, signature in ipairs(signatures) do
max_method_len = math.max(max_method_len, #signature.label)
end
end
for index, signature in ipairs(signatures) do
if not config.render_style.compact then
table.insert(lines, "")
end
table.insert(labels, #lines + 1)
local suffix = number and (" " .. signature_index_comment(index)) or ""
local padding = config.render_style.align_icons and string.rep(" ", max_method_len - #signature.label) or " "
-- Method signature with syntax highlighting
table.insert(lines, string.format("```%s", vim.bo.filetype))
-- table.insert(lines, string.format("%s Method:", config.icons.method))
table.insert(lines, string.format("%s %s%s%s", config.icons.method, signature.label, padding, suffix))
table.insert(lines, "```")
-- -- Parameters section
-- if signature.parameters and #signature.parameters > 0 then
-- if config.render_style.separator then
-- table.insert(lines, string.rep("─", 40))
-- end
-- table.insert(lines, string.format("%s Parameters:", config.icons.parameter))
-- for _, param in ipairs(signature.parameters) do
-- local param_doc = param.documentation and string.format(" - %s", vim.inspect(param.documentation))
-- table.insert(lines, string.format(" • %s = %s", vim.inspect(param.label), param_doc))
-- end
-- end
-- Documentation section
if signature.documentation then
if config.render_style.separator then
table.insert(lines, string.rep("", 40))
end
table.insert(lines, string.format("%s Documentation:", config.icons.documentation))
local doc_lines = vim.split(signature.documentation.value or signature.documentation, "\n")
for _, line in ipairs(doc_lines) do
table.insert(lines, " " .. line)
end
end
if index ~= #signatures and config.render_style.separator then
table.insert(lines, string.rep("", 40))
end
end
return lines, labels
end
function SignatureHelp:create_float_window(contents)
local max_width = math.min(self.config.max_width, vim.o.columns)
local max_height = math.min(self.config.max_height, #contents)
-- Calculate optimal position
local cursor = api.nvim_win_get_cursor(0)
local cursor_line = cursor[1]
local screen_line = vim.fn.screenpos(0, cursor_line, 1).row
local row_offset = self.config.floating_window_above_cur_line and -max_height - 1 or 1
if screen_line + row_offset < 1 then
row_offset = 2 -- Show below if not enough space above
end
local win_config = {
relative = "cursor",
row = row_offset - 1,
col = 0,
width = max_width,
height = max_height,
style = "minimal",
border = self.config.border,
zindex = 50, -- Ensure it's above most other floating windows
}
if self.win and api.nvim_win_is_valid(self.win) then
api.nvim_win_set_config(self.win, win_config)
api.nvim_win_set_buf(self.win, self.buf)
else
self.buf = api.nvim_create_buf(false, true)
self.win = api.nvim_open_win(self.buf, false, win_config)
end
vim.bo[self.buf].modifiable = true
api.nvim_buf_set_lines(self.buf, 0, -1, false, contents)
vim.bo[self.buf].modifiable = false
vim.wo[self.win][self.buf].foldenable = false
vim.wo[self.win][self.buf].wrap = true
vim.wo[self.win][self.buf].winblend = self.config.winblend
self.visible = true
end
function SignatureHelp:hide()
if self.visible then
-- Store current window and buffer
local current_win = api.nvim_get_current_win()
local current_buf = api.nvim_get_current_buf()
-- Close appropriate window based on mode
if self.config.dock_mode.enabled then
self:close_dock_window()
else
if self.win and api.nvim_win_is_valid(self.win) then
pcall(api.nvim_win_close, self.win, true)
end
if self.buf and api.nvim_buf_is_valid(self.buf) then
pcall(api.nvim_buf_delete, self.buf, { force = true })
end
self.win = nil
self.buf = nil
end
self.current_signatures = nil
self.visible = false
-- Restore focus
pcall(api.nvim_set_current_win, current_win)
pcall(api.nvim_set_current_buf, current_buf)
end
end
function SignatureHelp:find_parameter_range(signature_str, parameter_label)
-- Handle both string and table parameter labels
if type(parameter_label) == "table" then
return parameter_label[1], parameter_label[2]
end
-- Escape special pattern characters in parameter_label
local escaped_label = vim.pesc(parameter_label)
-- Look for the parameter with word boundaries
local pattern = [[\<]] .. escaped_label .. [[\>]]
local start_pos, end_pos = signature_str:find(pattern)
if not start_pos then
-- Fallback: try finding exact match if word boundary search fails
start_pos, end_pos = signature_str:find(escaped_label)
end
if not start_pos then
return nil, nil
end
-- local end_pos = start_pos + #parameter_label
return start_pos, end_pos
end end
function SignatureHelp:extract_default_value(param_info) function SignatureHelp:extract_default_value(param_info)
@ -255,166 +222,61 @@ function SignatureHelp:extract_default_value(param_info)
return nil return nil
end end
function SignatureHelp:set_active_parameter_highlights(active_parameter, signatures, labels) function SignatureHelp:set_active_parameter_highlights(param_str, lines)
if not self.buf or not api.nvim_buf_is_valid(self.buf) then if not self.sig_buf or not api.nvim_buf_is_valid(self.sig_buf) then
return return
end end
-- Clear existing highlights self.ns = api.nvim_create_namespace("lsp_signature_hi_parameter")
api.nvim_buf_clear_namespace(self.buf, -1, 0, -1) local hi = self.config.hi_parameter
-- Iterate over signatures to highlight the active parameter local s, e, line_num
for index, signature in ipairs(signatures) do for l, line in ipairs(lines) do
local parameter = signature.activeParameter or active_parameter local ss, ee, _ = string.find(line, param_str, 1, true)
if parameter and parameter >= 0 and parameter < #signature.parameters then if ss ~= nil then
local label = signature.parameters[parameter + 1].label line_num = l - 1
if type(label) == "string" then s = ss
-- Parse the signature string to find the exact range of the active parameter e = ee
local signature_str = signature.label
local start_pos, end_pos = self:find_parameter_range(signature_str, label)
if start_pos and end_pos then
api.nvim_buf_add_highlight(
self.buf,
-1,
"LspSignatureActiveParameter",
labels[index],
start_pos,
end_pos
)
end
elseif type(label) == "table" then
local start_pos, end_pos = unpack(label)
api.nvim_buf_add_highlight(
self.buf,
-1,
"LspSignatureActiveParameter",
labels[index],
start_pos + 6,
end_pos + 6
)
end
end end
end end
-- Add icon highlights if s and e and s > 0 then
local icon_highlights = { self.markid = api.nvim_buf_set_extmark(
{ self.config.icons.method, "SignatureHelpMethod" }, self.sig_buf,
{ self.config.icons.parameter, "SignatureHelpParameter" }, self.ns,
{ self.config.icons.documentation, "SignatureHelpDocumentation" }, line_num,
} s,
{ id = self.markid, end_line = line_num, end_col = e, hl_group = hi, strict = false }
for _, icon_hl in ipairs(icon_highlights) do )
local icon, hl_group = unpack(icon_hl)
local line_num = 0
while line_num < api.nvim_buf_line_count(self.buf) do
local line = api.nvim_buf_get_lines(self.buf, line_num, line_num + 1, false)[1]
local start_col = line:find(vim.pesc(icon))
if start_col then
api.nvim_buf_add_highlight(self.buf, -1, hl_group, line_num, start_col - 1, start_col + #icon - 1)
end
line_num = line_num + 1
end
end
end
function SignatureHelp:highlight_icons()
local icon_highlights = {
{ self.config.icons.method, "SignatureHelpMethod" },
{ self.config.icons.parameter, "SignatureHelpParameter" },
{ self.config.icons.documentation, "SignatureHelpDocumentation" },
}
for _, icon_hl in ipairs(icon_highlights) do
local icon, hl_group = unpack(icon_hl)
local line_num = 0
while line_num < api.nvim_buf_line_count(self.buf) do
local line = api.nvim_buf_get_lines(self.buf, line_num, line_num + 1, false)[1]
local start_col = line:find(vim.pesc(icon))
if start_col then
api.nvim_buf_add_highlight(self.buf, -1, hl_group, line_num, start_col - 1, start_col - 1 + #icon)
end
line_num = line_num + 1
end
end end
end end
function SignatureHelp:display(result) function SignatureHelp:display(result)
if not result or not result.signatures or #result.signatures == 0 then if not result or not result.signatures or #result.signatures == 0 then
self:hide() self:hide_sig_win()
return return
end end
-- Store current window and buffer
local current_win = api.nvim_get_current_win()
local current_buf = api.nvim_get_current_buf()
-- Prevent duplicate displays of identical content -- Prevent duplicate displays of identical content
if if
self.current_signatures self.current_signatures and vim.deep_equal(result.signatures, self.current_signatures)
and vim.deep_equal(result.signatures, self.current_signatures) -- and result.activeParameter == self.current_active_parameter
and result.activeParameter == self.current_active_parameter
then then
return return
end end
local content = helper.convert_signature_help_to_lines(result, self.config.win_opts.max_width)
local markdown, labels = markdown_for_signature_list(result.signatures, self.config) if not self.showing then
self.current_signatures = result.signatures self.sig_buf, self.sig_win = helper.create_float_window(content, "lua", self.config.win_opts) -- TODO: programmatically set file type:
self.current_active_parameter = result.activeParameter self.showing = true
if #markdown > 0 then
if self.config.dock_mode.enabled then
local win, buf = self:create_dock_window()
if win and buf then
api.nvim_buf_set_option(buf, "modifiable", true)
api.nvim_buf_set_lines(buf, 0, -1, false, markdown)
api.nvim_buf_set_option(buf, "modifiable", false)
self:set_active_parameter_highlights(result.activeParameter, result.signatures, labels)
self:apply_treesitter_highlighting()
end
else
self:create_float_window(markdown)
api.nvim_buf_set_option(self.buf, "filetype", "markdown")
self:set_active_parameter_highlights(result.activeParameter, result.signatures, labels)
self:apply_treesitter_highlighting()
end
else
self:hide()
end end
local param_str = helper.get_parameter_label(result)
-- Restore focus to original window and buffer self:set_active_parameter_highlights(param_str, content)
api.nvim_set_current_win(current_win)
api.nvim_set_current_buf(current_buf)
end
function SignatureHelp:apply_treesitter_highlighting()
local buf = self.config.dock_mode.enabled and self.dock_buf or self.buf
if not buf or 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, "markdown")
end)
-- Restore focus
api.nvim_set_current_win(current_win)
api.nvim_set_current_buf(current_buf)
end end
function SignatureHelp:trigger() function SignatureHelp:trigger()
if not self.enabled then if not self.enabled then
return return
end end
vim.notify("trigger")
local params = vim.lsp.util.make_position_params() local params = vim.lsp.util.make_position_params()
vim.lsp.buf_request(0, "textDocument/signatureHelp", params, function(err, result, _, _) vim.lsp.buf_request(0, "textDocument/signatureHelp", params, function(err, result, _, _)
@ -422,14 +284,14 @@ function SignatureHelp:trigger()
if not self.config.silent then if not self.config.silent then
vim.notify("Error in LSP Signature Help: " .. vim.inspect(err), vim.log.levels.ERROR) vim.notify("Error in LSP Signature Help: " .. vim.inspect(err), vim.log.levels.ERROR)
end end
self:hide() self:hide_sig_win()
return return
end end
if result and result.signatures and #result.signatures > 0 then if result and result.signatures and #result.signatures > 0 then
self:display(result) self:display(result)
else else
self:hide() self:hide_sig_win()
-- Only notify if not silent and if there was actually no signature help -- Only notify if not silent and if there was actually no signature help
if not self.config.silent and result then if not self.config.silent and result then
vim.notify("No signature help available", vim.log.levels.INFO) vim.notify("No signature help available", vim.log.levels.INFO)
@ -454,7 +316,7 @@ function SignatureHelp:toggle_normal_mode()
if self.normal_mode_active then if self.normal_mode_active then
self:trigger() self:trigger()
else else
self:hide() self:hide_sig_win()
end end
end end
@ -501,7 +363,7 @@ function SignatureHelp:setup_autocmds()
api.nvim_create_autocmd({ "InsertLeave" }, { api.nvim_create_autocmd({ "InsertLeave" }, {
group = group, group = group,
callback = function() callback = function()
self:hide() self:hide_sig_win()
self.normal_mode_active = false self.normal_mode_active = false
end, end,
}) })
@ -514,137 +376,6 @@ function SignatureHelp:setup_autocmds()
end, 100) end, 100)
end, end,
}) })
api.nvim_create_autocmd("ColorScheme", {
group = group,
callback = function()
if self.visible then
self:apply_treesitter_highlighting()
self:set_active_parameter_highlights(
self.current_signatures.activeParameter,
self.current_signatures,
{}
)
end
end,
})
end
function SignatureHelp:create_dock_window()
-- Store current window and buffer
local current_win = api.nvim_get_current_win()
local current_buf = api.nvim_get_current_buf()
-- Update dock window ID for current buffer
self.dock_win_id = "signature_help_dock_" .. current_buf
if not self.dock_win or not api.nvim_win_is_valid(self.dock_win) then
-- Create dock buffer if needed
if not self.dock_buf or not api.nvim_buf_is_valid(self.dock_buf) then
self.dock_buf = api.nvim_create_buf(false, true)
api.nvim_buf_set_option(self.dock_buf, "buftype", "nofile")
api.nvim_buf_set_option(self.dock_buf, "bufhidden", "hide")
api.nvim_buf_set_option(self.dock_buf, "modifiable", false)
api.nvim_buf_set_option(self.dock_buf, "filetype", "markdown")
-- Set buffer name with ID for easier tracking
api.nvim_buf_set_name(self.dock_buf, self.dock_win_id)
end
-- Calculate dock position and dimensions
local win_height = api.nvim_win_get_height(current_win)
local win_width = api.nvim_win_get_width(current_win)
local dock_height = math.min(self.config.dock_mode.height, math.floor(win_height * 0.3))
local padding = self.config.dock_mode.padding
local dock_width = win_width - (padding * 2)
local row = self.config.dock_mode.position == "bottom" and win_height - dock_height - padding or padding
-- Create dock window with enhanced config
self.dock_win = api.nvim_open_win(self.dock_buf, false, {
relative = "win",
win = current_win,
width = dock_width,
height = dock_height,
row = row,
col = padding,
style = "minimal",
border = self.config.border,
zindex = 45,
focusable = false, -- Make window non-focusable to prevent focus issues
})
-- Apply window options
local win_opts = {
wrap = true,
winblend = self.config.winblend,
foldenable = false,
cursorline = false,
winhighlight = "Normal:SignatureHelpDock,FloatBorder:SignatureHelpBorder",
signcolumn = "no",
}
for opt, value in pairs(win_opts) do
api.nvim_win_set_option(self.dock_win, opt, value)
end
-- Set up dock window keymaps
local dock_buf_keymaps = {
["q"] = function()
self:hide()
end,
["<Esc>"] = function()
self:hide()
end,
["<C-c>"] = function()
self:hide()
end,
["<C-n>"] = function()
self:next_signature()
end,
["<C-p>"] = function()
self:prev_signature()
end,
}
for key, func in pairs(dock_buf_keymaps) do
vim.keymap.set("n", key, func, { buffer = self.dock_buf, silent = true, nowait = true })
end
-- Set window ID as a window variable
api.nvim_win_set_var(self.dock_win, "signature_help_id", self.dock_win_id)
end
-- Ensure focus returns to original window
api.nvim_set_current_win(current_win)
return self.dock_win, self.dock_buf
end
function SignatureHelp:close_dock_window()
-- Fast check for existing dock window
if not self.dock_win_id then
return
end
-- Try to find window by ID
local wins = api.nvim_list_wins()
for _, win in ipairs(wins) do
local ok, win_id = pcall(api.nvim_win_get_var, win, "signature_help_id")
if ok and win_id == self.dock_win_id then
pcall(api.nvim_win_close, win, true)
break
end
end
-- Clean up buffer
if self.dock_buf and api.nvim_buf_is_valid(self.dock_buf) then
pcall(api.nvim_buf_delete, self.dock_buf, { force = true })
end
-- Reset dock window state
self.dock_win = nil
self.dock_buf = nil
end end
-- Add navigation between multiple signatures -- Add navigation between multiple signatures
@ -678,43 +409,6 @@ function SignatureHelp:prev_signature()
}) })
end end
function SignatureHelp:toggle_dock_mode()
-- Store current window and buffer
local current_win = api.nvim_get_current_win()
local current_buf = api.nvim_get_current_buf()
-- Store current signatures
local current_sigs = self.current_signatures
local current_active = self.current_active_parameter
-- Close existing windows efficiently
if self.config.dock_mode.enabled then
self:close_dock_window()
else
if self.win and api.nvim_win_is_valid(self.win) then
pcall(api.nvim_win_close, self.win, true)
pcall(api.nvim_buf_delete, self.buf, { force = true })
self.win = nil
self.buf = nil
end
end
-- Toggle mode
self.config.dock_mode.enabled = not self.config.dock_mode.enabled
-- Redisplay if we had signatures
if current_sigs then
self:display({
signatures = current_sigs,
activeParameter = current_active,
})
end
-- Restore focus
pcall(api.nvim_set_current_win, current_win)
pcall(api.nvim_set_current_buf, current_buf)
end
function SignatureHelp:setup_keymaps() function SignatureHelp:setup_keymaps()
-- Setup toggle keys using the actual config -- Setup toggle keys using the actual config
local toggle_key = self.config.toggle_key local toggle_key = self.config.toggle_key
@ -791,16 +485,5 @@ M.dependencies = {
} }
-- Add API methods for external use -- Add API methods for external use
M.toggle_dock = function()
if M._instance then
M._instance:toggle_dock_mode()
end
end
M.toggle_normal_mode = function()
if M._instance then
M._instance:toggle_normal_mode()
end
end
return M return M