Open6

【Neovim】`textDocument/documentHighlight`を怒らせずに、カーソル下の変数名をハイライトしたい!

airRnotairRnot

vim初心者の備忘録シリーズ

NeovimのファイラーはFernを使用しているのですが、Fern内でrenameをしようとすると、

CursorHoldI Autocommands for "*" の処理中にエラーが検出されました:
method textDocument/documentHighlight is not supported by any of the servers registered for the current buffer

と怒られることに気がつきました。

色々探っていると、options.luaで以下のような設定をしていたのが原因だとわかりました。

options.lua
-- enable highlight
vim.cmd [[
         set updatetime=500
         augroup lsp_document_highlight
         autocmd!
             autocmd CursorHold,CursorHoldI * lua vim.lsp.buf.document_highlight()
             autocmd CursorMoved,CursorMovedI * lua vim.lsp.buf.clear_references()
         augroup END
        ]]

詳しいことはわかりませんが、Fernのrename画面のfiletypeはfern-replacerで、これがtextDocument/documentHighlightに対応していない故のエラーかなと思いました。

airRnotairRnot

この時点のlspの設定

mason.nvim, nvim-lspconfig, null-ls.nvim, lazy.nvimを使用しています。

lsp.lua
return {
    { "neovim/nvim-lspconfig", event = { "BufReadPre", "BufNewFile" } },
    {
        "williamboman/mason-lspconfig.nvim",
        dependencies = { "williamboman/mason.nvim", "neovim/nvim-lspconfig", "hrsh7th/cmp-nvim-lsp" },
        event = { "BufReadPre", "BufNewFile" },
        config = function()
            require("mason-lspconfig").setup_handlers {
                function(server_name)
                    local opts = {
                        capabilities = require("cmp_nvim_lsp").default_capabilities(),
                    }

                    if server_name == "lua_ls" then
                        opts.settings = {
                            Lua = {
                                diagnostics = { globals = { "vim" } },
                                format = { enable = false }, -- To prevent indent conflicts with stylua
                            },
                        }
                    end

                    -- print("Setting up " .. server_name .. " with mason")
                    require("lspconfig")[server_name].setup(opts)
                end,
            }
        end,
    },
    {
        "jose-elias-alvarez/null-ls.nvim",
        event = { "BufReadPre", "BufNewFile" },
        config = function()
            local null_ls = require "null-ls"
            local augroup = vim.api.nvim_create_augroup("LspFormatting", {})
            null_ls.setup {

                -- If you install something in Mason, you should add it here. Refer to "https://github.com/jose-elias-alvarez/null-ls.nvim/blob/main/doc/BUILTINS.md" for more information.
                sources = {
                    null_ls.builtins.formatting.stylua,
                },

                on_attach = function(client, bufnr)
                    if client.supports_method "textDocument/formatting" then
                        vim.api.nvim_clear_autocmds { group = augroup, buffer = bufnr }
                        vim.api.nvim_create_autocmd("BufWritePre", {
                            group = augroup,
                            buffer = bufnr,
                            callback = function()
                                -- on 0.8, you should use vim.lsp.buf.format({ bufnr = bufnr }) instead
                                -- on later neovim version, you should use vim.lsp.buf.format({ async = false }) instead
                                vim.lsp.buf.format { async = false }
                            end,
                        })
                    end
                end,

                diagnostics_format = "#{m} (#{s}: #{c})",
            }
        end,
    },
    {
        "jay-babu/mason-null-ls.nvim",
        dependencies = {
            "williamboman/mason.nvim",
            "nvim-lua/plenary.nvim",
            "jose-elias-alvarez/null-ls.nvim",
        },
        event = { "BufReadPre", "BufNewFile" },
        config = function()
            require("mason-null-ls").setup {
                automatic_setup = true,
                handlers = {},
            }
        end,
    },
}

ここで、この部分に注目してみてください。

            null_ls.setup {

                -- If you install something in Mason, you should add it here. Refer to "https://github.com/jose-elias-alvarez/null-ls.nvim/blob/main/doc/BUILTINS.md" for more information.
                sources = {
                    null_ls.builtins.formatting.stylua,
                },

                on_attach = function(client, bufnr)
                    if client.supports_method "textDocument/formatting" then
                        vim.api.nvim_clear_autocmds { group = augroup, buffer = bufnr }
                        vim.api.nvim_create_autocmd("BufWritePre", {
                            group = augroup,
                            buffer = bufnr,
                            callback = function()
                                -- on 0.8, you should use vim.lsp.buf.format({ bufnr = bufnr }) instead
                                -- on later neovim version, you should use vim.lsp.buf.format({ async = false }) instead
                                vim.lsp.buf.format { async = false }
                            end,
                        })
                    end
                end,

                diagnostics_format = "#{m} (#{s}: #{c})",
            }

if client.supports_method "textDocument/formatting" then?

……解決策が見えた気がします。

airRnotairRnot

if client.supports_method "textDocument/formatting" thenみたいなことができるなら、if client.supports_method "textDocument/documentHighlight" thenもできるはずです。

しかし、null-ls内でやっても仕方がないので、lspconfig内でやります。

airRnotairRnot

修正後のlsp.lua

return {
    { "neovim/nvim-lspconfig", event = { "BufReadPre", "BufNewFile" } },
    {
        "williamboman/mason-lspconfig.nvim",
        dependencies = { "williamboman/mason.nvim", "neovim/nvim-lspconfig", "hrsh7th/cmp-nvim-lsp" },
        event = { "BufReadPre", "BufNewFile" },
        config = function()
            require("mason-lspconfig").setup_handlers {
                function(server_name)
                    local opts = {
                        capabilities = require("cmp_nvim_lsp").default_capabilities(),
                        on_attach = function(client, bufnr)
                            if client.supports_method "textDocument/documentHighlight" then
                                local lsp_document_highlight = vim.api.nvim_create_augroup("lsp_document_highlight", {})
                                vim.api.nvim_create_autocmd({ "CursorHold", "CursorHoldI" }, {
                                    group = lsp_document_highlight,
                                    buffer = bufnr,
                                    callback = function()
                                        vim.lsp.buf.document_highlight()
                                    end,
                                })
                                vim.api.nvim_create_autocmd({ "CursorMoved", "CursorMovedI" }, {
                                    group = lsp_document_highlight,
                                    buffer = bufnr,
                                    callback = function()
                                        vim.lsp.buf.clear_references()
                                    end,
                                })
                            end
                        end,
                    }

                    if server_name == "lua_ls" then
                        opts.settings = {
                            Lua = {
                                diagnostics = { globals = { "vim" } },
                                format = { enable = false }, -- To prevent indent conflicts with stylua
                            },
                        }
                    end

                    -- print("Setting up " .. server_name .. " with mason")
                    require("lspconfig")[server_name].setup(opts)
                end,
            }
        end,
    },
    {
        "jose-elias-alvarez/null-ls.nvim",
        event = { "BufReadPre", "BufNewFile" },
        config = function()
            local null_ls = require "null-ls"
            local augroup = vim.api.nvim_create_augroup("LspFormatting", {})
            null_ls.setup {

                -- If you install something in Mason, you should add it here. Refer to "https://github.com/jose-elias-alvarez/null-ls.nvim/blob/main/doc/BUILTINS.md" for more information.
                sources = {
                    null_ls.builtins.formatting.stylua,
                },

                on_attach = function(client, bufnr)
                    if client.supports_method "textDocument/formatting" then
                        vim.api.nvim_clear_autocmds { group = augroup, buffer = bufnr }
                        vim.api.nvim_create_autocmd("BufWritePre", {
                            group = augroup,
                            buffer = bufnr,
                            callback = function()
                                -- on 0.8, you should use vim.lsp.buf.format({ bufnr = bufnr }) instead
                                -- on later neovim version, you should use vim.lsp.buf.format({ async = false }) instead
                                vim.lsp.buf.format { async = false }
                            end,
                        })
                    end
                end,

                diagnostics_format = "#{m} (#{s}: #{c})",
            }
        end,
    },
    {
        "jay-babu/mason-null-ls.nvim",
        dependencies = {
            "williamboman/mason.nvim",
            "nvim-lua/plenary.nvim",
            "jose-elias-alvarez/null-ls.nvim",
        },
        event = { "BufReadPre", "BufNewFile" },
        config = function()
            require("mason-null-ls").setup {
                automatic_setup = true,
                handlers = {},
            }
        end,
    },
}

これで無事に怒られずにハイライトを有効にできました。
ちなみに、

set updatetime=500

で、ハイライトがかかるまでの時間を調節できます。

airRnotairRnot

この書き方だとon_attachが固定されてしまうので、こういう感じにしました。

return {
    { "neovim/nvim-lspconfig", event = { "BufReadPre", "BufNewFile" } },
    {
        "williamboman/mason-lspconfig.nvim",
        dependencies = { "williamboman/mason.nvim", "neovim/nvim-lspconfig", "hrsh7th/cmp-nvim-lsp" },
        event = { "BufReadPre", "BufNewFile" },
        config = function()
            require("mason-lspconfig").setup_handlers {
                function(server_name)
                    local highlight_variable = function(client, bufnr)
                        if client.supports_method "textDocument/documentHighlight" then
                            local lsp_document_highlight = vim.api.nvim_create_augroup("lsp_document_highlight", {})
                            vim.api.nvim_create_autocmd({ "CursorHold", "CursorHoldI" }, {
                                group = lsp_document_highlight,
                                buffer = bufnr,
                                callback = function()
                                    vim.lsp.buf.document_highlight()
                                end,
                            })
                            vim.api.nvim_create_autocmd({ "CursorMoved", "CursorMovedI" }, {
                                group = lsp_document_highlight,
                                buffer = bufnr,
                                callback = function()
                                    vim.lsp.buf.clear_references()
                                end,
                            })
                        end
                    end

                    local opts = {
                        capabilities = require("cmp_nvim_lsp").default_capabilities(),
                        on_attach = function(client, bufnr)
                            highlight_variable(client, bufnr)
                        end,
                    }

                    if server_name == "lua_ls" then
                        opts.settings = {
                            Lua = {
                                diagnostics = { globals = { "vim" } },
                                format = { enable = false }, -- To prevent indent conflicts with stylua
                            },
                        }
                    end

                    if server_name == "pylsp" then
                        opts.settings = {
                            pylsp = {
                                plugins = {
                                    pycodestyle = {
                                        maxLineLength = 88,
                                    },
                                },
                            },
                        }
                    end

                    if server_name == "eslint" then
                        opts.on_attach = function(client, bufnr)
                            highlight_variable(client, bufnr)
                            vim.api.nvim_create_autocmd("BufWritePre", {
                                buffer = bufnr,
                                command = "EslintFixAll",
                            })
                        end
                    end

                    -- print("Setting up " .. server_name .. " with mason")
                    require("lspconfig")[server_name].setup(opts)
                end,
            }
        end,
    },
    {
        "jose-elias-alvarez/null-ls.nvim",
        event = { "BufReadPre", "BufNewFile" },
        config = function()
            local null_ls = require "null-ls"
            local augroup = vim.api.nvim_create_augroup("LspFormatting", {})
            null_ls.setup {

                -- If you install something in Mason, you should add it here. Refer to "https://github.com/jose-elias-alvarez/null-ls.nvim/blob/main/doc/BUILTINS.md" for more information.
                sources = {
                    -- lua
                    null_ls.builtins.formatting.stylua,

                    -- python
                    null_ls.builtins.diagnostics.flake8.with {
                        extra_args = { "--max-line-length=88" },
                    },
                    null_ls.builtins.formatting.black,
                    null_ls.builtins.formatting.isort,

                    -- javascript
                    null_ls.builtins.formatting.prettier.with {
                        prefer_local = "node_modules/.bin",
                    },
                },

                on_attach = function(client, bufnr)
                    if client.supports_method "textDocument/formatting" then
                        vim.api.nvim_clear_autocmds { group = augroup, buffer = bufnr }
                        vim.api.nvim_create_autocmd("BufWritePre", {
                            group = augroup,
                            buffer = bufnr,
                            callback = function()
                                -- on 0.8, you should use vim.lsp.buf.format({ bufnr = bufnr }) instead
                                -- on later neovim version, you should use vim.lsp.buf.format({ async = false }) instead
                                vim.lsp.buf.format { async = false }
                            end,
                        })
                    end
                end,

                diagnostics_format = "#{m} (#{s}: #{c})",
            }
        end,
    },
    {
        "jay-babu/mason-null-ls.nvim",
        dependencies = {
            "williamboman/mason.nvim",
            "nvim-lua/plenary.nvim",
            "jose-elias-alvarez/null-ls.nvim",
        },
        event = { "BufReadPre", "BufNewFile" },
        config = function()
            require("mason-null-ls").setup {
                automatic_setup = true,
                handlers = {},
            }
        end,
    },
}

airRnotairRnot

最近思ったのですが、この設定まったくダメですね。
結局callbackが実行されるときにtextDocument/documentHighlightがsupportされているかを検証しないので普通に怒られます。

うーん、解決策が思いつかない。