├── .stylua.toml
├── LICENSE
├── README.md
├── doc
├── nvim-lightbulb.txt
└── tags
└── lua
└── nvim-lightbulb
├── config.lua
└── init.lua
/.stylua.toml:
--------------------------------------------------------------------------------
1 | indent_type = "Spaces"
2 | indent_width = 2
3 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Kieran Siek
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # nvim-lightbulb
2 |
3 | VSCode 💡 for neovim's built-in LSP.
4 |
5 |
6 | ## Table of contents
7 |
8 | - [Introduction](#introduction)
9 | - [Prerequisites](#prerequisites)
10 | - [Installation](#installation)
11 | - [Usage](#usage)
12 | - [Configuration](#configuration)
13 |
14 | ## Introduction
15 | The plugin shows a lightbulb in the sign column whenever a `textDocument/codeAction` is available at the current cursor position.
16 |
17 | This makes code actions both [discoverable and efficient](https://rust-analyzer.github.io/blog/2020/09/28/how-to-make-a-light-bulb.html#the-mighty), as code actions can be available even when there are no visible diagnostics (warning, information, hints etc.).
18 |
19 | ### Features
20 |
21 |
22 |
23 | > In the screenshot, colorscheme is [catppuccin](https://github.com/catppuccin/nvim), font is [iosevka](https://typeof.net/Iosevka/), programming language is [rust](https://www.rust-lang.org/)
24 |
25 | When there is a *code action* available at the current cursor location, show a lightbulb...
26 | 1. in the **sign column**
27 | 2. as **virtual text**
28 | 3. in a **floating window**
29 |
30 | or, change the look of
31 |
32 | 4. the **number column**
33 | 5. the **current line**
34 |
35 | or, get a configured message
36 |
37 | 6. as **status text**, retrievable with `require("nvim-lightbulb").get_status_text()`
38 |
39 | ## Prerequisites
40 |
41 | * Neovim v0.9.0 and above. Older versions may work but are not tested.
42 | * Working LSP server configuration.
43 |
44 | ## Installation
45 |
46 | Just like any other plugin.
47 |
48 | Example using [lazy.nvim](https://github.com/folke/lazy.nvim):
49 | ```lua
50 | { 'kosayoda/nvim-lightbulb' }
51 | ```
52 |
53 | Example using [packer.nvim](https://github.com/wbthomason/packer.nvim):
54 | ```lua
55 | use { 'kosayoda/nvim-lightbulb' }
56 | ```
57 |
58 | Example using [vim-plug](https://github.com/junegunn/vim-plug):
59 | ```vim
60 | Plug 'kosayoda/nvim-lightbulb'
61 | ```
62 |
63 | ## Usage
64 |
65 | Place this in your neovim configuration.
66 |
67 | ```lua
68 | require("nvim-lightbulb").setup({
69 | autocmd = { enabled = true }
70 | })
71 | ```
72 |
73 | - Configuration can be passed to `NvimLightbulb.setup`, or to `NvimLightbulb.update_lightbulb`.
74 | - Any configuration passed to `update_lightbulb` will override the one in `setup`.
75 | - For all options, see the [Configuration](#configuration) section.
76 | - To debug `nvim-lightbulb` see `NvimLightbulb.debug`
77 |
78 | ## Configuration
79 |
80 | ```lua
81 | local default_config = {
82 | -- Priority of the lightbulb for all handlers except float.
83 | priority = 10,
84 |
85 | -- Whether or not to hide the lightbulb when the buffer is not focused.
86 | -- Only works if configured during NvimLightbulb.setup
87 | hide_in_unfocused_buffer = true,
88 |
89 | -- Whether or not to link the highlight groups automatically.
90 | -- Default highlight group links:
91 | -- LightBulbSign -> DiagnosticSignInfo
92 | -- LightBulbFloatWin -> DiagnosticFloatingInfo
93 | -- LightBulbVirtualText -> DiagnosticVirtualTextInfo
94 | -- LightBulbNumber -> DiagnosticSignInfo
95 | -- LightBulbLine -> CursorLine
96 | -- Only works if configured during NvimLightbulb.setup
97 | link_highlights = true,
98 |
99 | -- Perform full validation of configuration.
100 | -- Available options: "auto", "always", "never"
101 | -- "auto" only performs full validation in NvimLightbulb.setup.
102 | -- "always" performs full validation in NvimLightbulb.update_lightbulb as well.
103 | -- "never" disables config validation.
104 | validate_config = "auto",
105 |
106 | -- Code action kinds to observe.
107 | -- To match all code actions, set to `nil`.
108 | -- Otherwise, set to a table of kinds.
109 | -- Example: { "quickfix", "refactor.rewrite" }
110 | -- See: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#codeActionKind
111 | action_kinds = nil,
112 |
113 | -- Enable code lens support.
114 | -- If the current position has executable code lenses, the icon is changed from `text` to `lens_text`
115 | -- for sign, virtual_text, float and status_text.
116 | -- The code lens icon is configurable per handler.
117 | code_lenses = false,
118 |
119 | -- Configuration for various handlers:
120 | -- 1. Sign column.
121 | sign = {
122 | enabled = true,
123 | -- Text to show in the sign column.
124 | -- Must be between 1-2 characters.
125 | text = "💡",
126 | lens_text = "🔎",
127 | -- Highlight group to highlight the sign column text.
128 | hl = "LightBulbSign",
129 | },
130 |
131 | -- 2. Virtual text.
132 | virtual_text = {
133 | enabled = false,
134 | -- Text to show in the virt_text.
135 | text = "💡",
136 | lens_text = "🔎",
137 | -- Position of virtual text given to |nvim_buf_set_extmark|.
138 | -- Can be a number representing a fixed column (see `virt_text_pos`).
139 | -- Can be a string representing a position (see `virt_text_win_col`).
140 | pos = "eol",
141 | -- Highlight group to highlight the virtual text.
142 | hl = "LightBulbVirtualText",
143 | -- How to combine other highlights with text highlight.
144 | -- See `hl_mode` of |nvim_buf_set_extmark|.
145 | hl_mode = "combine",
146 | },
147 |
148 | -- 3. Floating window.
149 | float = {
150 | enabled = false,
151 | -- Text to show in the floating window.
152 | text = "💡",
153 | lens_text = "🔎",
154 | -- Highlight group to highlight the floating window.
155 | hl = "LightBulbFloatWin",
156 | -- Window options.
157 | -- See |vim.lsp.util.open_floating_preview| and |nvim_open_win|.
158 | -- Note that some options may be overridden by |open_floating_preview|.
159 | win_opts = {
160 | focusable = false,
161 | },
162 | },
163 |
164 | -- 4. Status text.
165 | -- When enabled, will allow using |NvimLightbulb.get_status_text|
166 | -- to retrieve the configured text.
167 | status_text = {
168 | enabled = false,
169 | -- Text to set if a lightbulb is available.
170 | text = "💡",
171 | lens_text = "🔎",
172 | -- Text to set if a lightbulb is unavailable.
173 | text_unavailable = "",
174 | },
175 |
176 | -- 5. Number column.
177 | number = {
178 | enabled = false,
179 | -- Highlight group to highlight the number column if there is a lightbulb.
180 | hl = "LightBulbNumber",
181 | },
182 |
183 | -- 6. Content line.
184 | line = {
185 | enabled = false,
186 | -- Highlight group to highlight the line if there is a lightbulb.
187 | hl = "LightBulbLine",
188 | },
189 |
190 | -- Autocmd configuration.
191 | -- If enabled, automatically defines an autocmd to show the lightbulb.
192 | -- If disabled, you will have to manually call |NvimLightbulb.update_lightbulb|.
193 | -- Only works if configured during NvimLightbulb.setup
194 | autocmd = {
195 | -- Whether or not to enable autocmd creation.
196 | enabled = false,
197 | -- See |updatetime|.
198 | -- Set to a negative value to avoid setting the updatetime.
199 | updatetime = 200,
200 | -- See |nvim_create_autocmd|.
201 | events = { "CursorHold", "CursorHoldI" },
202 | -- See |nvim_create_autocmd| and |autocmd-pattern|.
203 | pattern = { "*" },
204 | },
205 |
206 | -- Scenarios to not show a lightbulb.
207 | ignore = {
208 | -- LSP client names to ignore.
209 | -- Example: {"null-ls", "lua_ls"}
210 | clients = {},
211 | -- Filetypes to ignore.
212 | -- Example: {"neo-tree", "lua"}
213 | ft = {},
214 | -- Ignore code actions without a `kind` like refactor.rewrite, quickfix.
215 | actions_without_kind = false,
216 | },
217 |
218 | --- A general filter function for code actions.
219 | --- The function is called for code actions *after* any `ignore` or `action_kinds`
220 | --- options are applied.
221 | --- The function should return true to keep the code action, false otherwise.
222 | ---@type (fun(client_name:string, result:lsp.CodeAction|lsp.Command):boolean)|nil
223 | filter = nil,
224 | }
225 | ```
226 |
--------------------------------------------------------------------------------
/doc/nvim-lightbulb.txt:
--------------------------------------------------------------------------------
1 | ==============================================================================
2 | ------------------------------------------------------------------------------
3 | *nvim-lightbulb*
4 |
5 | *nvim-lightbulb*: VSCode 💡 for neovim's built-in LSP.
6 |
7 | --- Quickstart ---
8 |
9 | Place this in your neovim configuration.
10 | >
11 | require("nvim-lightbulb").setup({
12 | autocmd = { enabled = true }
13 | })
14 |
15 | See |nvim-lightbulb-config| for available config settings.
16 |
17 |
18 | --- Modify Highlights ---
19 |
20 | To modify highlights, configure the corresponding highlight group.
21 | See |nvim-lightbulb-config| for a list of highlights used.
22 |
23 | Example:
24 | >
25 | vim.api.nvim_set_hl(0, "LightBulbSign", {link = "DiagnosticSignWarn"})
26 |
27 | ------------------------------------------------------------------------------
28 | *NvimLightbulb.setup()*
29 | `NvimLightbulb.setup`({config})
30 | Module setup.
31 |
32 | Optional. Any configuration can also be passed to |NvimLightbulb.update_lightbulb|.
33 |
34 | Parameters ~
35 | {config} `(nvim-lightbulb.Config|nil)` Partial or full configuration table. See |nvim-lightbulb-config|.
36 |
37 | Usage ~
38 | `require('nvim-lightbulb').setup({})`
39 |
40 | ------------------------------------------------------------------------------
41 | *NvimLightbulb.get_status_text()*
42 | `NvimLightbulb.get_status_text`({bufnr})
43 |
44 | Get the configured text according to lightbulb status.
45 | Any configuration provided overrides the defaults passed to |NvimLightbulb.setup|.
46 |
47 | Parameters ~
48 | {bufnr} `(number|nil)` Buffer handle. Defaults to current buffer.
49 |
50 | Usage ~
51 | `require('nvim-lightbulb').get_status_text()`
52 |
53 | ------------------------------------------------------------------------------
54 | *NvimLightbulb.update_lightbulb()*
55 | `NvimLightbulb.update_lightbulb`({config})
56 |
57 | Display the lightbulb according to configuration.
58 | Any configuration provided overrides the defaults passed to |NvimLightbulb.setup|.
59 |
60 |
61 | Parameters ~
62 | {config} `(table|nil)` Partial or full configuration table. See |nvim-lightbulb-config|.
63 |
64 | Usage ~
65 | `require('nvim-lightbulb').update_lightbulb({})`
66 |
67 | ------------------------------------------------------------------------------
68 | *NvimLightbulb.debug()*
69 | `NvimLightbulb.debug`({config})
70 |
71 | Display debug information related to nvim-lightbulb.
72 | Prints information about:
73 | - The current configuration
74 | - LSP servers found, ignored, supporting code actions...
75 | - Any code actions at the current location along with their code action kind
76 |
77 | Parameters ~
78 | {config} `(table|nil)` Partial or full configuration table. See |nvim-lightbulb-config|.
79 |
80 | Usage ~
81 | `require('nvim-lightbulb').debug({})`
82 |
83 |
84 | ==============================================================================
85 | ------------------------------------------------------------------------------
86 | *nvim-lightbulb-config*
87 | `default_config`
88 |
89 | Pass the configuration to |NvimLightbulb.setup| or |NvimLightbulb.update_lightbulb|.
90 |
91 | Default values:
92 | >lua
93 | local default_config = {
94 | -- Priority of the lightbulb for all handlers except float.
95 | priority = 10,
96 |
97 | -- Whether or not to hide the lightbulb when the buffer is not focused.
98 | -- Only works if configured during NvimLightbulb.setup
99 | hide_in_unfocused_buffer = true,
100 |
101 | -- Whether or not to link the highlight groups automatically.
102 | -- Default highlight group links:
103 | -- LightBulbSign -> DiagnosticSignInfo
104 | -- LightBulbFloatWin -> DiagnosticFloatingInfo
105 | -- LightBulbVirtualText -> DiagnosticVirtualTextInfo
106 | -- LightBulbNumber -> DiagnosticSignInfo
107 | -- LightBulbLine -> CursorLine
108 | -- Only works if configured during NvimLightbulb.setup
109 | link_highlights = true,
110 |
111 | -- Perform full validation of configuration.
112 | -- Available options: "auto", "always", "never"
113 | -- "auto" only performs full validation in NvimLightbulb.setup.
114 | -- "always" performs full validation in NvimLightbulb.update_lightbulb as well.
115 | -- "never" disables config validation.
116 | validate_config = "auto",
117 |
118 | -- Code action kinds to observe.
119 | -- To match all code actions, set to `nil`.
120 | -- Otherwise, set to a table of kinds.
121 | -- Example: { "quickfix", "refactor.rewrite" }
122 | -- See: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#codeActionKind
123 | action_kinds = nil,
124 |
125 | -- Enable code lens support.
126 | -- If the current position has executable code lenses, the icon is changed from `text` to `lens_text`
127 | -- for sign, virtual_text, float and status_text.
128 | -- The code lens icon is configurable per handler.
129 | code_lenses = false,
130 |
131 | -- Configuration for various handlers:
132 | -- 1. Sign column.
133 | sign = {
134 | enabled = true,
135 | -- Text to show in the sign column.
136 | -- Must be between 1-2 characters.
137 | text = "💡",
138 | lens_text = "🔎",
139 | -- Highlight group to highlight the sign column text.
140 | hl = "LightBulbSign",
141 | },
142 |
143 | -- 2. Virtual text.
144 | virtual_text = {
145 | enabled = false,
146 | -- Text to show in the virt_text.
147 | text = "💡",
148 | lens_text = "🔎",
149 | -- Position of virtual text given to |nvim_buf_set_extmark|.
150 | -- Can be a number representing a fixed column (see `virt_text_pos`).
151 | -- Can be a string representing a position (see `virt_text_win_col`).
152 | pos = "eol",
153 | -- Highlight group to highlight the virtual text.
154 | hl = "LightBulbVirtualText",
155 | -- How to combine other highlights with text highlight.
156 | -- See `hl_mode` of |nvim_buf_set_extmark|.
157 | hl_mode = "combine",
158 | },
159 |
160 | -- 3. Floating window.
161 | float = {
162 | enabled = false,
163 | -- Text to show in the floating window.
164 | text = "💡",
165 | lens_text = "🔎",
166 | -- Highlight group to highlight the floating window.
167 | hl = "LightBulbFloatWin",
168 | -- Window options.
169 | -- See |vim.lsp.util.open_floating_preview| and |nvim_open_win|.
170 | -- Note that some options may be overridden by |open_floating_preview|.
171 | win_opts = {
172 | focusable = false,
173 | },
174 | },
175 |
176 | -- 4. Status text.
177 | -- When enabled, will allow using |NvimLightbulb.get_status_text|
178 | -- to retrieve the configured text.
179 | status_text = {
180 | enabled = false,
181 | -- Text to set if a lightbulb is available.
182 | text = "💡",
183 | lens_text = "🔎",
184 | -- Text to set if a lightbulb is unavailable.
185 | text_unavailable = "",
186 | },
187 |
188 | -- 5. Number column.
189 | number = {
190 | enabled = false,
191 | -- Highlight group to highlight the number column if there is a lightbulb.
192 | hl = "LightBulbNumber",
193 | },
194 |
195 | -- 6. Content line.
196 | line = {
197 | enabled = false,
198 | -- Highlight group to highlight the line if there is a lightbulb.
199 | hl = "LightBulbLine",
200 | },
201 |
202 | -- Autocmd configuration.
203 | -- If enabled, automatically defines an autocmd to show the lightbulb.
204 | -- If disabled, you will have to manually call |NvimLightbulb.update_lightbulb|.
205 | -- Only works if configured during NvimLightbulb.setup
206 | autocmd = {
207 | -- Whether or not to enable autocmd creation.
208 | enabled = false,
209 | -- See |updatetime|.
210 | -- Set to a negative value to avoid setting the updatetime.
211 | updatetime = 200,
212 | -- See |nvim_create_autocmd|.
213 | ---@type string[]
214 | events = { "CursorHold", "CursorHoldI" },
215 | -- See |nvim_create_autocmd| and |autocmd-pattern|.
216 | ---@type string[]
217 | pattern = { "*" },
218 | },
219 |
220 | -- Scenarios to not show a lightbulb.
221 | ignore = {
222 | -- LSP client names to ignore.
223 | -- Example: {"null-ls", "lua_ls"}
224 | clients = {},
225 | -- Filetypes to ignore.
226 | -- Example: {"neo-tree", "lua"}
227 | ft = {},
228 | -- Ignore code actions without a `kind` like refactor.rewrite, quickfix.
229 | actions_without_kind = false,
230 | },
231 |
232 | --- A general filter function for code actions.
233 | --- The function is called for code actions after any `ignore` or `action_kinds`
234 | --- options are applied.
235 | --- The function should return true to keep the code action, false otherwise.
236 | ---@type (fun(client_name:string, result:lsp.CodeAction|lsp.Command):boolean)|nil
237 | filter = nil,
238 | }
239 |
240 | <
241 | Class ~
242 | {nvim-lightbulb.Config}
243 |
244 |
245 | vim:tw=78:ts=8:noet:ft=help:norl:
--------------------------------------------------------------------------------
/doc/tags:
--------------------------------------------------------------------------------
1 | NvimLightbulb.debug() nvim-lightbulb.txt /*NvimLightbulb.debug()*
2 | NvimLightbulb.get_status_text() nvim-lightbulb.txt /*NvimLightbulb.get_status_text()*
3 | NvimLightbulb.setup() nvim-lightbulb.txt /*NvimLightbulb.setup()*
4 | NvimLightbulb.update_lightbulb() nvim-lightbulb.txt /*NvimLightbulb.update_lightbulb()*
5 | nvim-lightbulb nvim-lightbulb.txt /*nvim-lightbulb*
6 | nvim-lightbulb-config nvim-lightbulb.txt /*nvim-lightbulb-config*
7 |
--------------------------------------------------------------------------------
/lua/nvim-lightbulb/config.lua:
--------------------------------------------------------------------------------
1 | local M = {}
2 |
3 | ---
4 | --- Pass the configuration to |NvimLightbulb.setup| or |NvimLightbulb.update_lightbulb|.
5 | ---
6 | --- Default values:
7 | ---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section)
8 | ---@tag nvim-lightbulb-config
9 | ---@class nvim-lightbulb.Config
10 | local default_config = {
11 | -- Priority of the lightbulb for all handlers except float.
12 | priority = 10,
13 |
14 | -- Whether or not to hide the lightbulb when the buffer is not focused.
15 | -- Only works if configured during NvimLightbulb.setup
16 | hide_in_unfocused_buffer = true,
17 |
18 | -- Whether or not to link the highlight groups automatically.
19 | -- Default highlight group links:
20 | -- LightBulbSign -> DiagnosticSignInfo
21 | -- LightBulbFloatWin -> DiagnosticFloatingInfo
22 | -- LightBulbVirtualText -> DiagnosticVirtualTextInfo
23 | -- LightBulbNumber -> DiagnosticSignInfo
24 | -- LightBulbLine -> CursorLine
25 | -- Only works if configured during NvimLightbulb.setup
26 | link_highlights = true,
27 |
28 | -- Perform full validation of configuration.
29 | -- Available options: "auto", "always", "never"
30 | -- "auto" only performs full validation in NvimLightbulb.setup.
31 | -- "always" performs full validation in NvimLightbulb.update_lightbulb as well.
32 | -- "never" disables config validation.
33 | validate_config = "auto",
34 |
35 | -- Code action kinds to observe.
36 | -- To match all code actions, set to `nil`.
37 | -- Otherwise, set to a table of kinds.
38 | -- Example: { "quickfix", "refactor.rewrite" }
39 | -- See: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#codeActionKind
40 | action_kinds = nil,
41 |
42 | -- Enable code lens support.
43 | -- If the current position has executable code lenses, the icon is changed from `text` to `lens_text`
44 | -- for sign, virtual_text, float and status_text.
45 | -- The code lens icon is configurable per handler.
46 | code_lenses = false,
47 |
48 | -- Configuration for various handlers:
49 | -- 1. Sign column.
50 | sign = {
51 | enabled = true,
52 | -- Text to show in the sign column.
53 | -- Must be between 1-2 characters.
54 | text = "💡",
55 | lens_text = "🔎",
56 | -- Highlight group to highlight the sign column text.
57 | hl = "LightBulbSign",
58 | },
59 |
60 | -- 2. Virtual text.
61 | virtual_text = {
62 | enabled = false,
63 | -- Text to show in the virt_text.
64 | text = "💡",
65 | lens_text = "🔎",
66 | -- Position of virtual text given to |nvim_buf_set_extmark|.
67 | -- Can be a number representing a fixed column (see `virt_text_pos`).
68 | -- Can be a string representing a position (see `virt_text_win_col`).
69 | pos = "eol",
70 | -- Highlight group to highlight the virtual text.
71 | hl = "LightBulbVirtualText",
72 | -- How to combine other highlights with text highlight.
73 | -- See `hl_mode` of |nvim_buf_set_extmark|.
74 | hl_mode = "combine",
75 | },
76 |
77 | -- 3. Floating window.
78 | float = {
79 | enabled = false,
80 | -- Text to show in the floating window.
81 | text = "💡",
82 | lens_text = "🔎",
83 | -- Highlight group to highlight the floating window.
84 | hl = "LightBulbFloatWin",
85 | -- Window options.
86 | -- See |vim.lsp.util.open_floating_preview| and |nvim_open_win|.
87 | -- Note that some options may be overridden by |open_floating_preview|.
88 | win_opts = {
89 | focusable = false,
90 | },
91 | },
92 |
93 | -- 4. Status text.
94 | -- When enabled, will allow using |NvimLightbulb.get_status_text|
95 | -- to retrieve the configured text.
96 | status_text = {
97 | enabled = false,
98 | -- Text to set if a lightbulb is available.
99 | text = "💡",
100 | lens_text = "🔎",
101 | -- Text to set if a lightbulb is unavailable.
102 | text_unavailable = "",
103 | },
104 |
105 | -- 5. Number column.
106 | number = {
107 | enabled = false,
108 | -- Highlight group to highlight the number column if there is a lightbulb.
109 | hl = "LightBulbNumber",
110 | },
111 |
112 | -- 6. Content line.
113 | line = {
114 | enabled = false,
115 | -- Highlight group to highlight the line if there is a lightbulb.
116 | hl = "LightBulbLine",
117 | },
118 |
119 | -- Autocmd configuration.
120 | -- If enabled, automatically defines an autocmd to show the lightbulb.
121 | -- If disabled, you will have to manually call |NvimLightbulb.update_lightbulb|.
122 | -- Only works if configured during NvimLightbulb.setup
123 | autocmd = {
124 | -- Whether or not to enable autocmd creation.
125 | enabled = false,
126 | -- See |updatetime|.
127 | -- Set to a negative value to avoid setting the updatetime.
128 | updatetime = 200,
129 | -- See |nvim_create_autocmd|.
130 | ---@type string[]
131 | events = { "CursorHold", "CursorHoldI" },
132 | -- See |nvim_create_autocmd| and |autocmd-pattern|.
133 | ---@type string[]
134 | pattern = { "*" },
135 | },
136 |
137 | -- Scenarios to not show a lightbulb.
138 | ignore = {
139 | -- LSP client names to ignore.
140 | -- Example: {"null-ls", "lua_ls"}
141 | clients = {},
142 | -- Filetypes to ignore.
143 | -- Example: {"neo-tree", "lua"}
144 | ft = {},
145 | -- Ignore code actions without a `kind` like refactor.rewrite, quickfix.
146 | actions_without_kind = false,
147 | },
148 |
149 | --- A general filter function for code actions.
150 | --- The function is called for code actions after any `ignore` or `action_kinds`
151 | --- options are applied.
152 | --- The function should return true to keep the code action, false otherwise.
153 | ---@type (fun(client_name:string, result:lsp.CodeAction|lsp.Command):boolean)|nil
154 | filter = nil,
155 | }
156 |
157 | --- Build a configuration based on the default configuration and accept overwrites.
158 | ---
159 | ---@param config nvim-lightbulb.Config|nil Partial or full configuration table. See |nvim-lightbulb-config|.
160 | ---@param is_setup boolean Whether or not the command is called during setup.
161 | ---@return nvim-lightbulb.Config
162 | ---
163 | ---@private
164 | M.build = function(config, is_setup)
165 | config = config or {}
166 | vim.validate({ config = { config, "table" } })
167 |
168 | config = vim.tbl_deep_extend("force", default_config, config)
169 |
170 | local validate = config.validate_config
171 | vim.validate({
172 | ["config.validate_config"] = {
173 | validate,
174 | function(c)
175 | return c == "auto" or c == "always" or c == "never"
176 | end,
177 | },
178 | })
179 | if validate == "never" or (validate == "auto" and not is_setup) then
180 | return config
181 | end
182 |
183 | -- Validate config
184 | vim.validate({
185 | hide_in_unfocused_buffer = { config.hide_in_unfocused_buffer, "boolean" },
186 | link_highlights = { config.link_highlights, "boolean" },
187 | action_kinds = { config.action_kinds, "table", true },
188 | code_lenses = { config.code_lenses, "boolean" },
189 | sign = { config.sign, "table" },
190 | virtual_text = { config.virtual_text, "table" },
191 | float = { config.float, "table" },
192 | status_text = { config.status_text, "table" },
193 | number = { config.number, "table" },
194 | line = { config.line, "table" },
195 | autocmd = { config.autocmd, "table" },
196 | ignore = { config.ignore, "table" },
197 | filter = { config.filter, "function", true },
198 | })
199 |
200 | vim.validate({
201 | ["sign.enabled"] = { config.sign.enabled, "boolean" },
202 | ["sign.text"] = { config.sign.text, "string" },
203 | ["sign.hl"] = { config.sign.hl, "string" },
204 | ["virtual_text.enabled"] = { config.virtual_text.enabled, "boolean" },
205 | ["virtual_text.text"] = { config.virtual_text.text, "string" },
206 | ["virtual_text.pos"] = { config.virtual_text.pos, { "string", "number" } },
207 | ["virtual_text.hl"] = { config.virtual_text.hl, "string" },
208 | ["virtual_text.hl_mode"] = { config.virtual_text.hl_mode, "string" },
209 | ["float.enabled"] = { config.float.enabled, "boolean" },
210 | ["float.text"] = { config.float.text, "string" },
211 | ["float.hl"] = { config.float.hl, "string" },
212 | ["float.win_opts"] = { config.float.win_opts, "table" },
213 | ["status_text.enabled"] = { config.status_text.enabled, "boolean" },
214 | ["status_text.text"] = { config.status_text.text, "string" },
215 | ["status_text.text_unavailable"] = { config.status_text.text_unavailable, "string" },
216 | ["number.enabled"] = { config.number.enabled, "boolean" },
217 | ["number.hl"] = { config.number.hl, "string" },
218 | ["line.enabled"] = { config.line.enabled, "boolean" },
219 | ["line.hl"] = { config.line.hl, "string" },
220 | ["autocmd.enabled"] = { config.autocmd.enabled, "boolean" },
221 | ["autocmd.updatetime"] = { config.autocmd.updatetime, "number" },
222 | ["autocmd.events"] = { config.autocmd.events, "table" },
223 | ["autocmd.pattern"] = { config.autocmd.pattern, "table" },
224 | ["ignore.clients"] = { config.ignore.clients, "table" },
225 | ["ignore.ft"] = { config.ignore.ft, "table" },
226 | ["ignore.actions_without_kind"] = { config.ignore.actions_without_kind, "boolean" },
227 | })
228 |
229 | return config
230 | end
231 |
232 | --- Set default configuration. Prefer |NvimLightbulb.setup| instead.
233 | ---
234 | ---@param opts nvim-lightbulb.Config|nil Partial or full configuration table. See |nvim-lightbulb-config|.
235 | ---
236 | ---@private
237 | M.set_defaults = function(opts)
238 | local new_opts = M.build(opts, true)
239 | default_config = new_opts
240 |
241 | local id = vim.api.nvim_create_augroup("LightBulb", {})
242 | -- Set up autocmd for update_lightbulb if configured
243 | if default_config.autocmd.enabled then
244 | if default_config.autocmd.updatetime > 0 then
245 | vim.opt.updatetime = default_config.autocmd.updatetime
246 | end
247 |
248 | vim.api.nvim_create_autocmd(default_config.autocmd.events, {
249 | pattern = default_config.autocmd.pattern,
250 | group = id,
251 | desc = "lua require('nvim-lightbulb').update_lightbulb()",
252 | callback = require("nvim-lightbulb").update_lightbulb,
253 | })
254 | end
255 |
256 | -- Set up autocmd for clear_lightbulb if configured
257 | if default_config.hide_in_unfocused_buffer then
258 | vim.api.nvim_create_autocmd({ "WinLeave" }, {
259 | pattern = { "*" },
260 | group = id,
261 | desc = "lua require('nvim-lightbulb').clear_lightbulb()",
262 | callback = function(args)
263 | require("nvim-lightbulb").clear_lightbulb(args.buf)
264 | end,
265 | })
266 | end
267 |
268 | -- Set up default highlight links
269 | if default_config.link_highlights then
270 | vim.api.nvim_set_hl(0, "LightBulbSign", { default = true, link = "DiagnosticSignInfo" })
271 | vim.api.nvim_set_hl(0, "LightBulbFloatWin", { default = true, link = "DiagnosticFloatingInfo" })
272 | vim.api.nvim_set_hl(0, "LightBulbVirtualText", { default = true, link = "DiagnosticVirtualTextInfo" })
273 | vim.api.nvim_set_hl(0, "LightBulbNumber", { default = true, link = "DiagnosticSignInfo" })
274 | vim.api.nvim_set_hl(0, "LightBulbLine", { default = true, link = "CursorLine" })
275 | end
276 | end
277 |
278 | --- Get a prettified representation of the config in a format suitable for |nvim_echo|.
279 | ---
280 | ---@param opts nvim-lightbulb.Config Configuration table. See |nvim-lightbulb-config|.
281 | ---@return table # The prettified configuration
282 | ---
283 | ---@private
284 | M.pretty_format = function(opts)
285 | local lines = {}
286 |
287 | local F = {}
288 |
289 | F.append = function(str, hl)
290 | table.insert(lines, { str, hl })
291 | end
292 |
293 | F.format = function(value, indent, prefix)
294 | indent = indent or 0
295 | if prefix then
296 | F.append(prefix)
297 | end
298 |
299 | if type(value) == "string" then
300 | F.append(string.format("'%s'", value), "String")
301 | elseif type(value) == "number" then
302 | F.append(tostring(value), "Number")
303 | elseif type(value) == "boolean" then
304 | F.append(tostring(value), "Boolean")
305 | elseif type(value) == "nil" then
306 | F.append("nil", "Keyword")
307 | elseif type(value) == "table" then
308 | if vim.tbl_isempty(value) then
309 | F.append("{}")
310 | else
311 | if vim.tbl_islist(value) then
312 | F.format_list(value)
313 | else
314 | F.format_table(value, indent + 2)
315 | end
316 | end
317 | else
318 | F.append(string.format("<%s>", type(value)))
319 | end
320 | end
321 |
322 | F.format_list = function(t)
323 | F.append("{ ")
324 | for idx, value in ipairs(t) do
325 | if idx ~= 1 then
326 | F.append(", ")
327 | end
328 | F.format(value)
329 | end
330 | F.append(" }")
331 | end
332 |
333 | F.format_table = function(t, indent)
334 | indent = indent or 0
335 |
336 | local idx = 0
337 |
338 | local is_long = vim.tbl_count(t) > 3
339 | local prefix = is_long and string.rep(" ", indent) or ""
340 | local suffix = is_long and "\n" or " "
341 |
342 | F.append("{" .. suffix)
343 |
344 | for key, value in pairs(t) do
345 | if idx ~= 0 then
346 | F.append("," .. suffix)
347 | end
348 |
349 | idx = idx + 1
350 |
351 | F.append(prefix .. key)
352 | F.append(" = ", "Operator")
353 | F.format(value, indent)
354 | end
355 |
356 | prefix = is_long and string.rep(" ", indent - 2) or ""
357 | F.append(suffix .. prefix .. "}")
358 | end
359 |
360 | F.format(opts)
361 | return lines
362 | end
363 |
364 | return M
365 |
--------------------------------------------------------------------------------
/lua/nvim-lightbulb/init.lua:
--------------------------------------------------------------------------------
1 | ---
2 | --- *nvim-lightbulb*: VSCode 💡 for neovim's built-in LSP.
3 | ---
4 | --- --- Quickstart ---
5 | ---
6 | --- Place this in your neovim configuration.
7 | --- >
8 | --- require("nvim-lightbulb").setup({
9 | --- autocmd = { enabled = true }
10 | --- })
11 | ---
12 | --- See |nvim-lightbulb-config| for available config settings.
13 | ---
14 | ---
15 | --- --- Modify Highlights ---
16 | ---
17 | --- To modify highlights, configure the corresponding highlight group.
18 | --- See |nvim-lightbulb-config| for a list of highlights used.
19 | ---
20 | --- Example:
21 | --- >
22 | --- vim.api.nvim_set_hl(0, "LightBulbSign", {link = "DiagnosticSignWarn"})
23 | ---@tag nvim-lightbulb
24 |
25 | local lsp_util = require("vim.lsp.util")
26 | local lightbulb_config = require("nvim-lightbulb.config")
27 |
28 | -- MSNV: 0.9.0
29 | local get_lsp_active_clients = vim.lsp.get_active_clients
30 | local get_lsp_line_diagnostics = function()
31 | return vim.lsp.diagnostic.get_line_diagnostics(0)
32 | end
33 |
34 | local set_win_option = vim.api.nvim_win_set_option
35 |
36 | ---@param client vim.lsp.Client
37 | ---@return fun(method:string, opts?: {bufnr: integer?}):boolean
38 | local supports_method = function(client)
39 | return client.supports_method
40 | end
41 |
42 | if vim.fn.has("nvim-0.10") == 1 then
43 | get_lsp_active_clients = vim.lsp.get_clients
44 | set_win_option = function(window, name, value)
45 | vim.wo[window][0][name] = value
46 | end
47 | end
48 |
49 | if vim.fn.has("nvim-0.11") == 1 then
50 | get_lsp_line_diagnostics = function()
51 | local opts = { lnum = vim.api.nvim_win_get_cursor(0)[1] - 1 }
52 | return vim.lsp.diagnostic.from(vim.diagnostic.get(0, opts))
53 | end
54 |
55 | supports_method = function(client)
56 | return function (method, opts)
57 | return client.supports_method(client, method, opts)
58 | end
59 | end
60 | end
61 |
62 | local NvimLightbulb = {}
63 |
64 | local LIGHTBULB_NS = vim.api.nvim_create_namespace("nvim-lightbulb")
65 |
66 | --- Module setup.
67 | ---
68 | --- Optional. Any configuration can also be passed to |NvimLightbulb.update_lightbulb|.
69 | ---
70 | ---@param config nvim-lightbulb.Config|nil Partial or full configuration table. See |nvim-lightbulb-config|.
71 | ---
72 | ---@usage `require('nvim-lightbulb').setup({})`
73 | NvimLightbulb.setup = function(config)
74 | _G.NvimLightbulb = NvimLightbulb
75 | lightbulb_config.set_defaults(config)
76 | end
77 |
78 | --- Check for codelenses at the current position.
79 | ---
80 | ---@param opts table Partial or full configuration table. See |nvim-lightbulb-config|.
81 | ---@param position table|nil The position to update the extmark to. If nil, it returns false.
82 | ---
83 | ---@private
84 | local function is_code_lens(opts, position)
85 | if not opts.code_lenses or position == nil then
86 | return false
87 | end
88 | local codelens_actions = {}
89 | for _, l in ipairs(vim.lsp.codelens.get(0)) do
90 | table.insert(codelens_actions, { start = l.range.start, finish = l.range["end"] })
91 | end
92 | for _, action in ipairs(codelens_actions) do
93 | if
94 | action.start.line <= position.line
95 | and position.line <= action.finish.line
96 | and action.start.character <= position.col
97 | and position.col <= action.finish.character
98 | then
99 | return true
100 | end
101 | end
102 | return false
103 | end
104 |
105 | --- Update the lightbulb float.
106 | ---
107 | ---@param opts table Partial or full configuration table. See |nvim-lightbulb-config|.
108 | ---@param position table|nil The position to update the extmark to. If nil, removes the extmark.
109 | ---@param bufnr integer The buffer to update the float in.
110 | ---
111 | ---@private
112 | local function update_float(opts, position, bufnr)
113 | -- Extract float options
114 | opts = opts.float
115 |
116 | if not opts.enabled or position == nil then
117 | return
118 | end
119 |
120 | -- Prevent `open_floating_preview` from closing the previous floating window
121 | -- Cache the value to restore it later
122 | local lsp_win = vim.b[bufnr].lsp_floating_preview
123 | vim.b[bufnr].lsp_floating_preview = nil
124 |
125 | -- Check if another lightbulb floating window already exists for this buffer and close it
126 | local existing_float = vim.b[bufnr].lightbulb_floating_window
127 | if existing_float and vim.api.nvim_win_is_valid(existing_float) then
128 | vim.api.nvim_win_close(existing_float, true)
129 | end
130 |
131 | -- Open the window and set highlight
132 | local sign_text = opts.text
133 | if is_code_lens(opts, position) then
134 | sign_text = opts.lens_text
135 | end
136 | local _, lightbulb_win = lsp_util.open_floating_preview({ sign_text }, "plaintext", opts.win_opts)
137 | set_win_option(lightbulb_win, "winhl", "Normal:" .. opts.hl)
138 |
139 | -- Set float transparency
140 | if opts.win_opts["winblend"] ~= nil then
141 | set_win_option(lightbulb_win, "winblend", opts.win_opts.winblend)
142 | end
143 |
144 | vim.b[bufnr].lightbulb_floating_window = lightbulb_win
145 | vim.b[bufnr].lsp_floating_preview = lsp_win
146 | end
147 |
148 | --- Update the lightbulb status text.
149 | ---
150 | ---@param opts table Partial or full configuration table. See |nvim-lightbulb-config|.
151 | ---@param position table|nil The position to update the extmark to. If nil, removes the extmark.
152 | ---@param bufnr integer The buffer to update the float in.
153 | ---
154 | ---@private
155 | local function update_status_text(opts, position, bufnr)
156 | if not opts.status_text.enabled then
157 | return
158 | end
159 |
160 | local sign_text = opts.status_text.text
161 | if is_code_lens(opts, position) then
162 | sign_text = opts.status_text.lens_text
163 | end
164 | if position == nil then
165 | vim.b[bufnr].current_lightbulb_status_text = opts.status_text.text_unavailable
166 | else
167 | vim.b[bufnr].current_lightbulb_status_text = sign_text
168 | end
169 | end
170 |
171 | -- The following is a variable designed to prevent re-entrancy in the
172 | -- update_extmark function below.
173 | local in_update_extmark = false
174 | --- Update the lightbulb extmark.
175 | ---
176 | ---@param opts table Partial or full configuration table. See |nvim-lightbulb-config|.
177 | ---@param position table|nil The position to update the extmark to. If nil, removes the extmark.
178 | ---@param bufnr integer The buffer to update the float in.
179 | ---
180 | ---@private
181 | local function update_extmark(opts, position, bufnr)
182 | -- Guard against re-entrancy.
183 | if (in_update_extmark) then
184 | return
185 | end
186 | -- Set guard against re-entrancy.
187 | in_update_extmark = true
188 |
189 | local sign_enabled = opts.sign.enabled
190 | local virt_text_enabled = opts.virtual_text.enabled
191 |
192 | -- Intended to fix an invalid reference when the buffer number is invalid
193 | -- for some unknown reason.
194 | if (vim.b[bufnr] == nil) then
195 | in_update_extmark = false
196 | return
197 | end
198 | local extmark_id = vim.b[bufnr].lightbulb_extmark
199 | if not (sign_enabled or virt_text_enabled) or position == nil then
200 | if extmark_id ~= nil then
201 | vim.api.nvim_buf_del_extmark(bufnr, LIGHTBULB_NS, extmark_id)
202 | end
203 | in_update_extmark = false
204 | return
205 | end
206 |
207 | local sign_text = opts.sign.text
208 | local virtual_text = opts.virtual_text.text
209 | if is_code_lens(opts, position) then
210 | sign_text = opts.sign.lens_text
211 | virtual_text = opts.virtual_text.lens_text
212 | end
213 |
214 | local extmark_opts = {
215 | id = extmark_id,
216 | priority = opts.priority,
217 | -- If true, breaks empty files
218 | strict = false,
219 | -- Sign configuration
220 | sign_text = sign_enabled and sign_text or nil,
221 | sign_hl_group = sign_enabled and opts.sign.hl or nil,
222 | -- Virtual text configuration
223 | virt_text = virt_text_enabled and { { virtual_text, opts.virtual_text.hl } } or nil,
224 | virt_text_pos = (virt_text_enabled and type(opts.virtual_text.pos) == "string") and opts.virtual_text.pos or nil,
225 | virt_text_win_col = (virt_text_enabled and type(opts.virtual_text.pos) == "number") and opts.virtual_text.pos
226 | or nil,
227 | hl_mode = virt_text_enabled and opts.virtual_text.hl_mode or nil,
228 | -- Number configuration
229 | number_hl_group = opts.number.enabled and opts.number.hl or nil,
230 | -- Line configuration
231 | line_hl_group = opts.line.enabled and opts.line.hl or nil,
232 | }
233 | vim.b[bufnr].lightbulb_extmark =
234 | vim.api.nvim_buf_set_extmark(bufnr, LIGHTBULB_NS, position.line, position.col + 1, extmark_opts)
235 |
236 | in_update_extmark = false
237 | end
238 |
239 | --- Handler factory to keep track of current lightbulb line.
240 | ---
241 | ---@param opts table Options passed when `update_lightbulb` is called
242 | ---@param position table Position of the cursor when lightbulb is called, like {line = 0, col = 0}
243 | ---@param bufnr number Buffer handle
244 | ---
245 | ---@private
246 | local function handler_factory(opts, position, bufnr)
247 | --- Handler for textDocument/codeAction.
248 | --- Note: This is not an |lsp-handler| because we use vim.lsp.buf_request_all and not vim.lsp.buf_request
249 | ---
250 | ---@param responses table Map of client_id:request_result.
251 | ---@private
252 | local function code_action_handler(responses)
253 | -- We received a response, so we shouldn't try to cancel it anymore
254 | vim.b[bufnr].lightbulb_lsp_cancel = nil
255 |
256 | ---@param id number The client id of the LSP server
257 | ---@param name string The client name of the LSP server
258 | ---@param result lsp.CodeAction | lsp.Command The CodeAction to filter
259 | ---@return boolean keep True to count the code action, false otherwise
260 | local filter = function(id, name, result)
261 | if opts.ignore_id[id] then
262 | return false
263 | end
264 |
265 | if opts.ignore.actions_without_kind then
266 | local found_without_kind = false
267 | for _, r in pairs(result) do
268 | if r.kind and r.kind ~= "" then
269 | found_without_kind = true
270 | break
271 | end
272 | end
273 | if not found_without_kind then
274 | return false
275 | end
276 | end
277 |
278 | for _, r in pairs(result) do
279 | if opts.filter and opts.filter(name, r) then
280 | return true
281 | end
282 | end
283 |
284 | return not opts.filter
285 | end
286 |
287 | -- Check for available code actions from all LSP server responses
288 | local has_actions = false
289 | for client_id, resp in pairs(responses) do
290 | if resp.result and not vim.tbl_isempty(resp.result) then
291 | if filter(client_id, opts.client_id_to_name[client_id], resp.result) then
292 | has_actions = true
293 | break
294 | end
295 | end
296 | end
297 |
298 | local pos = has_actions and position or nil
299 | update_extmark(opts, pos, bufnr)
300 | update_status_text(opts, pos, bufnr)
301 | update_float(opts, pos, bufnr)
302 | end
303 |
304 | return code_action_handler
305 | end
306 |
307 | ---
308 | --- Get the configured text according to lightbulb status.
309 | --- Any configuration provided overrides the defaults passed to |NvimLightbulb.setup|.
310 | ---
311 | ---@param bufnr number|nil Buffer handle. Defaults to current buffer.
312 | ---
313 | ---@usage `require('nvim-lightbulb').get_status_text()`
314 | NvimLightbulb.get_status_text = function(bufnr)
315 | bufnr = bufnr or vim.api.nvim_get_current_buf()
316 | return vim.F.npcall(vim.api.nvim_buf_get_var, bufnr, "current_lightbulb_status_text") or ""
317 | end
318 |
319 | NvimLightbulb.clear_lightbulb = function(bufnr)
320 | local extmark_id = vim.b[bufnr].lightbulb_extmark
321 | if extmark_id ~= nil then
322 | vim.api.nvim_buf_del_extmark(bufnr, LIGHTBULB_NS, extmark_id)
323 | end
324 | end
325 |
326 | ---
327 | --- Display the lightbulb according to configuration.
328 | --- Any configuration provided overrides the defaults passed to |NvimLightbulb.setup|.
329 | ---
330 | ---
331 | ---@param config table|nil Partial or full configuration table. See |nvim-lightbulb-config|.
332 | ---
333 | ---@usage `require('nvim-lightbulb').update_lightbulb({})`
334 | NvimLightbulb.update_lightbulb = function(config)
335 | local opts = lightbulb_config.build(config, false)
336 | opts.ignore_id = {}
337 | opts.client_id_to_name = {}
338 |
339 | -- Return if the filetype is ignored
340 | if vim.tbl_contains(opts.ignore.ft, vim.bo.filetype) then
341 | return
342 | end
343 |
344 | -- Key: client.name
345 | -- Value: true if ignore
346 | local ignored_clients = {}
347 | if opts.ignore.clients then
348 | for _, client in ipairs(opts.ignore.clients) do
349 | ignored_clients[client] = true
350 | end
351 | end
352 |
353 | local bufnr = vim.api.nvim_get_current_buf()
354 |
355 | -- Check for code action capability
356 | local code_action_cap_found = false
357 | for _, client in pairs(get_lsp_active_clients({ bufnr = bufnr })) do
358 | if client and supports_method(client)("textDocument/codeAction") then
359 | opts.client_id_to_name[client.id] = client.name or "Unknown Client"
360 |
361 | -- If it is ignored, add the id to the ignore table for the handler
362 | if ignored_clients[client.name] then
363 | opts.ignore_id[client.id] = true
364 | else
365 | -- Otherwise we have found a capable client
366 | code_action_cap_found = true
367 | end
368 | end
369 | end
370 | if not code_action_cap_found then
371 | return
372 | end
373 |
374 | -- Send the LSP request, canceling the previous one if necessary
375 | if vim.b[bufnr].lightbulb_lsp_cancel then
376 | -- The cancel function failing here may be due to the client no longer existing,
377 | -- the server having a bad implementation of the cancel etc.
378 | -- Failing doesn't affect the lightbulb behavior, so we can ignore the error.
379 | pcall(vim.b[bufnr].lightbulb_lsp_cancel)
380 | vim.b[bufnr].lightbulb_lsp_cancel = nil
381 | end
382 | local context = { diagnostics = get_lsp_line_diagnostics() }
383 | context.only = opts.action_kinds
384 |
385 | local params = lsp_util.make_range_params(0, "utf-8")
386 | params.context = context
387 |
388 | local position = {
389 | line = params.range.start.line,
390 | col = params.range.start.character,
391 | }
392 | vim.b[bufnr].lightbulb_lsp_cancel = vim.F.npcall(
393 | vim.lsp.buf_request_all,
394 | bufnr,
395 | "textDocument/codeAction",
396 | params,
397 | handler_factory(opts, position, bufnr)
398 | )
399 | end
400 |
401 | ---
402 | --- Display debug information related to nvim-lightbulb.
403 | --- Prints information about:
404 | --- - The current configuration
405 | --- - LSP servers found, ignored, supporting code actions...
406 | --- - Any code actions at the current location along with their code action kind
407 | ---
408 | ---@param config table|nil Partial or full configuration table. See |nvim-lightbulb-config|.
409 | ---
410 | ---@usage `require('nvim-lightbulb').debug({})`
411 | NvimLightbulb.debug = function(config)
412 | local opts = lightbulb_config.build(config, false)
413 | opts.ignore_id = {}
414 |
415 | local chunks = {}
416 | local function append(str, hl)
417 | table.insert(chunks, { str, hl })
418 | end
419 | local function warn(str, hl)
420 | append("! ", "DiagnosticWarn")
421 | append(str, hl)
422 | end
423 | local function info(str, hl)
424 | append("i ", "DiagnosticInfo")
425 | append(str, hl)
426 | end
427 |
428 | append("[")
429 | append("Configuration", "Special")
430 | append("]\n")
431 | vim.list_extend(chunks, lightbulb_config.pretty_format(opts))
432 | append("\n")
433 |
434 | append("\n[")
435 | append("Code Actions", "Special")
436 | append("]\n")
437 |
438 | local run_code_actions = true
439 |
440 | -- Return if the filetype is ignored
441 | if vim.tbl_contains(opts.ignore.ft, vim.bo.filetype) then
442 | warn("Filetype ignored: ")
443 | append(vim.bo.filetype, "DiagnosticWarn")
444 | append("\n")
445 | run_code_actions = false
446 | end
447 |
448 | -- Key: client.name
449 | -- Value: true if ignore
450 | local ignored_clients = {}
451 | if opts.ignore.clients then
452 | for _, client in ipairs(opts.ignore.clients) do
453 | ignored_clients[client] = true
454 | end
455 | end
456 |
457 | -- Key: client.id
458 | -- Value: client.name
459 | local client_id_to_name = {}
460 |
461 | local bufnr = vim.api.nvim_get_current_buf()
462 |
463 | -- Check for code action capability
464 | local no_code_action_servers = {}
465 | local code_action_servers = {}
466 | local ignored_servers = {}
467 |
468 | for _, client in pairs(get_lsp_active_clients({ bufnr = bufnr })) do
469 | if client and supports_method(client)("textDocument/codeAction") then
470 | client_id_to_name[client.id] = client.name
471 |
472 | -- If it is ignored, add the id to the ignore table for the handler
473 | if ignored_clients[client.name] then
474 | opts.ignore_id[client.id] = true
475 | if #ignored_servers > 0 then
476 | table.insert(ignored_servers, { ", " })
477 | end
478 | table.insert(ignored_servers, { client.name })
479 | else
480 | -- Otherwise we have found a capable client
481 | if #code_action_servers > 0 then
482 | table.insert(code_action_servers, { ", " })
483 | end
484 | table.insert(code_action_servers, { client.name })
485 | end
486 | else
487 | if #no_code_action_servers > 0 then
488 | table.insert(no_code_action_servers, { ", " })
489 | end
490 | table.insert(no_code_action_servers, { client.name })
491 | end
492 | end
493 |
494 | if not vim.tbl_isempty(no_code_action_servers) then
495 | info(" No code action support: ")
496 | vim.list_extend(chunks, no_code_action_servers)
497 | append("\n")
498 | end
499 |
500 | if vim.tbl_isempty(code_action_servers) then
501 | warn("No allowed servers with code action support found\n")
502 | run_code_actions = false
503 | else
504 | info("With code action support: ")
505 | vim.list_extend(chunks, code_action_servers)
506 | append("\n")
507 | end
508 |
509 | if not vim.tbl_isempty(ignored_servers) then
510 | info("With support but ignored: ")
511 | vim.list_extend(chunks, ignored_servers)
512 | append("\n")
513 | end
514 |
515 | -- Send the LSP request
516 | local context = { diagnostics = get_lsp_line_diagnostics() }
517 | context.only = opts.action_kinds
518 |
519 | local params = lsp_util.make_range_params(0, "utf-8")
520 | params.context = context
521 |
522 | --- Handler for textDocument/codeAction.
523 | --- Note: This is not an |lsp-handler| because we use vim.lsp.buf_request_all and not vim.lsp.buf_request
524 | ---
525 | ---@param responses table Map of client_id:request_result.
526 | ---@private
527 | local function code_action_handler(responses)
528 | local has_actions = false
529 |
530 | for client_id, resp in pairs(responses) do
531 | if not opts.ignore_id[client_id] and resp.result and not vim.tbl_isempty(resp.result) then
532 | local client_name = client_id_to_name[client_id] or "Unknown Client"
533 | append("\n")
534 | append(client_name, "Title")
535 | append("\n")
536 |
537 | for idx, r in pairs(resp.result) do
538 | local has_kind = r.kind and r.kind ~= ""
539 | append(string.format("%d. %s", idx, r.title))
540 | if has_kind then
541 | append(" " .. r.kind, "Comment")
542 | else
543 | append(" (no kind)", "Comment")
544 | end
545 |
546 | if opts.ignore.actions_without_kind and not has_kind then
547 | append(" [Ignored (actions_without_kind)]", "Error")
548 | elseif opts.filter and not opts.filter(client_name, r) then
549 | append(" [Ignored (filter)]", "Error")
550 | else
551 | has_actions = true
552 | end
553 |
554 | append("\n")
555 | end
556 | end
557 | end
558 |
559 | if not has_actions then
560 | append("\n")
561 | warn("No valid code actions found")
562 | end
563 |
564 | append("\n")
565 | vim.api.nvim_echo(chunks, true, {})
566 | end
567 |
568 | if run_code_actions then
569 | vim.lsp.buf_request_all(bufnr, "textDocument/codeAction", params, code_action_handler)
570 | else
571 | vim.api.nvim_echo(chunks, true, {})
572 | end
573 | end
574 |
575 | return NvimLightbulb
576 |
--------------------------------------------------------------------------------