Personal Log »

Python and LSP (II)

So shortly after I posted here my notes about my Python with LSP setup, I decided to give the neovim native LSP support a go. Mainly because I found that vim-lsc didn’t understand some of the messages Python LSP Server was sending and, although it looks like that didn’t have any effect, the error reporting was a bit distracting –even when I tried to disable it–. So I thought, how difficult would be using neovim’s built-in support?

Very easy, actually. I guess I’m not the only one not willing to burn all bridges in case I have to go back to use vim –that has been around long enough to make me think it will be there for ever–. There is no reason to think that neovim will disappear, or go in a direction I don’t fancy. My vim-lsc setup did work with vim, and in some cases I don’t have neovim available (or at least not at the minimal version required to enjoy all this).

But the fact is that I’m already enjoying some neovim-only features, like Telescope, so let’s go with full in with neovim for my LSP needs –at the end of the day, it is what I use for development–.

The changes to my previous configuration are actually very simple. First, I removed the vim-lsc plugin and added nvim-lspconfig, wrapped on a condition, so it doesn’t load if I use vim or the neovim version is not high enough:

if has('nvim-0.6.1')
    " lsp
    Plug 'neovim/nvim-lspconfig'

That should make vim –and older neovim– load fine.

Then, in my init.vim for neovim, I added an include to the file that will have the LSP specific configuration –and now I realise it should be wrapped on a version check, as well–. This doesn’t include my metals setup because nvim-metals doesn’t use the nvim-lspconfig framework –I need to investigate this, actually–.

I decided to include the configuration in lua instead of vimscript, because it is readable, and because the minimal example is using that language.

For full reference –for my future self–, this is the configuration:

-- for nvim lsp support

-- Mappings.
-- See `:help vim.diagnostic.*` for documentation on any of the below functions
local opts = { noremap=true, silent=true }
vim.api.nvim_set_keymap('n', '<leader>d', '<cmd>lua vim.diagnostic.open_float()<CR>', opts)
vim.api.nvim_set_keymap('n', '[d', '<cmd>lua vim.diagnostic.goto_prev()<CR>', opts)
vim.api.nvim_set_keymap('n', ']d', '<cmd>lua vim.diagnostic.goto_next()<CR>', opts)
vim.api.nvim_set_keymap('n', 'da', '<cmd>lua vim.diagnostic.setloclist()<CR>', opts)

-- Use an on_attach function to only map the following keys
-- after the language server attaches to the current buffer
local on_attach = function(client, bufnr)
  -- Enable completion triggered by <c-x><c-o>
  vim.api.nvim_buf_set_option(bufnr, 'omnifunc', 'v:lua.vim.lsp.omnifunc')

  -- Mappings.
  -- See `:help vim.lsp.*` for documentation on any of the below functions
  vim.api.nvim_buf_set_keymap(bufnr, 'n', '<C-]>', '<cmd>lua vim.lsp.buf.definition()<CR>', opts)
  vim.api.nvim_buf_set_keymap(bufnr, 'n', 'K', '<cmd>lua vim.lsp.buf.hover()<CR>', opts)
  vim.api.nvim_buf_set_keymap(bufnr, 'n', 'gi', '<cmd>lua vim.lsp.buf.implementation()<CR>', opts)
  vim.api.nvim_buf_set_keymap(bufnr, 'n', 'gsh', '<cmd>lua vim.lsp.buf.signature_help()<CR>', opts)
  vim.api.nvim_buf_set_keymap(bufnr, 'n', '<leader>rn', '<cmd>lua vim.lsp.buf.rename()<CR>', opts)
  vim.api.nvim_buf_set_keymap(bufnr, 'n', '<leader>ca', '<cmd>lua vim.lsp.buf.code_action()<CR>', opts)
  vim.api.nvim_buf_set_keymap(bufnr, 'n', 'gr', '<cmd>lua vim.lsp.buf.references()<CR>', opts)
  vim.api.nvim_buf_set_keymap(bufnr, 'n', '<leader>F', '<cmd>lua vim.lsp.buf.formatting()<CR>', opts)

local signs = { Error = "🔥", Warn = "⚠️ ", Hint = "✨", Info = "ℹ️ " }
for type, icon in pairs(signs) do
  local hl = "DiagnosticSign" .. type
  vim.fn.sign_define(hl, { text = icon, texthl = hl, numhl = hl })

-- Use a loop to conveniently call 'setup' on multiple servers and
-- map buffer local keybindings when the language server attaches
local servers = { 'pylsp' }
for _, lsp in pairs(servers) do
  require('lspconfig')[lsp].setup {
    on_attach = on_attach,
    flags = {
      -- This will be the default in neovim 0.7+
      debounce_text_changes = 150,
    -- FIXME: shouldn't this be only for pylsp?
    settings = {
        pylsp = {
            plugins = {
                pylsp_mypy = {
                    enabled = true,
                    live_move = false

Which is the suggested minimal configuration, plus the keybindings I use in metals –and I’m used to them–, plus a couple of small things, that affect my metals setup as well –e.g. the icons in the gutter to signal errors, warnings, etc; also something I need to investigate further–.

And that’s all, really. Seems to work fine, and all the noise from vim-lsc seems to be gone. I don’t know if the native neovim LSP support is doing anything differently, but at least is not reporting errors, so I guess… if I don’t know about it, it is perfectly fine.

This is still a bit work in progress and there are things I still don’t fully understand, but I will get there!

Would you like to discuss the post? You can send me an email!