├── .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 | nvim-lightbulb 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 | --------------------------------------------------------------------------------