From c291a7df6659b747c03c9ff41c6910f8c01be9d0 Mon Sep 17 00:00:00 2001 From: Bryan Date: Sat, 21 Dec 2024 15:54:00 -0600 Subject: [PATCH] lots of reworking. Seperate out signature help from documentation. --- lua/signup/helper.lua | 310 ++++++++++++++++++++ lua/signup/init.lua | 655 +++++++++++------------------------------- 2 files changed, 479 insertions(+), 486 deletions(-) create mode 100644 lua/signup/helper.lua diff --git a/lua/signup/helper.lua b/lua/signup/helper.lua new file mode 100644 index 0000000..a6339fc --- /dev/null +++ b/lua/signup/helper.lua @@ -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", + "bdelete", + { 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, +} diff --git a/lua/signup/init.lua b/lua/signup/init.lua index 90874a2..c61d82b 100644 --- a/lua/signup/init.lua +++ b/lua/signup/init.lua @@ -1,4 +1,5 @@ local api = vim.api +local helper = require("signup.helper") local M = {} @@ -7,11 +8,13 @@ SignatureHelp.__index = SignatureHelp function SignatureHelp.new() local instance = setmetatable({ - win = nil, - buf = nil, - dock_win = nil, - dock_buf = nil, - dock_win_id = "signature_help_dock_" .. vim.api.nvim_get_current_buf(), + sig_win = nil, + sig_buf = nil, + doc_win = nil, + doc_buf = nil, + showing = false, + ns = nil, + markid = nil, timer = nil, visible = false, current_signatures = nil, @@ -39,13 +42,22 @@ function SignatureHelp.new() bg = "#86e1fc", fg = "#1a1a1a", }, - border = "solid", - winblend = 10, - auto_close = true, + hi_parameter = "LspSignatureActiveParameter", + win_opts = { + 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 = { "(", "," }, - max_height = 10, - max_width = 40, - floating_window_above_cur_line = true, + auto_close = true, preview_parameters = true, debounce_time = 100, dock_toggle_key = "sd", @@ -66,167 +78,122 @@ function SignatureHelp.new() return instance end -local function signature_index_comment(index) - if #vim.bo.commentstring ~= 0 then - return vim.bo.commentstring:format(index) - else - return "(" .. index .. ")" - end +-- local function signature_index_comment(index) +-- if #vim.bo.commentstring ~= 0 then +-- return vim.bo.commentstring:format(index) +-- else +-- return "(" .. index .. ")" +-- 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 -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() - 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 +function SignatureHelp:hide_doc_win() + helper.close_float_window(self.doc_win, self.doc_buf) end function SignatureHelp:extract_default_value(param_info) @@ -255,166 +222,61 @@ function SignatureHelp:extract_default_value(param_info) return nil end -function SignatureHelp:set_active_parameter_highlights(active_parameter, signatures, labels) - if not self.buf or not api.nvim_buf_is_valid(self.buf) then +function SignatureHelp:set_active_parameter_highlights(param_str, lines) + if not self.sig_buf or not api.nvim_buf_is_valid(self.sig_buf) then return end - -- Clear existing highlights - api.nvim_buf_clear_namespace(self.buf, -1, 0, -1) + self.ns = api.nvim_create_namespace("lsp_signature_hi_parameter") + local hi = self.config.hi_parameter - -- Iterate over signatures to highlight the active parameter - for index, signature in ipairs(signatures) do - local parameter = signature.activeParameter or active_parameter - if parameter and parameter >= 0 and parameter < #signature.parameters then - local label = signature.parameters[parameter + 1].label - if type(label) == "string" then - -- Parse the signature string to find the exact range of the active parameter - 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 + 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 - -- Add icon highlights - 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 + #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 + if s and e and s > 0 then + self.markid = api.nvim_buf_set_extmark( + self.sig_buf, + 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(result) if not result or not result.signatures or #result.signatures == 0 then - self:hide() + self:hide_sig_win() return 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 if - self.current_signatures - and vim.deep_equal(result.signatures, self.current_signatures) - and result.activeParameter == self.current_active_parameter + self.current_signatures and vim.deep_equal(result.signatures, self.current_signatures) + -- and result.activeParameter == self.current_active_parameter then return end - - local markdown, labels = markdown_for_signature_list(result.signatures, self.config) - self.current_signatures = result.signatures - self.current_active_parameter = result.activeParameter - - 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() + local content = helper.convert_signature_help_to_lines(result, self.config.win_opts.max_width) + if not self.showing then + self.sig_buf, self.sig_win = helper.create_float_window(content, "lua", self.config.win_opts) -- TODO: programmatically set file type: + self.showing = true end - - -- Restore focus to original window and buffer - 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) + local param_str = helper.get_parameter_label(result) + self:set_active_parameter_highlights(param_str, content) end function SignatureHelp:trigger() if not self.enabled then return end - vim.notify("trigger") local params = vim.lsp.util.make_position_params() vim.lsp.buf_request(0, "textDocument/signatureHelp", params, function(err, result, _, _) @@ -422,14 +284,14 @@ function SignatureHelp:trigger() if not self.config.silent then vim.notify("Error in LSP Signature Help: " .. vim.inspect(err), vim.log.levels.ERROR) end - self:hide() + self:hide_sig_win() return end if result and result.signatures and #result.signatures > 0 then self:display(result) else - self:hide() + self:hide_sig_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) @@ -454,7 +316,7 @@ function SignatureHelp:toggle_normal_mode() if self.normal_mode_active then self:trigger() else - self:hide() + self:hide_sig_win() end end @@ -501,7 +363,7 @@ function SignatureHelp:setup_autocmds() api.nvim_create_autocmd({ "InsertLeave" }, { group = group, callback = function() - self:hide() + self:hide_sig_win() self.normal_mode_active = false end, }) @@ -514,137 +376,6 @@ function SignatureHelp:setup_autocmds() end, 100) 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, - [""] = function() - self:hide() - end, - [""] = function() - self:hide() - end, - [""] = function() - self:next_signature() - end, - [""] = 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 -- Add navigation between multiple signatures @@ -678,43 +409,6 @@ function SignatureHelp:prev_signature() }) 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() -- Setup toggle keys using the actual config local toggle_key = self.config.toggle_key @@ -791,16 +485,5 @@ M.dependencies = { } -- 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