Managing LSPs in Neovim: Enable/Disable for the Entire Session

Salih Dhaifullah - Oct 31 - - Dev Community

Neovim’s built-in LSP functionality provides powerful coding assistance. However, there may be times when you want to disable LSP features entirely for the duration of your session. This guide will show you how to toggle LSPs on and off globally until you close Neovim.

Why Toggle LSPs?

  • Performance Issues: In larger projects, LSPs can consume resources, leading to slower performance.

  • Distraction-Free Coding: You may want to focus on code without the assistance of LSP features such as autocompletion or linting.

  • Testing Changes: Disabling LSPs can help you test specific changes without interference.

Code Implementation

-- Initialize a flag to toggle LSPs on or off
local lsp_enabled = true
-- Store buffers attached to each LSP client
local attached_buffers_by_client = {}
-- Store configurations for each LSP client
local client_configs = {}

-- Store a reference to the original buf_attach_client function
local original_buf_attach_client = vim.lsp.buf_attach_client

-- Function to add a buffer to the client's buffer table
local function add_buf(client_id, buf)
    if not attached_buffers_by_client[client_id] then
        attached_buffers_by_client[client_id] = {}
    end

    -- Check if the buffer is already in the list
    local exists = false
    for _, value in ipairs(attached_buffers_by_client[client_id]) do
        if value == buf then
            exists = true
            break
        end
    end

    -- Add the buffer if it doesn’t already exist in the client’s list
    if not exists then
        table.insert(attached_buffers_by_client[client_id], buf)
    end
end

-- Middleware function to control LSP client attachment to buffers
-- Prevents LSP client from reattaching if LSPs are disabled
vim.lsp.buf_attach_client = function(bufnr, client_id)
    if not lsp_enabled then
        -- Cache client configuration if not already stored
        if not client_configs[client_id] then
            local client_config = vim.lsp.get_client_by_id(client_id)
            client_configs[client_id] = (client_config and client_config.config) or {}
        end

        -- Add buffer to client’s attached buffer list and stop the client
        add_buf(client_id, bufnr)
        vim.lsp.stop_client(client_id)

        return false                                    -- Indicate the client should not attach
    end
    return original_buf_attach_client(bufnr, client_id) -- Use the original attachment method if enabled LSP
end

-- Update state with new client IDs after a toggle
local function update_clients_ids(ids_map)
    local new_attached_buffers_by_client = {}
    local new_client_configs = {}

    -- Map each client ID to its new ID and carry over configurations
    for client_id, buffers in pairs(attached_buffers_by_client) do
        local new_id = ids_map[client_id]
        new_attached_buffers_by_client[new_id] = buffers
        new_client_configs[new_id] = client_configs[client_id]
    end

    attached_buffers_by_client = new_attached_buffers_by_client -- Update global attached buffer table
    client_configs = new_client_configs                         -- Update global client config table
end

-- Stops the client, waiting up to 5 seconds; force quits if needed
local function client_stop(client)
    vim.lsp.stop_client(client.id, false)

    local timer = vim.uv.new_timer() -- Create a timer
    local max_attempts = 50          -- Set max attempts to check if stopped
    local attempts = 0               -- Track the number of attempts

    timer:start(100, 100, vim.schedule_wrap(function()
        attempts = attempts + 1

        if client.is_stopped() then -- Check if the client is stopped
            timer:stop()
            timer:close()
            vim.diagnostic.reset()               -- Reset diagnostics for the client
        elseif attempts >= max_attempts then     -- If max attempts reached
            vim.lsp.stop_client(client.id, true) -- Force stop the client
            timer:stop()
            timer:close()
            vim.diagnostic.reset() -- Reset diagnostics for the client
        end
    end))
end

-- Toggle LSPs on or off, managing client states and attached buffers
local function toggle_lsp()
    if lsp_enabled then                 -- If LSP is currently enabled, disable it
        client_configs = {}             -- Clear client configurations
        attached_buffers_by_client = {} -- Clear attached buffers

        -- Loop through all active LSP clients
        for _, client in ipairs(vim.lsp.get_clients()) do
            client_configs[client.id] = client.config -- Cache client config

            -- Loop through all buffers attached to the client
            for buf, _ in pairs(client.attached_buffers) do
                add_buf(client.id, buf)                   -- Add buffer to the client’s buffer table
                vim.lsp.buf_detach_client(buf, client.id) -- Detach the client from the buffer
            end

            client_stop(client) -- Stop the client
        end

        print("LSPs Disabled")
    else -- If LSP is currently disabled, enable it
        local new_ids = {}

        -- Reinitialize clients with previous configurations
        for client_id, buffers in pairs(attached_buffers_by_client) do
            local client_config = client_configs[client_id]                -- Retrieve client config
            local new_client_id, err = vim.lsp.start_client(client_config) -- Start client with config

            new_ids[client_id] = new_client_id                             -- Map old client ID to new client ID

            if err then                                                    -- Notify if there was an error starting the client
                vim.notify(err, vim.log.levels.WARN)
                return nil
            end

            -- Reattach buffers to the newly started client
            for _, buf in ipairs(buffers) do
                original_buf_attach_client(buf, new_client_id)
            end
        end

        update_clients_ids(new_ids) -- Update client IDs
        print("LSPs Enabled")       -- Notify that LSPs are enabled
    end

    lsp_enabled = not lsp_enabled -- Toggle the LSP enabled flag
end

-- Set key mapping to toggle LSP on or off with <leader>tl
vim.keymap.set("n", "<leader>tl", toggle_lsp)
Enter fullscreen mode Exit fullscreen mode

Understanding LspStop

While the :LspStop command can stop LSP clients for the current buffer, it has some limitations. Here’s how it behaves:

  • Stop Current Buffer: Executing :LspStop detaches the LSP client from only the currently active buffer. This means that LSP features will be disabled only for that buffer.

  • Reopening the Buffer: If you close and reopen the buffer, the LSP client automatically restarts and reattaches. This behavior is standard in Neovim, ensuring that once you access a buffer again, the language server is reactivated.

  • Opening New Buffers: Similarly, if you open a new buffer associated with an LSP client, Neovim will trigger the LSP to start again. Thus, even if you previously stopped LSP for one buffer, any new buffer supported by a language server will reactivate the client.

.
Terabox Video Player