Skip to content

博客

NeoVim配置Go开发环境

在上一篇NeoVim开发环境配置中记录了NeoVim的基本插件配置,随着了解的深入,这里对NeoVim的插件配置进行一步优化处理。

首先对配置进行切分,避免配置都混杂在init.vim中,不利于管理,下面是我的配置文件结构:

Terminal window
$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

安装 vim-plug$HOME/.local/share/nvim/site/autoload/plug.vim:

Terminal window
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)、插件管理、快捷键管理、各插件独立进行配置,配置分离便于管理。

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命令来使用:

img

每次使用时Buffer会充满整个窗口,并不是我们常用形态,此时需要再进行配置,指定显示的位置以及大小等:

call defx#custom#option('_', {
\ 'winwidth': 38,
\ 'direction': 'topleft',
\ 'split': 'vertical',
\ 'show_ignored_files': 0,
\ 'buffer_name': '',
\ 'toggle': 1,
\ 'resume': 1,
\ })

效果如下:

Defx -split=vertical

使用时通过输入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中可以正常显示字体图标:

Terminal window
brew tap homebrew/cask-fonts
brew cask install font-hack-nerd-font

iTerm2配置:

image-20220121123108917

这里有一点需要注意的是,如果勾选了Use a different font for non-ASSII text,那么第二种字体也需要使用Nerd Fonts中的一种,不然还是无法正常显示图标。

配置好iTerm2后,还需要安装defx-icons插件,才能使defx.nvim显示出文件图标,plug.vim添加:

Plug 'kristijanhusak/defx-icons'

:PlugInstall安装重启后,就可以看到:

image-20220121123639182

此时发现文件图片和文件名重叠,添加下面配置即可:

let g:defx_icons_column_length = 2

此时效果如下:

image-20220121123849035

使用官方文档中的默认快捷键配置后,发现总是从当前窗口中,打开文件,很不符合习惯:

image-20220121124512001

如果想要和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,
\})

mhinz/vim-startify 启动屏可以记录最近编辑的文件,使用对应数字编号就可以快速打开文件,使用起来非常方便。

image-20220121125121988

Neovim已经内置了语言服务器协议 (LSP)。LSP时一种开放的、基于JSON- RPC的协议,用于源代码编辑器和语言服务器之间的通信,可以提供特定于编程语言的功能,如:

  1. 调转到定义
  2. 自动完成
  3. 代码操作(自动格式化、包导入…)
  4. 显示方法签名
  5. 显示、转到参考
  6. 代码片段

从 0.5 版本开始,NeoVim 原生支持该协议。NeoVim在nvim-lspconfig 插件中维护了一个配置列表。该存储库包含设置和排除许多服务器故障的说明。

如果需要为自己刚兴趣的编程语言,需要为其安装和配置相应的LSP服务器,这些可以通过nvim-lspconfig插件来处理。

Plug 'neovim/nvim-lspconfig'

需要注意的是这个插件只是配置管理,我们还需要单独为相应的编程语言安装LSP服务器,具体详见server_configurations

安装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)

format img

格式化代码快捷键配置:

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'}

始终将所有操作复制到系统剪贴板:

set clipboard+=unnamedplus

目前基于内置lsp的功能已经比较完善,但是缺乏一个良好的操作UI,如重命名时,输入新名字的地方在窗口底部:

image-20220121173142281

使用async-lsp-finder后:

img

安装

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>
  1. neovim-lsp
  2. nvim-treesitter
  3. nvim-lspconfig
  4. defx.nvim
  5. defx.txt
  6. How to set up Neovim 0.5 + Modern plugins (LSP, Treesitter, Fuzzy finder, etc)
  7. dotfiles-public
  8. programming-go-in-neovim
  9. go-vim
  10. awesome-neovim
  11. VIM USER MANUAL
  12. runtimepath
  13. 如何配置 Vim 的 Golang 开发环境

NeoVim开发环境配置

img

在vscode等IDE中使用vim插件也已经很久了,最近发现了NeoVim的存在,之前也非常好奇国外的程序员为啥有许多,执着于编辑器之神(Vim) & 神的编辑器(Emacs),因此也尝试使用NeoVim配置了一套环境。

Bram Moolenaar 在写 Vim 时还是 90 年代初,至今已经 20 多年 过去了。其中,不仅包含了大量的遗留代码,而且程序的维护、Bug 的 修复、以及新特性的添加都变得越来越困难。为了解决这些问题,Neovim 项目应运而生。Neo 即“新”之意,它是 Vim 在这个新时代的重生。

根据 Neovim 的自述说明,在总体上,它将达到下列目的:

  • 通过简化维护以改进 Bug 修复及特性添加的速度;
  • 分派各个开发人员的工作;
  • 实现新的、现代化的用户界面,而不必修改核心源代码;
  • 利用新的、基于协同进程的新插件架构改善扩展性,并支持使用任何语言 编写插件

以上介绍来自linuxtoy

此外, 在最近的版本中, 还有非常值得注意的几点:

  • 实现了嵌入式终端模拟器, 可以跟各种REPL插件说再见了
  • 使用远程API(好像是socket), 不光能使用各种语言编写插件, 而且可以很方便的编写GUI版本, 甚至嵌入至IDE中
  • 由于内部优化了事件监听器还是什么的, 代码粘贴的时候, 可以自动识别, 不像vim里一样需要:set paste, 不然会出现蜜汁缩进和括号对
  • 直接支持剪贴板, 不需要重新编译

macOS/OS X 上使用Homebrew安装

Terminal window
brew install neovim

更多安装方式参考:Installing Neovim

安装

Terminal window
sh -c 'curl -fLo "${XDG_DATA_HOME:-$HOME/.local/share}"/nvim/site/autoload/plug.vim --create-dirs \
https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim'

基本使用

neovim继承自vim,基本支持vim的所有特性,可以直接使用vim的配置文件:

Terminal window
mkdir -p ${XDG_CONFIG_HOME:=$HOME/.config}
ln -s ~/.vim $XDG_CONFIG_HOME/nvim
ln -s ~/.vimrc $XDG_CONFIG_HOME/nvim/init.vim

也可以重新开始配置neovim

Terminal window
mkdir -p ~/.config/ ~/.config/nvim
touch ~/.config/ ~/.config/nvim/init.vim

Plug插件,需要在 call plug#begin()call plug#end() 节点中,使用Plug命令进行声明,如:

Terminal window
call plug#begin()
" The default plugin directory will be as follows:
" - Vim (Linux/macOS): '~/.vim/plugged'
" - Vim (Windows): '~/vimfiles/plugged'
" - Neovim (Linux/macOS/Windows): stdpath('data') . '/plugged'
" You can specify a custom plugin directory by passing it as the argument
" - e.g. `call plug#begin('~/.vim/plugged')`
" - Avoid using standard Vim directory names like 'plugin'
" Make sure you use single quotes
" Using a tagged release; wildcard allowed (requires git 1.9.2 or above)
Plug 'fatih/vim-go', { 'tag': '*' }
" Initialize plugin system
call plug#end()

配置好插件后,进行安装升级

Terminal window
:PlugInstall
:PlugUpdate
:PlugUpgrade

查看已经安装的插件状态

Terminal window
:PlugStatus

更多资料,参考vim-plug

方便开发使用,需要对下面一些基本功能进行配置:

  1. 目录管理 - nerdtree
  2. 文件模糊搜索 - fzf
  3. 代码缩略图 - tagbar
  4. 主题配色 - molokai
  5. 状态栏 - airline
  6. 编程语言相关插件(vim-go)
  7. 自动补全- neocomplete.vim
  8. 其他(vim-stratify)

常用非常流行的目录管理插件nerdtree

Terminal window
Plug 'scrooloose/nerdtree'
nnoremap <leader>n :NERDTreeFocus<CR>
nnoremap <C-n> :NERDTree<CR>
nnoremap <C-t> :NERDTreeToggle<CR>
nnoremap <C-f> :NERDTreeFind<CR>

配置快捷键Ctr + n, Ctrl + t, Ctrl +f

fzf 是一个非常高效实用且美观的命令行工具,并且配置有对应的 vim 插件 fzf.vim。比如下面这个效果图,我们可以让 fzf 在中间进行显示,有点类似于 IDEA 的搜索窗口。

image-20220118164902042

fzf.vim 依赖fzf,安装插件前需要先安装好fzf:

Terminal window
brew install fzf

使用插件:

Terminal window
Plug 'junegunn/fzf', { 'do': { -> fzf#install() } }
Plug 'junegunn/fzf.vim'

NeoVim命令模式下输入:Files :Buffers即可弹出模糊搜索窗口进行搜索

更多信息参考:fzf.vimfzf

Tagbar是一个Vim插件,它提供了一种简单的方法来浏览当前文件的标记并获得其结构的概述。它通过创建一个侧边栏来显示当前文件的ctags-generated标记,并按其范围排序。

image-20220118170918463

安装插件,并配置快捷键F8:

Terminal window
" tagbar
Plug 'majutsushi/tagbar'
nnoremap <F8> :TagbarToggle<cr>

如果您这样做,F8键将切换Tagbar窗口,当然也可以配置任何其他自己想要的快捷键。

更多资料参考:tagbar

主题的话根据个人喜好选择。

Terminal window
Plug 'fatih/molokai'
syntax enable
set t_Co=256
let g:rehash256 = 1
let g:molokai_original = 1
colorscheme molokai
Terminal window
Plug 'vim-airline/vim-airline'
Plug 'vim-airline/vim-airline-themes'

或者

Terminal window
Plug 'morhetz/gruvbox'
" 使用配色方案gruvbox
colorscheme gruvbox

更多主题样式配置,常见:vim-airline gruvbox

使用vim/neogim进行go语言的开发需要vim-go

Terminal window
" go 主要插件
Plug 'fatih/vim-go', { 'tag': '*' }
" go 中的代码追踪,输入 gd 就可以自动跳转
Plug 'dgryski/vim-godef'

更多配置参考:vim-go

代码自动补全,需要使用deoplete.nvim

安装依赖:

Terminal window
pip3 install pynvim
pip3 install msgpack

安装配置插件

Plug 'Shougo/deoplete.nvim', { 'do': ':UpdateRemotePlugins' }
let g:deoplete#enable_at_startup = 1

配置TAB快捷键完成自动提示输入:

Terminal window
" <TAB>: completion.
inoremap <expr><TAB> pumvisible() ? "\<C-n>" : "\<TAB>"

至此重启NeoVim后,自动补全会根据当前文件中已有的名字进行猜测并补全。为了根据不同的语言特性提供更加完善的补全效果,需要安装相应的。 如对于Python可以再安装deoplete-jedi 依赖安装

Terminal window
pip3 install --user jedi --upgrade

插件安装:

Plug 'deoplete-plugins/deoplete-jedi'

修改配置后,使用:PlugInstall命令进行安装完成后重启NeoVim,此时就可以基于jedi在NeoVim上对Python提供完美的自动补全功能。

更多配置参考:deoplete.nvim

vim-startify 为Vim/NeoVim开屏页美化插件,可以记录最近编辑的文件,使用对应数字编号就可以快速打开文件,使用起来非常方便。

image-20220118172551047

安装:

Terminal window
Plug 'mhinz/vim-startify'

完整的配置init.vim

Terminal window
call plug#begin()
" The default plugin directory will be as follows:
" - Vim (Linux/macOS): '~/.vim/plugged'
" - Vim (Windows): '~/vimfiles/plugged'
" - Neovim (Linux/macOS/Windows): stdpath('data') . '/plugged'
" You can specify a custom plugin directory by passing it as the argument
" - e.g. `call plug#begin('~/.vim/plugged')`
" - Avoid using standard Vim directory names like 'plugin'
" Make sure you use single quotes
" The NERDTree is a file system explorer for the Vim editor.
Plug 'preservim/nerdtree'
" indentation guides to all lines
Plug 'lukas-reineke/indent-blankline.nvim'
" fzf
Plug 'junegunn/fzf', { 'do': { -> fzf#install() } }
Plug 'junegunn/fzf.vim'
" 状态栏美化
Plug 'vim-airline/vim-airline'
Plug 'vim-airline/vim-airline-themes'
" tagbar
Plug 'majutsushi/tagbar'
nnoremap <F8> :TagbarToggle<cr>
" vim开屏页美化插件,可以记录最近编辑的文件,使用对应数字编号就可以快速打开文件,使用起来非常方便。
Plug 'mhinz/vim-startify'
" AutoComplete
Plug 'Shougo/deoplete.nvim'
" Python AutoComplete
Plug 'deoplete-plugins/deoplete-jedi'
let g:deoplete#enable_at_startup = 1
" Insert or delete brackets, parens, quotes in pair.
Plug 'jiangmiao/auto-pairs'
" 配色方案
Plug 'morhetz/gruvbox'
call plug#end()
set encoding=utf-8
set tabstop=4
set softtabstop=4
set shiftwidth=4
" 表示Tab自动转换成空格
set expandtab
" 表示换行后自动缩进
set autoindent
" 智能对齐
set smartindent
" 当文件在外部被修改时,自动重新读取
set autoread
" 显示行号、 显示当前行行号,其它行的行号都是以当前行为基准从 1 开始增加(即相对行号)
set number relativenumber
" 设置配色方案
colorscheme gruvbox
let g:airline_theme='gruvbox'
" 启用语法高亮
syntax enable
" 使用黑色主题-light dark
set bg=dark
" vim记住的历史操作的数量,默认的是20
set history=40
" NERDTree
nnoremap <leader>n :NERDTreeFocus<CR>
nnoremap <C-n> :NERDTree<CR>
nnoremap <C-t> :NERDTreeToggle<CR>
nnoremap <C-f> :NERDTreeFind<CR>
" <TAB>: completion.
inoremap <expr><TAB> pumvisible() ? "\<C-n>" : "\<TAB>"
  1. 维基百科-Vim
  2. 精通 VIM ,此文就够了
  3. nerdtree
  4. fzf
  5. him-startify
  6. tagbar
  7. vim-airline
  8. vim-go
  9. neocomplete.vim
  10. programming-go-in-neovim

Python爬虫

Conda 下载安装包进行安装, 使用conda管理Python环境 进行Python环境配置管理。

1.1 Beautiful Soup

Beautiful Soup 是一个可以从HTML或XML文件中提取数据的Python库.它能够通过你喜欢的转换器实现惯用的文档导航,查找,修改文档的方式.Beautiful Soup会帮你节省数小时甚至数天的工作时间.

安装Beautiful Soup

Terminal window
$ conda install beautifulsoup4
#安装解析器lxml
$ conda install lxml
  1. conda install 遇到 “Collecting package metadata (current_repodata.json): failed”
Terminal window
$ conda install lxml (p3) 0 [15:43:43]
Collecting package metadata (current_repodata.json): failed
CondaHTTPError: HTTP 000 CONNECTION FAILED for url <https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/osx-64/current_repodata.json>
Elapsed: -
An HTTP error occurred when trying to retrieve this URL.
HTTP errors are often intermittent, and a simple retry will get you on your way.
'https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/osx-64'

关闭conda的ssl验证即可:

Terminal window
$ conda config --set ssl_verify false
  1. https://conda.io/en/latest/miniconda.html
  2. Beautiful Soup 4.4.0 文档

Docker网络

Docker使用Linux桥接,在主机构建了一个特殊的、可定制的虚拟网络拓扑结构,Docker网桥接口路由,这个网桥接口被成为docker0。 每一个容器都被赋予一个唯一的私有IP地址,从外部的网络是不能直接连接到该私有IP。主机上的每个容器都会连接到docker0,构成一个网络,同时网桥接口docker0会连接到主机所连接的网络上。

Docker安装后自动提供了三种网络,可以使用docker network ls命令查看:

Terminal window
$~ docker network ls
NETWORK ID NAME DRIVER SCOPE
50d276f46ba7 bridge bridge local
f7d70556f900 host host local
7081fbaf563c none null local

所有的Docker容器要符合四种原型中的一种,原型定义了一个容器如何和本地容器、主机网络进行通信,每种原型有不同的目的,隔离程度。

img

  • Closed容器,容器中进程只能访问本地回环接口,不允许任何的网络流量,容器内进程不能访问容器外的网络,容器外程序也无法连接到容器网络接口上。

docker run 命令后添加参数—net none,即可创建Closed容器:

Terminal window
$ docker run --rm --net none alpine ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN qlen 1000
link/ipip 0.0.0.0 brd 0.0.0.0
3: ip6tnl0@NONE: <NOARP> mtu 1452 qdisc noop state DOWN qlen 1000
link/tunnel6 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 brd 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
  • Bridged容器 拥有两个接口:私有的本地回环接口和一个通过网桥连接主机的私有接口。Bridged容器是Docker的默认选项,可定制性最高,被认为是最佳实践。

docker run 命令忽略—net选项,或者—net 值设置为bridge,创建一个Brided容器:

Terminal window
$docker run --rm --net bridge alpine:latest ip addr
$docker run --rm alpine:latest ip addr

从容器中访问阿里DNS服务器:

Terminal window
$ docker run --rm alpine:latest ping -w 2 223.5.5.5 0 [15:36:26]
PING 223.5.5.5 (223.5.5.5): 56 data bytes
64 bytes from 223.5.5.5: seq=0 ttl=37 time=27.976 ms
64 bytes from 223.5.5.5: seq=1 ttl=37 time=31.903 ms
--- 223.5.5.5 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 27.976/29.939/31.903 ms
  • Joined

Joined容器原型隔离程度更低,这些容器共享一个网络栈,容器之间没有任何隔离。

可以使用docker run —network将两个容器进行连接,如下:第一个命令启动一个在本地回环接口监听的服务器,第二个命令连接第一个容器,并列出当前容器所有的开发端口。

Terminal window
$ docker run -d --name tudou --net none alpine:latest nc -l 127.0.0.1:4444
a72ecddb0f461720495960331c36ea29594e7986e87e8fa2f1cfff9f41468301
$ docker run -it --rm --network container:tudou alpine netstat -al
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 0.0.0.0:33957 0.0.0.0:* LISTEN
Active UNIX domain sockets (servers and established)
Proto RefCnt Flags Type State I-Node Path

当两个容器被连接,每一个接口都被共享,因此会引入端口冲突的问题。

Joined容器的使用场景

  1. 想要不同容器上的程序通过本地回环接口进行通信
  2. 一个容器中的程序将要改变Joined网络栈,而另外一个程序将要使用那个被改变的网络栈
  3. 想要监控另外一个容器中某个程序的网络流量
  • Open

Open容器非常危险。它没有网络容器,并对主机网络有完全的访问权。

docker run 命令的—net选项值为host时,就会创建Open容器:

Terminal window
$ docker run --rm --net host alpine ip addr

grpc SSL/TLS

gRPC提供了内置的授权机制(Authorization),也提供接口用于扩展自定义授权验证。gRPC旨在和多种身份验证(Authentication)机制配合使用,可以轻松安全的使用gRPC同其他系统进行通信。

gRPC支持下面几种机制:

  • SSL/TLS:gRPC集成了SSL/TLS,并促进使用SSL/TLS对服务进行身份验证,并对客户端和服务端之间交互的所有数据进行加密
  • ALTS
  • Token-based authentication with Google

同时,也支持扩展自定义认证机制。

如下面一个最基本的Hello grpc程序中, 请求和响应都是明文传输,容易造成敏感信息、伪造篡改等问题。

server.go

package main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
pb "xwxwgo.com/hello-grpc/lib/protos"
)
type Server struct {
pb.UnimplementedHelloServiceServer
}
func (s *Server) SayHello(ctx context.Context, req *pb.HelloReq) (*pb.HelloRsp, error) {
return &pb.HelloRsp{Reply: req.Greeting}, nil
}
func main() {
lis, err := net.Listen("tcp", ":8080")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
server := grpc.NewServer()
pb.RegisterHelloServiceServer(server, &Server{})
server.Serve(lis)
}

client/main.go

package main
import (
"context"
"log"
"google.golang.org/grpc"
pb "xwxwgo.com/hello-grpc/lib/protos"
)
func main() {
conn, err := grpc.Dial(":8080", grpc.WithInsecure())
if err != nil {
log.Fatalf("dail field: %v", err)
}
defer conn.Close()
client := pb.NewHelloServiceClient(conn)
rsp, err := client.SayHello(context.Background(), &pb.HelloReq{Greeting: "Hello grpc"})
if err != nil {
log.Fatalf("call server failed: %v", err)
}
log.Printf("SayHello: %+v", rsp)
}

通过Wireshark本地gRPC抓包,可以看到请求具体信息:

image-20211019163046195

通过SSL/TLS对服务进行身份认证,可以客户端和服务端之间交互的所有数据进行加密。

Terminal window
# 私钥
$ openssl ecparam -genkey -name secp384r1 -out server.key
# 自签公钥
$ openssl req -new -x509 -sha256 -key server.key -out server.pem -days 3650
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:
Email Address []:

Client:

credentials.NewClientTLSFromFile:通过服务端的自签公钥和服务名称,来构造TLS凭证

creds, _ := credentials.NewClientTLSFromFile(certFile, "hello-grpc")
conn, _ := grpc.Dial("localhost:50051", grpc.WithTransportCredentials(creds))
// error handling omitted
client := pb.NewHelloServiceClient(conn)
// ...

Server:

credentials.NewServerTLSFromFile:服务端证书文件和密钥构造 TLS 凭证

creds, _ := credentials.NewServerTLSFromFile(certFile, keyFile)
s := grpc.NewServer(grpc.Creds(creds))
pb.RegisterHelloServiceServer(s, &server.HelloServer{})
lis, _ := net.Listen("tcp", "localhost:50051")
// error handling omitted
s.Serve(lis)

再次进行抓包:

image-20211019164650896

  1. Analyzing gRPC messages using Wireshark
  2. Loopback capture