Skip to content

Behind the scenes

This is what require('lsp-zero') does, explained with code:

lua
--- btw, the user can disable all of this. or just parts of it.

---
-- lsp-zero applies cmp_nvim_lsp's capabilities automatically 
-- to each language server configured by lspconfig
---
local function ensure_capabilities()
  local util = require('lspconfig.util')

  util.on_setup = util.add_hook_after(
    util.on_setup,
    function(config, user_config)
      config.capabilities = vim.tbl_deep_extend(
        'force',
        config.capabilities,
        require('cmp_nvim_lsp').default_capabilities(),
        vim.tbl_get(user_config, 'capabilities') or {}
      )
    end
  )
end

---
-- since the user can load the plugins in whatever order they want,
-- lsp-zero only makes sure the "essential" options for nvim-cmp are in place.
---
local function ensure_cmp_works()
  local module_cache_empty = package.loaded['cmp'] == nil
  local ok, cmp = pcall(require, 'cmp')

  local base = {
    sources = {
      {name = 'nvim_lsp'}
    },
    snippet = {
      expand = function(args)
        require('luasnip').lsp_expand(args.body)
      end,
    },
  }

  if ok and module_cache_empty then
    base.mapping = cmp.mapping.preset.insert({})
    cmp.setup(base)
    return
  end

  local make_new = function()
    local c = require('cmp')
    local cmp_config = c.get_config()
    local new_config = {}

    if vim.tbl_isempty(cmp_config.sources) then
      new_config.sources = base.sources
    end

    if vim.tbl_isempty(cmp_config.mapping) then
      new_config.mapping = c.mapping.preset.insert({})
    end

    local current_expand = cmp_config.snippet.expand
    local lsp_expand = base.snippet.expand

    new_config.snippet = {
      expand = function(args)
        local ok = pcall(current_expand, args)
        if not ok then
          current_expand = lsp_expand
          lsp_expand(args)
        end
      end,
    }

    return new_config
  end

  if ok and vim.g.loaded_cmp then
    cmp.setup(make_new())
    return
  end

  vim.api.nvim_create_autocmd('User', {
    pattern = 'CmpReady',
    once = true,
    callback = function() require('cmp').setup(make_new()) end,
  })
end

---
-- make some improvements to the ui
---
local function ui_settings()
  local border_style = 'rounded'

  vim.lsp.handlers['textDocument/hover'] = vim.lsp.with(
    vim.lsp.handlers.hover,
    {border = border_style}
  )

  vim.lsp.handlers['textDocument/signatureHelp'] = vim.lsp.with(
    vim.lsp.handlers.signature_help,
    {border = border_style}
  )

  vim.diagnostic.config({
    float = {border = border_style}
  })

  -- the default value for the signcolumn is 'auto'
  -- and that's annoying. don't believe me?
  -- use vim.opt.signcolumn = 'auto'
  vim.opt.signcolumn = 'yes'
end

---
-- this is what require('lsp-zero') does
---
ensure_capabilities()
ensure_cmp_works()
ui_settings()

---
-- now that all the boilerplate is done
-- the user can setup the language servers
---
require('lspconfig').lua_ls.setup({})

---
-- and also configure nvim-cmp however they want.
---
local cmp = require('cmp')
cmp.setup({
  mapping = cmp.mapping.preset.insert({
    -- use the `Enter` key to confirm completion
    ['<CR>'] = cmp.mapping.confirm({select = false}),

    -- Ctrl+Space to trigger completion menu
    ['<C-Space>'] = cmp.mapping.complete(),

    -- Scroll up and down in the completion documentation
    ['<C-u>'] = cmp.mapping.scroll_docs(-4),
    ['<C-d>'] = cmp.mapping.scroll_docs(4),
  })
})