在上一篇NeoVim开发环境配置中记录了NeoVim的基本插件配置,随着了解的深入,这里对NeoVim的插件配置进行一步优化处理。
首先对配置进行切分,避免配置都混杂在init.vim中,不利于管理,下面是我的配置文件结构:
|
|
init.vim: 根配置文件
macos.vim: mscOS特殊配置
plug.vim :Vim-plug插件配置
maps.vim:快捷键配置
after/plugin: 放置插件的配置脚本,这里面的文件也会在vim每次启动的时候加载,不过是等待plugin加载完成之后才加载after里的内容,所以叫做after。VIM USER MANUAL runtimepath
1、插件管理器:vim-plug
安装 vim-plug 到 $HOME/.local/share/nvim/site/autoload/plug.vim
:
|
|
我在$HOME/.config/nvim/plug.vim
管理配置插件:
|
|
为了使plug.vim配置生效,需要在init.vim中进行配置加载:
1
|
runtime ./plug.vim |
此时,重启NeoVim,运行:PlugInstall
进行插件安装。
此外其他几个配置文件:maps.vim,macos.vim 也需要手动配置加载:
1 2 3 4 5 6 7 8 |
if has("unix") let s:uname = system("uname -s") " Do mac stuff if s:uname == "Darwin\n" runtime ./macos.vim endif endif runtime ./maps.vim |
后续安装的插件,分个单独配置,放置到after/plugin
目录下。
至此root配置(init.vim)、插件管理、快捷键管理、各插件独立进行配置,配置分离便于管理。
2、目录文件插件
NERDTree是Vim最常用的插件之一,可以在Vim运行时显示目录和文件结构,类似TextMate左侧的文件浏览器,但操作起来更为方便。
目前还有另外一个目录和文件结构管理插件defx.nvim:
- 不依赖于 denite.nvim
- Vim8/neovim 兼容(Vim8 需要 nvim-yarp)
- 由 Python3 实现
- 没有双重过滤器功能
- 列功能
- 类似于 denite.nvim 一样支持 source
- 支持选项
- 突出显示由列定义
- 很少的命令(仅:Defx 命令?)
- 扩展重命名
- 支持标记
安装
1
|
Plug 'Shougo/defx.nvim', { 'do': ':UpdateRemotePlugins' } |
配置使用
安装完成后,使用:Defx
命令来使用:
每次使用时Buffer会充满整个窗口,并不是我们常用形态,此时需要再进行配置,指定显示的位置以及大小等:
1 2 3 4 5 6 7 8 9 |
call defx#custom#option('_', { \ 'winwidth': 38, \ 'direction': 'topleft', \ 'split': 'vertical', \ 'show_ignored_files': 0, \ 'buffer_name': '', \ 'toggle': 1, \ 'resume': 1, \ }) |
效果如下:
设置快捷键
使用时通过输入Defx
命令太过繁琐效率不高,可以通过设置快捷键来改善,maps.vim中添加:
1 2 3 |
nnoremap <silent>sf :<C-u>Defx \ -auto-cd \ -columns=mark:indent:icon:icons:filename:type:git<CR> |
后续通过键入sf
即可唤出文件目录管理。
在after/plugin/defx.rc.vim
添加快捷键配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
autocmd FileType defx call s:defx_my_settings() function! s:defx_my_settings() abort " Define mappings nnoremap <silent><buffer><expr> <CR> \ defx#do_action('open') nnoremap <silent><buffer><expr> c \ defx#do_action('copy') nnoremap <silent><buffer><expr> m \ defx#do_action('move') nnoremap <silent><buffer><expr> p \ defx#do_action('paste') nnoremap <silent><buffer><expr> l \ defx#do_action('open') nnoremap <silent><buffer><expr> E \ defx#do_action('open', 'vsplit') nnoremap <silent><buffer><expr> P \ defx#do_action('preview') nnoremap <silent><buffer><expr> o \ defx#do_action('open_tree', 'toggle') nnoremap <silent><buffer><expr> K \ defx#do_action('new_directory') nnoremap <silent><buffer><expr> N \ defx#do_action('new_file') nnoremap <silent><buffer><expr> M \ defx#do_action('new_multiple_files') nnoremap <silent><buffer><expr> C \ defx#do_action('toggle_columns', \ 'mark:indent:icon:filename:type:size:time') nnoremap <silent><buffer><expr> S \ defx#do_action('toggle_sort', 'time') nnoremap <silent><buffer><expr> d \ defx#do_action('remove') nnoremap <silent><buffer><expr> r \ defx#do_action('rename') nnoremap <silent><buffer><expr> ! \ defx#do_action('execute_command') nnoremap <silent><buffer><expr> x \ defx#do_action('execute_system') nnoremap <silent><buffer><expr> yy \ defx#do_action('yank_path') nnoremap <silent><buffer><expr> . \ defx#do_action('toggle_ignored_files') nnoremap <silent><buffer><expr> ; \ defx#do_action('repeat') nnoremap <silent><buffer><expr> h \ defx#do_action('cd', ['..']) nnoremap <silent><buffer><expr> ~ \ defx#do_action('cd') nnoremap <silent><buffer><expr> q \ defx#do_action('quit') nnoremap <silent><buffer><expr> <Space> \ defx#do_action('toggle_select') . 'j' nnoremap <silent><buffer><expr> * \ defx#do_action('toggle_select_all') nnoremap <silent><buffer><expr> j \ line('.') == line('$') ? 'gg' : 'j' nnoremap <silent><buffer><expr> k \ line('.') == 1 ? 'G' : 'k' nnoremap <silent><buffer><expr> <C-l> \ defx#do_action('redraw') nnoremap <silent><buffer><expr> <C-g> \ defx#do_action('print') nnoremap <silent><buffer><expr> cd \ defx#do_action('change_vim_cwd') endfunction |
添加图标
设置文件图标可以让文件目录使用起来更加舒适,此时需要使用任意一种Nerd Fonts字体,并配置好iTerm,以便在iTerm2中可以正常显示字体图标:
|
|
iTerm2配置:
这里有一点需要注意的是,如果勾选了Use a different font for non-ASSII text
,那么第二种字体也需要使用Nerd Fonts中的一种,不然还是无法正常显示图标。
配置好iTerm2后,还需要安装defx-icons插件,才能使defx.nvim显示出文件图标,plug.vim
添加:
1
|
Plug 'kristijanhusak/defx-icons' |
:PlugInstall
安装重启后,就可以看到:
此时发现文件图片和文件名重叠,添加下面配置即可:
1
|
let g:defx_icons_column_length = 2 |
此时效果如下:
从最近关闭的窗口打开文件
使用官方文档中的默认快捷键配置后,发现总是从当前窗口中,打开文件,很不符合习惯:
如果想要和vscode一样,从右侧窗口打开文件,可以使用drop
替代open
来配置快捷键:
1 2 |
nnoremap <silent><buffer><expr> <CR> defx#do_action('drop') nnoremap <silent><buffer><expr> l defx#do_action('drop') |
文件名太长
文件名太长导致显示问题时,可以通过下面的方式,限制文件名可以显示的最大长度:
1 2 3 |
call defx#custom#column('filename', { \ 'max_width': 26, \}) |
3、启动屏
mhinz/vim-startify 启动屏可以记录最近编辑的文件,使用对应数字编号就可以快速打开文件,使用起来非常方便。
4、内置LSP配置gopls
Neovim已经内置了语言服务器协议 (LSP)。LSP时一种开放的、基于JSON- RPC的协议,用于源代码编辑器和语言服务器之间的通信,可以提供特定于编程语言的功能,如:
- 调转到定义
- 自动完成
- 代码操作(自动格式化、包导入…)
- 显示方法签名
- 显示、转到参考
- 代码片段
从 0.5 版本开始,NeoVim 原生支持该协议。NeoVim在nvim-lspconfig 插件中维护了一个配置列表。该存储库包含设置和排除许多服务器故障的说明。
如果需要为自己刚兴趣的编程语言,需要为其安装和配置相应的LSP服务器,这些可以通过nvim-lspconfig插件来处理。
安装插件
1
|
Plug 'neovim/nvim-lspconfig' |
需要注意的是这个插件只是配置管理,我们还需要单独为相应的编程语言安装LSP服务器,具体详见server_configurations
gopls
安装Google的 golang lsp server见:
https://github.com/golang/tools/tree/master/gopls
激活LSP服务,以及完整配置如下, lspconfig.rc.vim:
1
|
require'lspconfig'.gopls.setup{} |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
if !exists('g:lspconfig') finish endif lua << EOF vim.lsp.set_log_level("debug") EOF lua << EOF local nvim_lsp = require('lspconfig') nvim_lsp.pyright.setup{} nvim_lsp.gopls.setup{} local capabilities = vim.lsp.protocol.make_client_capabilities() capabilities.textDocument.completion.completionItem.snippetSupport = true -- 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) 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', 'ga', '<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', 'gi', '<cmd>lua vim.lsp.buf.implementation()<CR>', opts) buf_set_keymap('n', 'K', '<cmd>lua vim.lsp.buf.hover()<CR>', opts) buf_set_keymap('n', '<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) buf_set_keymap('n', '<space>e', '<cmd>lua vim.diagnostic.open_float()<CR>', opts) buf_set_keymap('n', '[d', '<cmd>lua vim.diagnostic.goto_prev()<CR>', opts) buf_set_keymap('n', ']d', '<cmd>lua vim.diagnostic.goto_next()<CR>', opts) buf_set_keymap('n', '<space>q', '<cmd>lua vim.diagnostic.setloclist()<CR>', opts) buf_set_keymap('n', '<space>f', '<cmd>lua vim.lsp.buf.formatting()<CR>', opts) end -- Use a loop to conveniently call 'setup' on multiple servers and -- map buffer local keybindings when the language server attaches local servers = { 'pyright', 'rust_analyzer', 'tsserver' } for _, lsp in ipairs(servers) do nvim_lsp[lsp].setup { on_attach = on_attach, flags = { debounce_text_changes = 150, } } end nvim_lsp.gopls.setup{ cmd = {'gopls'}, -- for postfix snippets and analyzers capabilities = capabilities, settings = { gopls = { experimentalPostfixCompletions = true, analyses = { unusedparams = true, shadow = true, }, staticcheck = true, }, }, on_attach = on_attach, } function goimports(timeoutms) local context = { source = { organizeImports = true } } vim.validate { context = { context, "t", true } } local params = vim.lsp.util.make_range_params() params.context = context -- See the implementation of the textDocument/codeAction callback -- (lua/vim/lsp/handler.lua) for how to do this properly. local result = vim.lsp.buf_request_sync(0, "textDocument/codeAction", params, timeout_ms) if not result or next(result) == nil then return end local actions = result[1].result if not actions then return end local action = actions[1] -- textDocument/codeAction can return either Command[] or CodeAction[]. If it -- is a CodeAction, it can have either an edit, a command or both. Edits -- should be executed first. if action.edit or type(action.command) == "table" then if action.edit then vim.lsp.util.apply_workspace_edit(action.edit) end if type(action.command) == "table" then vim.lsp.buf.execute_command(action.command) end else vim.lsp.buf.execute_command(action) end end EOF |
配置gopls服务,提供跳转、查看定义、重命名等:
1 2 3 4 5 6 7 8 |
-- 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', 'ga', '<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', 'gi', '<cmd>lua vim.lsp.buf.implementation()<CR>', opts) |
格式化代码
格式化代码快捷键配置:
1
|
buf_set_keymap('n', '<space>f', '<cmd>lua vim.lsp.buf.formatting()<CR>', opts) |
保存文件时自动格式化:
1 2 |
" 保存代码前进行自动格式化 autocmd BufWritePre *.go lua vim.lsp.buf.formatting() |
包导入
触发包导入有两种方式来解决,一种时通过code action来手动触发:
1
|
buf_set_keymap('n', 'ga', '<Cmd>lua vim.lsp.buf.code_action()<CR>', opts) |
也可以通过保存文件时,自动触发:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
function goimports(timeoutms) local context = { source = { organizeImports = true } } vim.validate { context = { context, "t", true } } local params = vim.lsp.util.make_range_params() params.context = context -- See the implementation of the textDocument/codeAction callback -- (lua/vim/lsp/handler.lua) for how to do this properly. local result = vim.lsp.buf_request_sync(0, "textDocument/codeAction", params, timeout_ms) if not result or next(result) == nil then return end local actions = result[1].result if not actions then return end local action = actions[1] -- textDocument/codeAction can return either Command[] or CodeAction[]. If it -- is a CodeAction, it can have either an edit, a command or both. Edits -- should be executed first. if action.edit or type(action.command) == "table" then if action.edit then vim.lsp.util.apply_workspace_edit(action.edit) end if type(action.command) == "table" then vim.lsp.buf.execute_command(action.command) end else vim.lsp.buf.execute_command(action) end end |
并配置:
1
|
autocmd BufWritePre *.go lua goimports(1000) |
自动完成
完成是开箱即用的。我们只需要将它映射到 vimomnifunc
即可使我们的Ctrl+x
,Ctrl+o
工作:
1
|
buf_set_option('omnifunc', 'v:lua.vim.lsp.omnifunc') |
但是,每次触发都需要通过Ctrl+x
,Ctrl+o
来触发,十分不方便,为了自动触发,可以在通过安装新的插件ms-jpq/coq_nvim来完成,如:
1 2 3 4 5 |
" 增强代码自动完成 Plug 'ms-jpq/coq_nvim', {'branch': 'coq'} " 9000+ Snippets Plug 'ms-jpq/coq.artifacts', {'branch': 'artifacts'} Plug 'ms-jpq/coq.thirdparty', {'branch': '3p'} |
5、其他优化配置
复制到自动剪贴板
始终将所有操作复制到系统剪贴板:
1
|
set clipboard+=unnamedplus |
优化NeoVim内置lsp
目前基于内置lsp的功能已经比较完善,但是缺乏一个良好的操作UI,如重命名时,输入新名字的地方在窗口底部:
使用async-lsp-finder后:
安装
1
|
Plug 'glepnir/lspsaga.nvim' |
快捷键配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
" 异步lsp查找 nnoremap <silent> gh :Lspsaga lsp_finder<CR> " Code Action nnoremap <silent><leader>ca :Lspsaga code_action<CR> vnoremap <silent><leader>ca :<C-U>Lspsaga range_code_action<CR> " 悬停文档 nnoremap <silent>K :Lspsaga hover_doc<CR> " scroll down hover doc or scroll in definition preview nnoremap <silent> <C-f> <cmd>lua require('lspsaga.action').smart_scroll_with_saga(1)<CR> " scroll up hover doc nnoremap <silent> <C-b> <cmd>lua require('lspsaga.action').smart_scroll_with_saga(-1)<CR> " help nnoremap <silent> gs :Lspsaga signature_help<CR> " 重命名 nnoremap <silent>gr :Lspsaga rename<CR> " 预览定义 nnoremap <silent> gd :Lspsaga preview_definition<CR> " 浮动终端 nnoremap <silent> <A-d> :Lspsaga open_floaterm<CR> tnoremap <silent> <A-d> <C-\><C-n>:Lspsaga close_floaterm<CR> |