(fixed) bugs, (feat) new dock mode, more optimized, please read the Readme to set opts well.

main
Dan7h3x 2024-11-27 18:00:29 +03:30
parent 06722cbff6
commit 2b21a2aa51
2 changed files with 566 additions and 241 deletions

136
README.md
View File

@ -6,13 +6,18 @@ A little (smart maybe) lsp signature helper for neovim.
# Neovim Signature Help Plugin # Neovim Signature Help Plugin
This Neovim plugin provides a signature help feature for LSP (Language Server Protocol) clients. It displays function signatures and parameter information in a floating window as you type in insert mode or move the cursor in normal mode. The plugin also includes a notification system to display messages with different levels of severity (info, warning, error). This Neovim plugin provides a signature help feature for LSP (Language Server Protocol) clients. I can't tell much, just watch the showcases.
# ScreenShots (WIP) # ScreenShots (WIP)
![Screenshot 1](https://github.com/user-attachments/assets/1ba206b1-cde8-41f4-9abd-f8501c6b16e1) ![signup_1](https://github.com/user-attachments/assets/e9319dcf-1d9d-4567-a500-a24d38933cb6)
![Screenshot 2](https://github.com/user-attachments/assets/aba63fe7-a302-4c9d-9f4a-ce28c9c35c03) ![signup_2](https://github.com/user-attachments/assets/192b0809-3e66-42bf-9e6e-c1eae744f7b8)
![Screenshot 3](https://github.com/user-attachments/assets/986f0bcb-aecf-4483-8210-83b93cfd72d2) ![signup_3](https://github.com/user-attachments/assets/ca43b7a0-63fa-469c-8db0-df7a49dab483)
![signup_def](https://github.com/user-attachments/assets/6c4d7e09-5baa-418f-a086-e60b4eb4b501)
We have `dock` mode but its under dev for now, please take low expectations:
![signup_dock](https://github.com/user-attachments/assets/40455737-a952-4a3f-ae1f-fadd7ad68ea2)
## Features ## Features
@ -26,17 +31,18 @@ This Neovim plugin provides a signature help feature for LSP (Language Server Pr
### Using [lazy.nvim](https://github.com/folke/lazy.nvim) ### Using [lazy.nvim](https://github.com/folke/lazy.nvim)
Add the following to your `init.lua`: Add the following to your `init.lua` and use `main` branch always:
```lua ```lua
require("lazy").setup({ require("lazy").setup({
{ {
"Dan7h3x/signup.nvim", "Dan7h3x/signup.nvim",
branch = "main", branch = "main",
config = function() opts = {
require("signup").setup({
-- Your configuration options here -- Your configuration options here
}) },
config = function(_,opts)
require("signup").setup(opts)
end end
} }
}) })
@ -62,30 +68,23 @@ EOF
## Configuration ## Configuration
The plugin comes with a default configuration, but you can customize it according to your preferences. Here are the available options: The plugin comes with a default configuration, but you can customize it
according to your preferences. Here are the available options:
```lua ```lua
require('signup').setup( opts = {
{
win = nil,
buf = nil,
timer = nil,
visible = false,
current_signatures = nil,
enabled = false,
normal_mode_active = false,
config = {
silent = false, silent = false,
number = true, number = true,
icons = { icons = {
parameter = "", parameter = "",
method = " ", method = "󰡱",
documentation = " ", documentation = "󱪙",
}, },
colors = { colors = {
parameter = "#86e1fc", parameter = "#86e1fc",
method = "#c099ff", method = "#c099ff",
documentation = "#4fd6be", documentation = "#4fd6be",
default_value = "#a80888",
}, },
active_parameter_colors = { active_parameter_colors = {
bg = "#86e1fc", bg = "#86e1fc",
@ -93,78 +92,27 @@ require('signup').setup(
}, },
border = "solid", border = "solid",
winblend = 10, winblend = 10,
} auto_close = true,
} trigger_chars = { "(", "," },
) max_height = 10,
``` max_width = 40,
floating_window_above_cur_line = true,
### Options preview_parameters = true,
debounce_time = 30,
- **silent**: If `true`, suppresses notifications. Default is `false`. dock_toggle_key = "<Leader>sd",
- **number**: If `true`, displays the signature index. Default is `true`. toggle_key = "<C-k>",
- **icons**: Custom icons for method, parameter, and documentation. dock_mode = {
- **colors**: Custom colors for method, parameter, and documentation. enabled = false,
- **border**: Border style for the floating window. Default is `"rounded"`. position = "bottom",
- **winblend**: Transparency level for the floating window. Default is `10`. height = 3,
- **override**: If `true`, overrides the default LSP handler for `textDocument/signatureHelp`. Default is `true`. padding = 1,
## Usage
### Toggle Signature Help in Normal Mode
You can toggle the signature help in normal mode using the default keybinding `<C-k>`. You can customize this keybinding in the setup function:
```lua
require('signup').setup({
toggle_key = "<C-k>", -- Customize the toggle key here
})
```
### Trigger Signature Help in Insert Mode
The signature help is automatically triggered when you move the cursor or change text in insert mode.
### Notifications
The plugin includes a notification system to display messages with different levels of severity (info, warning, error). These notifications are displayed in a floating window and automatically disappear after a few seconds.
## Highlight Groups
The plugin defines the following highlight groups:
- **LspSignatureActiveParameter**: Highlight for the active parameter.
- **SignatureHelpMethod**: Highlight for method icons.
- **SignatureHelpParameter**: Highlight for parameter icons.
- **SignatureHelpDocumentation**: Highlight for documentation icons.
- **NotificationInfo**: Highlight for info notifications.
- **NotificationWarn**: Highlight for warning notifications.
- **NotificationError**: Highlight for error notifications.
## Examples
### Customizing Icons and Colors
```lua
require('signup').setup({
icons = {
parameter = " ",
method = " ",
documentation = " ",
}, },
colors = { render_style = {
parameter = "#ffa500", separator = true,
method = "#8a2be2", compact = true,
documentation = "#008000", align_icons = true,
}, },
}) }
```
### Disabling Notifications
```lua
require('signup').setup({
silent = true,
})
``` ```
## Contributing ## Contributing
@ -176,3 +124,7 @@ Contributions are welcome! Please feel free to submit a pull request or open an
This plugin is licensed under the MIT License. See the [LICENSE](LICENSE) file for more details. This plugin is licensed under the MIT License. See the [LICENSE](LICENSE) file for more details.
--- ---
```
```

View File

@ -6,26 +6,34 @@ local SignatureHelp = {}
SignatureHelp.__index = SignatureHelp SignatureHelp.__index = SignatureHelp
function SignatureHelp.new() function SignatureHelp.new()
return setmetatable({ local instance = setmetatable({
win = nil, win = nil,
buf = nil, buf = nil,
dock_win = nil,
dock_buf = nil,
dock_win_id = "signature_help_dock_" .. vim.api.nvim_get_current_buf(),
timer = nil, timer = nil,
visible = false, visible = false,
current_signatures = nil, current_signatures = nil,
enabled = false, enabled = false,
normal_mode_active = false, normal_mode_active = false,
config = { current_signature_idx = nil,
config = nil,
}, SignatureHelp)
instance._default_config = {
silent = false, silent = false,
number = true, number = true,
icons = { icons = {
parameter = "", parameter = "",
method = "", method = "󰡱",
documentation = "", documentation = "󱪙",
}, },
colors = { colors = {
parameter = "#86e1fc", parameter = "#86e1fc",
method = "#c099ff", method = "#c099ff",
documentation = "#4fd6be", documentation = "#4fd6be",
default_value = "#a80888",
}, },
active_parameter_colors = { active_parameter_colors = {
bg = "#86e1fc", bg = "#86e1fc",
@ -33,8 +41,29 @@ function SignatureHelp.new()
}, },
border = "solid", border = "solid",
winblend = 10, winblend = 10,
auto_close = true,
trigger_chars = { "(", "," },
max_height = 10,
max_width = 40,
floating_window_above_cur_line = true,
preview_parameters = true,
debounce_time = 30,
dock_toggle_key = "<Leader>sd",
toggle_key = "<C-k>",
dock_mode = {
enabled = false,
position = "bottom",
height = 3,
padding = 1,
},
render_style = {
separator = true,
compact = true,
align_icons = true,
},
} }
}, SignatureHelp)
return instance
end end
local function signature_index_comment(index) local function signature_index_comment(index)
@ -48,51 +77,95 @@ end
local function markdown_for_signature_list(signatures, config) local function markdown_for_signature_list(signatures, config)
local lines, labels = {}, {} local lines, labels = {}, {}
local number = config.number and #signatures > 1 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 for index, signature in ipairs(signatures) do
if not config.render_style.compact then
table.insert(lines, "")
end
table.insert(labels, #lines + 1) table.insert(labels, #lines + 1)
local suffix = number and (' ' .. signature_index_comment(index)) or '' 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", vim.bo.filetype))
table.insert(lines, string.format("%s %s%s", config.icons.method, signature.label, suffix)) -- 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, "```") table.insert(lines, "```")
-- Parameters section
-- if signature.parameters and #signature.parameters > 0 then -- if signature.parameters and #signature.parameters > 0 then
-- table.insert(lines, "") -- if config.render_style.separator then
-- table.insert(lines, string.rep("─", 40))
-- end
-- table.insert(lines, string.format("%s Parameters:", config.icons.parameter)) -- table.insert(lines, string.format("%s Parameters:", config.icons.parameter))
-- for _, param in ipairs(signature.parameters) do -- for _, param in ipairs(signature.parameters) do
-- table.insert(lines, string.format(" • %s", param.label)) -- local param_doc = param.documentation and
-- string.format(" - %s", param.documentation.value or param.documentation) or ""
-- table.insert(lines, string.format(" • %s = %s", param.label, param_doc))
-- end -- end
-- end -- end
-- Documentation section
if signature.documentation then if signature.documentation then
table.insert(lines, "") if config.render_style.separator then
table.insert(lines, string.rep("-", 40))
end
table.insert(lines, string.format("%s Documentation:", config.icons.documentation)) table.insert(lines, string.format("%s Documentation:", config.icons.documentation))
vim.list_extend(lines, vim.split(signature.documentation.value or signature.documentation, "\n")) 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 end
if index ~= #signatures then if index ~= #signatures and config.render_style.separator then
table.insert(lines, "---") table.insert(lines, string.rep("", 40))
end end
end end
return lines, labels return lines, labels
end end
function SignatureHelp:create_float_window(contents) function SignatureHelp:create_float_window(contents)
local width = math.min(45, vim.o.columns) local max_width = math.min(self.config.max_width, vim.o.columns)
local height = math.min(#contents, 10) local max_height = math.min(self.config.max_height, #contents)
-- Calculate optimal position
local cursor = api.nvim_win_get_cursor(0) local cursor = api.nvim_win_get_cursor(0)
local row = cursor[1] - api.nvim_win_get_cursor(0)[1] 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 = { local win_config = {
relative = "cursor", relative = "cursor",
row = row + 1, row = row_offset - 1,
col = 0, col = 0,
width = width, width = max_width,
height = height, height = max_height,
style = "minimal", style = "minimal",
border = self.config.border, 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 if self.win and api.nvim_win_is_valid(self.win) then
@ -115,60 +188,86 @@ end
function SignatureHelp:hide() function SignatureHelp:hide()
if self.visible then 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) 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 }) pcall(api.nvim_buf_delete, self.buf, { force = true })
end
self.win = nil self.win = nil
self.buf = nil self.buf = nil
end
self.visible = false self.visible = false
self.current_signatures = nil
-- Restore focus
pcall(api.nvim_set_current_win, current_win)
pcall(api.nvim_set_current_buf, current_buf)
end end
end end
-- function SignatureHelp:set_active_parameter_highlights(active_parameter, signatures, labels) function SignatureHelp:find_parameter_range(signature_str, parameter_label)
-- if not self.buf or not api.nvim_buf_is_valid(self.buf) then return end -- Handle both string and table parameter labels
-- if type(parameter_label) == "table" then
-- api.nvim_buf_clear_namespace(self.buf, -1, 0, -1) return parameter_label[1], parameter_label[2]
-- end
-- for index, signature in ipairs(signatures) do
-- local parameter = signature.activeParameter or active_parameter -- Escape special pattern characters in parameter_label
-- if parameter and parameter >= 0 and parameter < #signature.parameters then local escaped_label = vim.pesc(parameter_label)
-- local label = signature.parameters[parameter + 1].label
-- if type(label) == "string" then -- Look for the parameter with word boundaries
-- -- vim.fn.matchadd("LspSignatureActiveParameter", "\\<" .. label .. "\\>") local pattern = [[\b]] .. escaped_label .. [[\b]]
-- vim.fn.matchadd("LspSignatureActiveParameter", "\\V\\<" .. vim.fn.escape(label, '\\') .. "\\>") local start_pos = signature_str:find(pattern)
-- elseif type(label) == "table" then
-- -- api.nvim_buf_add_highlight(self.buf, -1, "LspSignatureActiveParameter", labels[index], unpack(label)) if not start_pos then
-- assert(self.buf and labels[index] and label[1] and label[2], "Invalid arguments") -- Fallback: try finding exact match if word boundary search fails
-- api.nvim_buf_add_highlight(self.buf, -1, "LspSignatureActiveParameter", labels[index], unpack(label)) start_pos = signature_str:find(escaped_label)
-- end end
-- end
-- end if not start_pos then return nil, nil end
--
-- -- Add icon highlights local end_pos = start_pos + #parameter_label - 1
-- local icon_highlights = { return start_pos, end_pos
-- { self.config.icons.method, "SignatureHelpMethod" }, end
-- { self.config.icons.parameter, "SignatureHelpParameter" },
-- { self.config.icons.documentation, "SignatureHelpDocumentation" }, 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
-- for _, icon_hl in ipairs(icon_highlights) do
-- local icon, hl_group = unpack(icon_hl) local doc = type(param_info.documentation) == "string"
-- local line_num = 0 and param_info.documentation
-- while line_num < api.nvim_buf_line_count(self.buf) do or param_info.documentation.value
-- local line = api.nvim_buf_get_lines(self.buf, line_num, line_num + 1, false)[1]
-- local start_col = line:find(vim.pesc(icon)) -- Look for common default value patterns
-- if start_col then local patterns = {
-- api.nvim_buf_add_highlight(self.buf, -1, hl_group, line_num, start_col + 1, start_col + #icon + 1) "default:%s*([^%s]+)",
-- end "defaults%s+to%s+([^%s]+)",
-- line_num = line_num + 1 "%(default:%s*([^%)]+)%)",
-- end }
-- end
-- end 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(active_parameter, signatures, labels) function SignatureHelp:set_active_parameter_highlights(active_parameter, signatures, labels)
if not self.buf or not api.nvim_buf_is_valid(self.buf) then return end if not self.buf or not api.nvim_buf_is_valid(self.buf) then return end
-- Clear existing highlights
api.nvim_buf_clear_namespace(self.buf, -1, 0, -1) api.nvim_buf_clear_namespace(self.buf, -1, 0, -1)
-- Iterate over signatures to highlight the active parameter
for index, signature in ipairs(signatures) do for index, signature in ipairs(signatures) do
local parameter = signature.activeParameter or active_parameter local parameter = signature.activeParameter or active_parameter
if parameter and parameter >= 0 and parameter < #signature.parameters then if parameter and parameter >= 0 and parameter < #signature.parameters then
@ -178,8 +277,8 @@ function SignatureHelp:set_active_parameter_highlights(active_parameter, signatu
local signature_str = signature.label local signature_str = signature.label
local start_pos, end_pos = self:find_parameter_range(signature_str, label) local start_pos, end_pos = self:find_parameter_range(signature_str, label)
if start_pos and end_pos then if start_pos and end_pos then
api.nvim_buf_add_highlight(self.buf, -1, "LspSignatureActiveParameter", labels[index], start_pos - 1, api.nvim_buf_add_highlight(self.buf, -1, "LspSignatureActiveParameter", labels[index], start_pos,
end_pos - 1) end_pos)
end end
elseif type(label) == "table" then elseif type(label) == "table" then
local start_pos, end_pos = unpack(label) local start_pos, end_pos = unpack(label)
@ -202,27 +301,39 @@ function SignatureHelp:set_active_parameter_highlights(active_parameter, signatu
local line = api.nvim_buf_get_lines(self.buf, line_num, line_num + 1, false)[1] local line = api.nvim_buf_get_lines(self.buf, line_num, line_num + 1, false)[1]
local start_col = line:find(vim.pesc(icon)) local start_col = line:find(vim.pesc(icon))
if start_col then if start_col then
api.nvim_buf_add_highlight(self.buf, -1, hl_group, line_num, start_col, start_col + #icon) api.nvim_buf_add_highlight(self.buf, -1, hl_group, line_num, start_col - 1, start_col + #icon - 1)
end end
line_num = line_num + 1 line_num = line_num + 1
end end
end end
end end
function SignatureHelp:find_parameter_range(signature_str, parameter_label) function SignatureHelp:highlight_icons()
local start_pos = signature_str:find(parameter_label) local icon_highlights = {
if not start_pos then return nil, nil end { self.config.icons.method, "SignatureHelpMethod" },
{ self.config.icons.parameter, "SignatureHelpParameter" },
{ self.config.icons.documentation, "SignatureHelpDocumentation" },
}
local end_pos = start_pos + #parameter_label - 1 for _, icon_hl in ipairs(icon_highlights) do
local icon, hl_group = unpack(icon_hl)
-- Ensure the parameter label is not part of a larger word local line_num = 0
local before_char = signature_str:sub(start_pos - 1, start_pos - 1) while line_num < api.nvim_buf_line_count(self.buf) do
local after_char = signature_str:sub(end_pos + 1, end_pos + 1) local line = api.nvim_buf_get_lines(self.buf, line_num, line_num + 1, false)[1]
if (start_pos > 1 and not before_char:match("%s")) or (end_pos < #signature_str and not after_char:match("%s")) then local start_col = line:find(vim.pesc(icon))
return nil, nil 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
return start_pos - 1, end_pos
end end
function SignatureHelp:display(result) function SignatureHelp:display(result)
@ -231,30 +342,67 @@ function SignatureHelp:display(result)
return return
end end
local markdown, labels = markdown_for_signature_list(result.signatures, self.config) -- Store current window and buffer
local current_win = api.nvim_get_current_win()
local current_buf = api.nvim_get_current_buf()
if vim.deep_equal(result.signatures, self.current_signatures) then -- 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 then
return return
end end
local markdown, labels = markdown_for_signature_list(result.signatures, self.config)
self.current_signatures = result.signatures self.current_signatures = result.signatures
self.current_active_parameter = result.activeParameter
if #markdown > 0 then 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) self:create_float_window(markdown)
api.nvim_buf_set_option(self.buf, "filetype", "markdown") api.nvim_buf_set_option(self.buf, "filetype", "markdown")
self:set_active_parameter_highlights(result.activeParameter, result.signatures, labels) self:set_active_parameter_highlights(result.activeParameter, result.signatures, labels)
self:apply_treesitter_highlighting() self:apply_treesitter_highlighting()
end
else else
self:hide() self:hide()
end end
-- Restore focus to original window and buffer
api.nvim_set_current_win(current_win)
api.nvim_set_current_buf(current_buf)
end end
function SignatureHelp:apply_treesitter_highlighting() 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 if not pcall(require, "nvim-treesitter") then
return return
end end
require("nvim-treesitter.highlight").attach(self.buf, "markdown") -- 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()
@ -270,11 +418,12 @@ function SignatureHelp:trigger()
return return
end end
if result then if result and result.signatures and #result.signatures > 0 then
self:display(result) self:display(result)
else else
self:hide() self:hide()
if not self.config.silent then -- 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) vim.notify("No signature help available", vim.log.levels.INFO)
end end
end end
@ -364,41 +513,265 @@ function SignatureHelp:setup_autocmds()
}) })
end end
function M.setup(opts) function SignatureHelp:create_dock_window()
opts = opts or {} -- Store current window and buffer
local signature_help = SignatureHelp.new() local current_win = api.nvim_get_current_win()
signature_help.config = vim.tbl_deep_extend("force", signature_help.config, opts) local current_buf = api.nvim_get_current_buf()
signature_help:setup_autocmds()
local toggle_key = opts.toggle_key or "<C-k>" -- Update dock window ID for current buffer
vim.keymap.set("n", toggle_key, function() self.dock_win_id = "signature_help_dock_" .. current_buf
signature_help:toggle_normal_mode()
end, { noremap = true, silent = true, desc = "Toggle signature help in normal mode" })
if pcall(require, "nvim-treesitter") then if not self.dock_win or not api.nvim_win_is_valid(self.dock_win) then
require("nvim-treesitter").define_modules({ -- Create dock buffer if needed
signature_help_highlighting = { if not self.dock_buf or not api.nvim_buf_is_valid(self.dock_buf) then
module_path = "signature_help.highlighting", self.dock_buf = api.nvim_create_buf(false, true)
is_supported = function(lang) api.nvim_buf_set_option(self.dock_buf, "buftype", "nofile")
return lang == "markdown" api.nvim_buf_set_option(self.dock_buf, "bufhidden", "hide")
end, 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
-- 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({
signatures = self.current_signatures,
activeParameter = self.current_active_parameter,
activeSignature = self.current_signature_idx - 1
}) })
end end
vim.api.nvim_set_hl(0, "LspSignatureActiveParameter",
{ fg = opts.config.active_parameter_colors.fg, bg = opts.config.active_parameter_colors.bg })
vim.cmd(string.format([[
highlight default SignatureHelpMethod guifg=%s
highlight default SignatureHelpParameter guifg=%s
highlight default SignatureHelpDocumentation guifg=%s
]], signature_help.config.colors.method, signature_help.config.colors.parameter,
signature_help.config.colors.documentation))
if opts.override then function SignatureHelp:prev_signature()
vim.lsp.handlers["textDocument/signatureHelp"] = function(_, result, context, config) if not self.current_signatures then return end
config = vim.tbl_deep_extend("force", signature_help.config, config or {}) self.current_signature_idx = (self.current_signature_idx or 1) - 1
signature_help:display(result) if self.current_signature_idx < 1 then
self.current_signature_idx = #self.current_signatures
end end
self:display({
signatures = self.current_signatures,
activeParameter = self.current_active_parameter,
activeSignature = self.current_signature_idx - 1
})
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
local dock_toggle_key = self.config.dock_toggle_key
if toggle_key then
vim.keymap.set("n", toggle_key, function()
self:toggle_normal_mode()
end, { noremap = true, silent = true, desc = "Toggle signature help in normal mode" })
end
if dock_toggle_key then
vim.keymap.set("n", dock_toggle_key, function()
self:toggle_dock_mode()
end, { noremap = true, silent = true, desc = "Toggle between dock and float mode" })
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 = {
SignatureHelpDock = { link = "NormalFloat" },
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 and keymaps
signature_help:setup_autocmds()
signature_help:setup_keymaps()
-- Store instance for potential reuse
M._initialized = true
M._instance = signature_help
return signature_help
end
-- Add version and metadata for lazy.nvim compatibility
M.version = "1.0.0"
M.dependencies = {
"nvim-treesitter/nvim-treesitter",
}
-- 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
end end