配置neovim7
最近经常要在Linux下疯狂编辑文件,我不想专门打开一个极其臃肿的软件来对这些文件进行编辑,也不想每次都手敲,没有一点代码提示,所以今天下午配置一下neovim。
我不会把它配置成一个完全可以使用的IDE,因为涉及到开发工作我都会使用其它的编辑器或IDE,我的目标是将它配置成一个基本可用的,具有代码提示和补全功能的文本编辑器
安装
去到它repo中的Release页面,下载你的系统的对应版本,我这里是Ubuntu,所以我下载deb的。
下面两条命令进行下载安装
wget https://github.com/neovim/neovim/releases/download/v0.7.2/nvim-linux64.deb
sudo dpkg -i nvim-linux64.deb
配置
NeoVim使用lua进行配置,相比于传统的Vim来说,简单,易读,强大。
基础配置语法
我们先在~/.config/nvim
中创建init.lua
vim.o.number=true
vim.o.tabstop=2
vim.o.shiftwidth=2
vim.o.smartindent=true
vim.o.termguicolors=true
vim.o.cursorline=true
vim.o.expandtab=true
vim.o.swapfile=false
这就是把vim配置转成lua的语法格式了,通过vim.o
来进行配置。这个文档中有所有的配置列表
分文件配置
如果将插件、基础配置都放在一个文件里,很快文件就会变得难以维护。好在,使用lua做配置文件的好处就是天生支持导入模块。
创建~/.config/nvim/lua/basic.lua
和~/.config/nvim/lua/plugins.lua
,然后将init.lua
中本来的内容剪切到basic.lua
中,另一个文件保持为空的状态,然后在init.lua
中引入这两个文件:
-- init.lua
require('basic')
require('plugins')
当前目录结构:
➜ nvim tree
.
├── init.lua
├── lua
│ ├── basic.lua
│ └── plugins.lua
require
是lua自带的导入其它lua文件的函数,你可以通过require(文件名)
来导入和当前文件相同层级结构的lua源文件,也可以通过require(文件夹.文件夹2.文件名)
来导入更深层次的lua源文件。**neovim默认将lua
文件夹作为了搜索路径,所以我们不用使用require("lua.basic")
插件管理器——Packer
Nvim's data directory is located in ~/.local/share/nvim/ and contains swap for open files, the ShaDa (Shared Data) file, and the site directory for plugins.
Nvim的数据目录在
~/.local/share/nvim
下,其中包含打开文件的交换文件、ShaDa(共享数据)文件以及为插件所准备的site
文件夹
通过上面的文字可以看出,我们可以通过将Nvim的插件复制到~/.local/share/nvim/site
文件夹中来扩展NeoVim的功能,不过这个过程很繁琐,所以我们可以使用插件管理器来完成。
安装
下面是Linux/Unix的安装方式,其实就是将packer的仓库克隆到nvim的插件目录下:
git clone --depth 1 https://github.com/wbthomason/packer.nvim\
~/.local/share/nvim/site/pack/packer/start/packer.nvim
其它系统的用户可以通过QuickStart来获取安装教程。
使用
向plugins.lua
中添加如下内容,这些内容也可以在Packer的ReadMe中找到
vim.cmd [[packadd packer.nvim]]
return require('packer').startup(function(use)
-- Packer can manage itself
use 'wbthomason/packer.nvim'
-- 你可以在这里定义你所需要的插件列表
end)
上面的代码,就是导入packer
并执行它的startup
方法,我们要在startup
的回调方法中编写我们要插件管理器管理的插件,这里默认只有一个wbthomason/packer.nvim
,没错,就是将packer本身交给packer管理。
后面,如果我们有其它的插件想要被packer管理,我们都可以写在这里。
换源
因为默认情况下这些仓库都是在github中的,所以可能有访问不到的情况,这里可以配置packer使用其它镜像源
vim.cmd [[packadd packer.nvim]]
return require('packer').startup({
function(use)
-- packer itself
use 'wbthomason/packer.nvim'
end,
config = {
-- 最大并发数
max_jobs = 16,
-- 自定义源
git = {
default_url_format = "https://hub.fastgit.xyz/%s",
-- default_url_format = "https://mirror.ghproxy.com/https://github.com/%s",
-- default_url_format = "https://gitcode.net/mirrors/%s",
-- default_url_format = "https://gitclone.com/github.com/%s",
},
},
})
命令
Packer提供以下命令:
-- 每当你想要让你的插件配置发生改变时,你必须运行这个或者`PackerSync`
-- 重新生成编译的loader文件
:PackerCompile
-- 移除所有关闭或未使用的插件
:PackerClean
-- Clean,然后安装丢失的插件
:PackerInstall
-- Clean, 然后更新安装的插件
:PackerUpdate
-- 相当于 `PackerUpdate` 然后 `PackerCompile`
:PackerSync
-- Loads opt plugin immediately
:PackerLoad completion-nvim ale
LSP配置
LSP即语言服务器协议,由微软开发,让IDE与各种语言的自动提示、跳转等功能解耦,这些功能由语言服务器提供,IDE只负责处理UI逻辑。IDE和语言服务器之间通过LSP交互。
在nvim中输入命令:help lsp
即可获得一份LSP配置指南。
QuickStart
Nvim提供一个LSP客户端,但是服务器由第三方提供,跟着下面的步骤来获得LSP的特性:
- 安装
nvim-lspconfig
插件,它提供了多种服务器的通用配置,所以你可以很快上手 - 安装一个语言服务器,可以从这里找到一个语言服务器的列表
- 添加
lua require('lspconfig').xx.setup{…}
到你的init.vim
中,xx
是对应配置的名字,去lspconfig的README查看更多细节 - 检查一个LSP客户端是否被添加到了当前的缓冲区中
:lua print(vim.inspect(vim.lsp.buf_get_clients()))
安装nvim-lspconfig
得益于Packer,nvim-lspconfig
的安装非常简单
return require('packer').startup({
function(use)
-- packer itself
use 'wbthomason/packer.nvim'
-- lspconfig
-- 添加这一行
use 'neovim/nvim-lspconfig'
end
-- ...
然后,运行:PackerSync
安装Lua语言服务器
既然我们现在得写Lua,那就安装个Lua的语言服务器吧,有点难度,但是能让我们学得更扎实。其实有LSP的管理器插件,我们完全不用这么麻烦。
你可以直接跳到下一节,自动安装LSP服务器
进入上面所说的LSP服务器列表,找到lua的lsp,这里我们使用下面这个
跟着文档,找到Install For Command Line,按照里面的步骤安装。
第一步,下载对应系统的release,然后将它解压到~/.local/share/nvim/lsp
文件夹中:
wget https://github.com/sumneko/lua-language-server/releases/download/3.5.3/lua-language-server-3.5.3-linux-x64.tar.gz
第二步,为它创建配置文件,主要就是完成QuickStart步骤中的第三步——添加lua require('lspconfig').xx.setup{…}
到配置中
代码参考这篇博客:12_[nvim0.5+从0单排]_nvim内置lsp搭建lua IDE
local nvim_lsp = require('lspconfig')
-- 在语言服务器附加到当前缓冲区之后
-- 使用 on_attach 函数仅映射以下键
Itkey_on_attach = function(client, bufnr)
local function buf_set_keymap(...) vim.api.nvim_buf_set_keymap(bufnr, ...) end
local function buf_set_option(...) vim.api.nvim_buf_set_option(bufnr, ...) end
--Enable completion triggered by <c-x><c-o>
buf_set_option('omnifunc', 'v:lua.vim.lsp.omnifunc')
-- Mappings.
local opts = { noremap=true, silent=true }
-- See `:help vim.lsp.*` for documentation on any of the below functions
buf_set_keymap('n', 'gD', '<Cmd>lua vim.lsp.buf.declaration()<CR>', opts)
buf_set_keymap('n', 'gd', '<Cmd>lua vim.lsp.buf.definition()<CR>', opts)
--buf_set_keymap('n', 'K', '<Cmd>lua vim.lsp.buf.hover()<CR>', opts)
buf_set_keymap('n', 'gi', '<cmd>lua vim.lsp.buf.implementation()<CR>', opts)
--buf_set_keymap('i', '<C-k>', '<cmd>lua vim.lsp.buf.signature_help()<CR>', opts)
buf_set_keymap('n', '<space>wa', '<cmd>lua vim.lsp.buf.add_workspace_folder()<CR>', opts)
buf_set_keymap('n', '<space>wr', '<cmd>lua vim.lsp.buf.remove_workspace_folder()<CR>', opts)
buf_set_keymap('n', '<space>wl', '<cmd>lua print(vim.inspect(vim.lsp.buf.list_workspace_folders()))<CR>', opts)
buf_set_keymap('n', '<space>D', '<cmd>lua vim.lsp.buf.type_definition()<CR>', opts)
--重命名
buf_set_keymap('n', '<space>rn', '<cmd>lua vim.lsp.buf.rename()<CR>', opts)
--智能提醒,比如:自动导包 已经用lspsaga里的功能替换了
--buf_set_keymap('n', '<space>ca', '<cmd>lua vim.lsp.buf.code_action()<CR>', opts)
buf_set_keymap('n', 'gr', '<cmd>lua vim.lsp.buf.references()<CR>', opts)
buf_set_keymap('n', '<space>e', '<cmd>lua vim.lsp.diagnostic.show_line_diagnostics()<CR>', opts)
--buf_set_keymap('n', '<C-j>', '<cmd>lua vim.lsp.diagnostic.goto_prev()<CR>', opts)
buf_set_keymap('n', '<S-C-j>', '<cmd>lua vim.lsp.diagnostic.goto_next()<CR>', opts)
buf_set_keymap('n', '<space>q', '<cmd>lua vim.lsp.diagnostic.set_loclist()<CR>', opts)
buf_set_keymap("n", "<leader>f", "<cmd>lua vim.lsp.buf.formatting()<CR>", opts)
-- 代码保存自动格式化formatting
if client.resolved_capabilities.document_formatting then
vim.api.nvim_command [[augroup Format]]
vim.api.nvim_command [[autocmd! * <buffer>]]
vim.api.nvim_command [[autocmd BufWritePre <buffer> lua vim.lsp.buf.formatting_seq_sync()]]
vim.api.nvim_command [[augroup END]]
end
end
-- Add additional capabilities supported by nvim-cmp
local capabilities = vim.lsp.protocol.make_client_capabilities()
capabilities.textDocument.completion.completionItem.documentationFormat = { 'markdown', 'plaintext' }
capabilities.textDocument.completion.completionItem.snippetSupport = true
capabilities.textDocument.completion.completionItem.preselectSupport = true
capabilities.textDocument.completion.completionItem.insertReplaceSupport = true
capabilities.textDocument.completion.completionItem.labelDetailsSupport = true
capabilities.textDocument.completion.completionItem.deprecatedSupport = true
capabilities.textDocument.completion.completionItem.commitCharactersSupport = true
capabilities.textDocument.completion.completionItem.tagSupport = { valueSet = { 1 } }
capabilities.textDocument.completion.completionItem.resolveSupport = {
properties = {
'documentation',
'detail',
'additionalTextEdits',
},
}
Itkey_capabilities = capabilities
---------------分割线-------------
local system_name
if vim.fn.has("mac") == 1 then
system_name = "macOS"
elseif vim.fn.has("unix") == 1 then
system_name = "Linux"
elseif vim.fn.has("win32") == 1 then
system_name = "Windows"
else
print("Unsupported system for sumneko")
end
-- set the path to the sumneko installation; if you previously installed via the now deprecated :LspInstall, use
--local sumneko_root_path = vim.fn.stdpath('config')..'/lsp/lua-language-server'
local sumneko_root_path = vim.fn.stdpath("data") .. "/lsp/lua-language-server"
--local sumneko_root_path = vim.fn.stdpath('cache')..'/lsp/lua-language-server'
local sumneko_binary = sumneko_root_path .. "/bin/" .. system_name .. "/lua-language-server"
local runtime_path = vim.split(package.path, ";")
table.insert(runtime_path, "lua/?.lua")
table.insert(runtime_path, "lua/?/init.lua")
require "lspconfig".sumneko_lua.setup {
on_attach = Itkey_on_attach,
cmd = {sumneko_binary, "-E", sumneko_root_path .. "/main.lua", "--locale=zh-cn"},
settings = {
Lua = {
runtime = {
-- Tell the language server which version of Lua you're using (most likely LuaJIT in the case of Neovim)
version = "LuaJIT",
-- Setup your lua path
path = runtime_path
},
diagnostics = {
-- Get the language server to recognize the `vim` global
globals = {"vim"}
},
workspace = {
-- Make the server aware of Neovim runtime files
library = vim.api.nvim_get_runtime_file("", true)
},
-- Do not send telemetry data containing a randomized but unique identifier
telemetry = {
enable = false
}
}
},
capabilities = Itkey_capabilities
}
在你的init.lua
文件中引入这个文件:
require('lspconf.lua')
退出并再次运行nvim,你可能会发现一些问题
它说,语言服务器可能不存在,路径不对或不能执行,经观察路径确实不对,多了个Linux
。我们修改上面的大段代码中的第78行:
- local sumneko_binary = sumneko_root_path .. "/bin/" .. system_name .. "/lua-language-server"
+ local sumneko_binary = sumneko_root_path .. "/bin/" .. "/lua-language-server"
当我们再次启动NeoVim时,LSP已经生效了,只是这UI。。。怎么窜位了(这一看就是没安装Nerd Font的原因)
自动LSP安装
上面的安装过程实在是过于繁琐,虽然我们最终成功的安装了lua的LSP。下面我要介绍的nvim-lsp-isntaller
,是一个类似市场的东西,你可以在里面一键安装LSP。
-- lua/plugins.lua
use { "williamboman/nvim-lsp-installer" }
然后,你需要在init.lua
中配置它,总之,setup
方法必须被调用
-- init.lua
require("nvim-lsp-installer").setup({
ui = {
icons = {
package_installed = "✓",
package_pending = "➜",
package_uninstalled = "✗"
}
}
})
几乎所有的插件的安装流程都是这样的,安装后你需要
require
后再执行setup
,进行一系列的配置。这些配置一般在插件的ReadMe中。
我们不想让这些插件的配置步骤污染init.lua
,我们可以在plugins.lua
中直接写
-- nvim-lsp-installer
use {
"williamboman/nvim-lsp-installer",
config = function()
require("nvim-lsp-installer").setup {
ui = {
icons = {
server_installed = "✓",
server_pending = "➜",
server_uninstalled = "✗"
}
}
}
end
}
然后执行PackerSync
,现在输入LspInstallInfo
就会弹出下面这个框框
我们删掉之前手动安装的Lua LSP服务器,并使用这个自动安装,输入:LspInstall sumneko_lua
命令,将自动安装lua的lsp
我们再以同样的方法安装pyright
之后在init.lua
中初始化它们:
local sumneko_lua_binpath = '/home/yudoge/.local/share/nvim/lsp_servers/sumneko_lua/extension/server/bin/'
local sumneko_lua_binapp = sumneko_lua_binpath .. 'lua-language-server'
require 'lspconfig'.sumneko_lua.setup {
cmd = { sumneko_lua_binapp, '-E', sumneko_lua_binpath..'main.lua', '--locale=zh-cn' }
}
require 'lspconfig'.pyright.setup {}
可以看到,即使使用了自动安装工具,有些lsp的配置还是比较麻烦,比如lua这个,有的则比较简单。所以,当要安装一个插件时,最好的资料就是直接去看官方文档。
为了避免污染init.lua
,我们可以建立lua/lspconfig.lua
,在这里初始所有lsp。
自动补全
要明确的一点是,nvim-lspconfig
虽然能帮我们做一些自动的lsp服务器的配置,但它并没有提供自动补全功能,我们需要安装提供自动补全功能的插件,并且将它与lsp服务器整合。
这里我们安装的是nvim-cmp
-- lua/plugins.lua
use 'hrsh7th/nvim-cmp'
use 'hrsh7th/cmp-nvim-lsp'
第一个是插件的本体,第二个是自动补齐的来源,这是一个来自于LSP服务器的自动补全来源。
然后,我们需要对该插件进行配置,在init.lua
中写入:
local cmp = require('cmp')
cmp.setup({
-- 指定来源名称
sources = cmp.config.sources({
{ name = 'nvim_lsp' }
})
})
为了不污染init.lua
,你也可以将它放到独立的文件中。现在,已经有了自动补齐功能。
现在,我们可以尝试再安装其它来源
-- lua/plugins.lua
use 'hrsh7th/cmp-path'
该来源可以基于文件系统中的文件路径进行补全
别忘了添加配置
-- init.lua
local cmp = require('cmp')
cmp.setup({
sources = cmp.config.sources({
{ name = 'nvim_lsp' },
{ name = 'path' },
})
})
现在,已经可以基于文件进行补全了
按键映射和Snippet插入
你应该已经发现了,这个自动补全目前只能看,没法补全,你没法按上下和回车来选择一个项目
其实官方文档里已经有相关的配置,为了开启按键映射,你需要配置下面的东西:
-- init.lua
cmp.setup({
sources = cmp.config.sources({
{ name = 'nvim_lsp' },
{ name = 'path' },
}),
mapping = cmp.mapping.preset.insert({
['<C-b>'] = cmp.mapping.scroll_docs(-4),
['<C-f>'] = cmp.mapping.scroll_docs(4),
['<C-Space>'] = cmp.mapping.complete(),
['<C-e>'] = cmp.mapping.abort(),
['<CR>'] = cmp.mapping.confirm({ select = true }),
['<Tab>'] = cmp.mapping.confirm({ select = true }), -- Accept currently selected item. Set `select` to `false` to only confirm explicitly selected items.
}),
})
配置后,上下左右键能用了,而且ctrl+b
、ctrl+f
用于在弹出的文档中上下翻页,ctrl-e
可以结束自动补全,回车和tab可以应用当前选中的自动补全。
但是当你选中时,你会毫不留情的看到一个错误,这是因为这个插件并不提供向文档中插入的功能,插入功能由snippet
类插件提供,官方给出了好几个支持的插件,我们这里选择vsnip
。
-- lua/plugins.lua
use 'hrsh7th/cmp-vsnip'
use 'hrsh7th/vim-vsnip'
-- init.lua
cmp.setup({
-- ...
snippet = {
-- REQUIRED - you must specify a snippet engine
expand = function(args)
vim.fn["vsnip#anonymous"](args.body) -- For `vsnip` users.
-- require('luasnip').lsp_expand(args.body) -- For `luasnip` users.
-- require('snippy').expand_snippet(args.body) -- For `snippy` users.
-- vim.fn["UltiSnips#Anon"](args.body) -- For `ultisnips` users.
end,
},
-- ...
})
现在,自动补全也能用了。
总结
NeoVim的插件安装大致分为以下步骤
- 找到对应的仓库
- 将仓库名添加到插件管理器的配置文件中,比如
use xxx/xxx
- 阅读插件文档,进行相关配置
并且每个NeoVim插件的职能范围是很小的,多个插件间有时会相互依赖,这时你要处理好依赖关系。
通过一下午的配置,最深的体会就是阅读文档的能力非常重要,很多东西是需要你通过阅读文档先建立起对这东西的最基本最正确的理解才能开始的。最初不想在这上浪费太多时间,所以就找一些csdn、知乎上的教程,但殊不知那些教程虽然是复制粘贴就行,但如果你对这个东西的理解不充分,当复制粘贴时出了问题,你根本就没法解决。我不是说学一个东西最好的办法就是自底向上一步一步向上爬,这会累死你的,而是我们应该选择一个适合我们自身情况的抽象切面,站在这个基础上再一步一步往上爬。比如学Java后端开发,你没必要先去研究CPU是怎么运行的,而是站在Java这门高级语言的基础上一点点向上爬。