My favorite editor is always vim and even better is its recent revamp, the neovim. When comparing neovim to a modern IDE, there are a lot of programming support features missing but none of the IDEs can run over SSH in text screen. To make neovim one step closer to IDEs, we can add languages server protocol support to it. Then 80% of the time you won’t find any need for other IDE features.
Language server protocol is simply speaking a language-agnostic syntax analyzer. An editor supporting LSP can manage all languages the same way. A new language to support simply means to get another backend plugin for the LSP server.
Setting up LSP for neovim is only a few steps:
- Install lspconfig to neovim from https://github.com/neovim/nvim-lspconfig
- Install a LSP server locally
- Connect them
In detail.
First install Python LSP server with
pip install 'python-lsp-server[all]'
there are other LSP server as well, e.g., Node.js or C++. See https://langserver.org/
The [all]
part is to get all options, including rope, pylint, flake8, etc. By
default this package only use Jedi.
Next, edit neovim config to load the LSP plugin. For example, I am using Packer
so in ~/.config/nvim/lua/plugins.lua
, add
local function plugins(use)
...
-- add this
use { "neovim/lsp-config" }
...
end
...
package.startup(plugins)
After that, we should run :PackerInstall
in neovim to get this installed. And then, set up the init to load the LSP, e.g., at ~/.config/nvim/init.lua
,
require("lspconfig").pylsp.setup()
That’s all you need.
But to make good use of the LSP server, we need to hook up some vi key bindings
to LSP functions. Some suggested key bindings are available on the readme of
https://github.com/neovim/nvim-lspconfig and the below is what I did, editing
~/.config/nvim/after/plugin/autocmds.lua
, and limited them to buffers editing
Python files:
local api = vim.api
-- ......
-- for LSP
local function lspkeys()
-- C-X C-O for completion
vim.opt_local.omnifunc = "v:lua.vim.lsp.omnifunc"
-- gq to format code
vim.opt_local.formatexpr = "v:lua.vim.lsp.formatexpr"
-- C-], C-W ], C-W } for jump to definitions
vim.opt_local.tagfunc = "v:lua.vim.lsp.tagfunc"
api.nvim_buf_set_keymap(0, "n", "gD", "<cmd>lua vim.lsp.buf.declaration()<CR>", { silent = true })
api.nvim_buf_set_keymap(0, "n", "gd", "<cmd>lua vim.lsp.buf.definition()<CR>", { silent = true })
api.nvim_buf_set_keymap(0, "n", "K", "<cmd>lua vim.lsp.buf.hover()<CR>", { silent = true })
api.nvim_buf_set_keymap(0, "n", "gi", "<cmd>lua vim.lsp.buf.implementation()<CR>", { silent = true })
api.nvim_buf_set_keymap(0, "n", "<C-k>", "<cmd>lua vim.lsp.buf.signature_help()<CR>", { silent = true })
api.nvim_buf_set_keymap(0, "n", "<space>D", "<cmd>lua vim.lsp.buf.type_definition()<CR>", { silent = true })
api.nvim_buf_set_keymap(0, "n", "<space>rn","<cmd>lua vim.lsp.buf.rename()<CR>", { silent = true })
api.nvim_buf_set_keymap(0, "n", "<space>ca","<cmd>lua vim.lsp.buf.code_action()<CR>", { silent = true })
api.nvim_buf_set_keymap(0, "n", "gr", "<cmd>lua vim.lsp.buf.references()<CR>", { silent = true })
api.nvim_buf_set_keymap(0, "n", "<space>f", "<cmd>lua vim.lsp.buf.format({async=true})<CR>", { silent = true })
api.nvim_buf_set_keymap(0, "n", "gS", "<cmd>lua vim.lsp.buf.document_symbol()<CR>", { silent = true })
api.nvim_buf_set_keymap(0, "n", "gW", "<cmd>lua vim.lsp.buf.workspace_symbol()<CR>",{ silent = true })
end
api.nvim_create_autocmd("FileType", { pattern = "python", callback = lspkeys })
The function name should tell you what the keys are doing.
Moreover, I usually use line width of 100 characters. I would fine-tune the LSP
set up in init.lua
to as follows to suppress premature long line warnings:
require("lspconfig").pylsp.setup{
settings = {
pylsp = {
plugins = {
pycodestyle = {
maxLineLength = 100
},
},
}
}
}