NeoVim配置Go开发环境
在上一篇NeoVim开发环境配置中记录了NeoVim的基本插件配置,随着了解的深入,这里对NeoVim的插件配置进行一步优化处理。
首先对配置进行切分,避免配置都混杂在init.vim中,不利于管理,下面是我的配置文件结构:
$HOME/.config/nvim/
├── after
│ └── plugin
│ ├── coq_nvim.rc.vim
│ ├── defx.rc.vim
│ ├── fugitive.rc.vim
│ ├── lspconfig.rc.vim
│ └── treesitter.rc.vim
├── init.vim
├── macos.vim
├── maps.vim
└── plug.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
:
sh -c 'curl -fLo $HOME/.local/share/nvim/site/autoload/plug.vim --create-dirs \
https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim'
我在$HOME/.config/nvim/plug.vim
管理配置插件:
if has("nvim")
let g:plug_home = stdpath('data') . '/plugged'
endif
call plug#begin()
...
call plug#end()
为了使plug.vim配置生效,需要在init.vim中进行配置加载:
runtime ./plug.vim
此时,重启NeoVim,运行:PlugInstall
进行插件安装。
此外其他几个配置文件:maps.vim,macos.vim 也需要手动配置加载:
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 命令?)
- 扩展重命名
- 支持标记
安装
Plug 'Shougo/defx.nvim', { 'do': ':UpdateRemotePlugins' }
配置使用
安装完成后,使用:Defx
命令来使用:
每次使用时Buffer会充满整个窗口,并不是我们常用形态,此时需要再进行配置,指定显示的位置以及大小等:
call defx#custom#option('_', {
\ 'winwidth': 38,
\ 'direction': 'topleft',
\ 'split': 'vertical',
\ 'show_ignored_files': 0,
\ 'buffer_name': '',
\ 'toggle': 1,
\ 'resume': 1,
\ })
效果如下:
设置快捷键
使用时通过输入Defx
命令太过繁琐效率不高,可以通过设置快捷键来改善,maps.vim中添加:
nnoremap <silent>sf :<C-u>Defx
\ -auto-cd
\ -columns=mark:indent:icon:icons:filename:type:git<CR>
后续通过键入sf
即可唤出文件目录管理。
在after/plugin/defx.rc.vim
添加快捷键配置:
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中可以正常显示字体图标:
brew tap homebrew/cask-fonts
brew cask install font-hack-nerd-font
iTerm2配置:
这里有一点需要注意的是,如果勾选了Use a different font for non-ASSII text
,那么第二种字体也需要使用Nerd Fonts中的一种,不然还是无法正常显示图标。
配置好iTerm2后,还需要安装defx-icons插件,才能使defx.nvim显示出文件图标,plug.vim
添加:
Plug 'kristijanhusak/defx-icons'
:PlugInstall
安装重启后,就可以看到:
此时发现文件图片和文件名重叠,添加下面配置即可:
let g:defx_icons_column_length = 2
此时效果如下:
从最近关闭的窗口打开文件
使用官方文档中的默认快捷键配置后,发现总是从当前窗口中,打开文件,很不符合习惯:
如果想要和vscode一样,从右侧窗口打开文件,可以使用drop
替代open
来配置快捷键:
nnoremap <silent><buffer><expr> <CR> defx#do_action('drop')
nnoremap <silent><buffer><expr> l defx#do_action('drop')
文件名太长
文件名太长导致显示问题时,可以通过下面的方式,限制文件名可以显示的最大长度:
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插件来处理。
安装插件
Plug 'neovim/nvim-lspconfig'
需要注意的是这个插件只是配置管理,我们还需要单独为相应的编程语言安装LSP服务器,具体详见server_configurations
gopls
安装Google的 golang lsp server见:
https://github.com/golang/tools/tree/master/gopls
激活LSP服务,以及完整配置如下, lspconfig.rc.vim:
require'lspconfig'.gopls.setup{}
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服务,提供跳转、查看定义、重命名等:
-- 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', '<space>f', '<cmd>lua vim.lsp.buf.formatting()<CR>', opts)
保存文件时自动格式化:
" 保存代码前进行自动格式化
autocmd BufWritePre *.go lua vim.lsp.buf.formatting()
包导入
触发包导入有两种方式来解决,一种时通过code action来手动触发:
buf_set_keymap('n', 'ga', '<Cmd>lua vim.lsp.buf.code_action()<CR>', opts)
也可以通过保存文件时,自动触发:
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
并配置:
autocmd BufWritePre *.go lua goimports(1000)
自动完成
完成是开箱即用的。我们只需要将它映射到 vimomnifunc
即可使我们的Ctrl+x
,Ctrl+o
工作:
buf_set_option('omnifunc', 'v:lua.vim.lsp.omnifunc')
但是,每次触发都需要通过Ctrl+x
,Ctrl+o
来触发,十分不方便,为了自动触发,可以在通过安装新的插件ms-jpq/coq_nvim来完成,如:
" 增强代码自动完成
Plug 'ms-jpq/coq_nvim', {'branch': 'coq'}
" 9000+ Snippets
Plug 'ms-jpq/coq.artifacts', {'branch': 'artifacts'}
Plug 'ms-jpq/coq.thirdparty', {'branch': '3p'}
5、其他优化配置
复制到自动剪贴板
始终将所有操作复制到系统剪贴板:
set clipboard+=unnamedplus
优化NeoVim内置lsp
目前基于内置lsp的功能已经比较完善,但是缺乏一个良好的操作UI,如重命名时,输入新名字的地方在窗口底部:
使用async-lsp-finder后:
安装
Plug 'glepnir/lspsaga.nvim'
快捷键配置:
" 异步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>