本篇設定部分已經過時,請看 Golang 開發環境 - 使用 neovim 0.5

你確定要用 neovim 開發 golang?

現在是 vscode 稱霸天下的時代,理所當然的 (neo)vim 也常常被拿來 vscode 比較,vscode 內建整合了 git、debugger、自動補全引擎、terminal、extension manager,另外 Golang 在 vscode 上的 extension 支援相當良好: 包括跳轉到定義 (go to definition)、重新命名 (rename)、跳轉到型態定義 (go to type definition)⋯⋯ 一堆細節功能,近期由於 google golang team 接手 go 語言的 vscode extension,相信使用 vscode 開發是不錯的選擇。

(neo)vim 當然也能達到 vscode 的類似功能,但是必須花時間設定調教,在 vscode 問世之前我會推薦 (Neo)vim 給我的朋友,vscode 問世之後我就開始推薦 vscode。

本文給那些本來就在 (neo)vim 裡打滾的人,但對 go 語言設定不熟的人, 如果你是一張白紙,而且沒有被虐的癖好,我建議選擇 vscode,可以用較少的時間進入到寫程式的環節,而不是一直在花時間在設定,這篇文你也沒必要看下去。

預備動作

Must have

  • 你應該先安裝好 neovim nightly 版本,brew install neovim --HEAD
  • 你應該要知道怎麼使用 vim-plug,至少要知道 :PlugInstall
  • 你應該先把 go 語言環境安裝好

Nice to have

  • 你知道 :checkhealth:UpdateRemotePlugin
  • 你熟悉 :help 的使用方式,這裡有一篇好文:Learn to use help

開發環境基本功能

一個好用的 Editor 我認為應該包含以下幾點基本功能:

  • Auto completion - 好的自動補全引擎
  • File manager - 可以在側邊欄看到專案的結構
  • Real time linter - 在寫 code 犯錯的同時,有一些提示 (也就是 diagnostic 功能)
  • Background compile - 編譯的同時不應該卡住整個 Editor,要讓使用者能做其他事情
  • Code navigation: 包含跳轉到定義,跳轉到型態定義,或是當一個檔案寫得很長的時候,怎麼綜觀全局

以下我將會介紹我使用的 Plugin,達到我上述所提到的功能。 以下使用的皆是 Neovim nightly 版本,使用 stable 是沒有官方 LSP 支援的。

Note:neovim 是由 vim 所 fork 出來的,差異介紹請看這篇,某些 Plugin 不會同時相容於兩者,我接會標明。

File explorer (defx.nvim)

為什麼不用老牌的 nerdtree 就好,要使用別的 file explorer, 沒辦法 nerdtree 就是慢,只要專案稍微有一點規模,就會有明顯的卡頓。

defx 由 python3 寫成,是一個需要高度手動設定的 file explorer,不像 nerdtree 幾乎可以開箱及用,在設定時一定得查看 defx 的 help 文件,裡頭有相當多設定範例。

為了讓 neovim 知道 python3 在哪,將此行寫入 vimrc:

let g:python3_host_prog = '/path/to/python3'

設定完此行之行 執行 :checkhealth,neovim 就會開始查看有沒有抓到 python3

安裝

Plug 'Shougo/defx.nvim'

安裝完後執行 :UpdateRemotePlugins

使用 F4 映射到 defx

noremap <silent><F4> <cmd>Defx -buffer-name="defx"<cr>

外觀設定

call defx#custom#column('icon', {
            \ 'directory_icon': '▸',
            \ 'opened_icon': '▾',
            \ 'root_icon': '📁 ',
            \ })


call defx#custom#column('filename', {
            \ 'min_width': 128,
            \ 'max_width': 128,
            \ })

call defx#custom#option('_', {
            \ 'columns': 'mark:indent:icon:filename:type',
            \ 'split': 'vertical',
            \ 'winwidth': 35,
            \ 'direction': 'topleft',
            \ 'resume': v:false,
            \ 'toggle': v:true
            \ })

使用 enter 打開或關閉資料夾:

nnoremap <silent><buffer><expr> <CR>
            \ defx#is_directory() ?
            \ defx#do_action('open_or_close_tree') :
            \ defx#do_action('drop')

因為 defx 的高度彈性,以及舊有的 nerdtree使用習慣,所以我就把 defx 的 key mapping 設定成類似 nerdtree,這裡就只做基本設定,以後有空會多開一篇詳細講 defx 設定,如果有興趣請參考我的設定

Note: defx (neovim only)

類似 Plugin: nerdtree (vim and neovim), vim-dirvish (vim and neovim)

Auto completion (completion-nvim) & diagnostic (diagnostic-nvim)

completion-nvim 和 diagnostic-nvim 都是基於 nvim-lspconfig 的 Plugin, 可以把 nvim-lspconfig 當成和 language server 溝通的基礎設施,Golang 預設的 language server 當然就是 gopls 啦

安裝

Plug 'neovim/nvim-lspconfig'
Plug 'nvim-lua/diagnostic-nvim'
Plug 'nvim-lua/completion-nvim'

在 lsp client 時,啟動 completion-nvimdiagnostic-nvim

lua << EOF
    local nvim_lsp = require'nvim_lsp'
    local on_attach_vim = function(client, bufnr)
        require'completion'.on_attach(client, bufnr)
        require'diagnostic'.on_attach(client, bufnr)
    end
    nvim_lsp.gopls.setup{
        on_attach=on_attach_vim
    }
EOF

diagnostic-nvim 範例設定:

call sign_define("LspDiagnosticsErrorSign", {"text" : "E", "texthl" : "LspDiagnosticsError"})
call sign_define("LspDiagnosticsWarningSign", {"text" : "W", "texthl" : "LspDiagnosticsWarning"})
call sign_define("LspDiagnosticsInformationSign", {"text" : "I", "texthl" : "LspDiagnosticsInformation"})
call sign_define("LspDiagnosticsHintSign", {"text" : "H", "texthl" : "LspDiagnosticsHint"})

let g:diagnostic_show_sign = 1
let g:diagnostic_enable_virtual_text = 1
let g:diagnostic_insert_delay = 0
let g:diagnostic_virtual_text_prefix = '<'

效果:

如果覺得字還沒打完就有警告很煩,可以改變此選項 let g:diagnostic_insert_delay = 1,則會在進入 normal mode 時進行警告。

completion-nvim 範例設定:

let g:completion_chain_complete_list = [
            \{'complete_items': ['lsp']},
            \]

效果

Note: nvim-lspconfig, completion-nvim, diagnostic-nvim 皆不相容 vim

LSP 相關 Plugin: vim-lsp (vim only), LanguageClient-neovim (neovim only)

Completion 相關 Plugin: ncm2 (vim8 and neovim), deoplete.nvim (vim8 and neovim)

Diagnostic 相關 Plugin: neomake (neovim and vim8), ale (neovim and vim8)

看了 completion-nvim 的 contribution 大部分是由 haorenW1025 大大所寫,帳號掛 NTU,台灣人寫的套件還不支持一波

Code Outline (tagbar)

當 code 越寫越多,一個 file 越長越大,一個 file 超過 1000 行,或超過 15 個 function,就需要一個綜觀全局的 Plugin,這當然就需要交給 tagbar 了。

另外一種情境,當你不熟你手上的這份專案時,用 tagbar 先進行大綱式的觀察,是再好不過的方式了

首先先安裝 gotags, gotags 在 Readme 上都寫好怎麼設定了。

安裝 Tagbar:

Plug 'majutsushi/tagbar'

gotags 及 tagbar 設定:

let g:tagbar_type_go = {
            \ 'ctagstype' : 'go',
            \ 'kinds'     : [
            \ 'p:package',
            \ 'i:imports:1',
            \ 'c:constants',
            \ 'v:variables',
            \ 't:types',
            \ 'n:interfaces',
            \ 'w:fields',
            \ 'e:embedded',
            \ 'm:methods',
            \ 'r:constructor',
            \ 'f:functions'
            \ ],
            \ 'sro' : '.',
            \ 'kind2scope' : {
            \ 't' : 'ctype',
            \ 'n' : 'ntype'
            \ },
            \ 'scope2kind' : {
            \ 'ctype' : 't',
            \ 'ntype' : 'n'
            \ },
            \ 'ctagsbin'  : 'gotags',
            \ 'ctagsargs' : '-sort -silent'
            \ }

Go development plugin (vim-go)

Golang 在 vim 的開發中,不可能不提 vim-go 了,應該也是我在 vim plugin 看過支援完整的開發工具之一了,vim-go 設定眾多,我只挑出了我覺得最有用的幾個出來談。

安裝

Plug 'fatih/vim-go',{'for':'go', 'do': ':GoUpdateBinaries'}

golangci-lint 是把不一樣的 go 語言 linter 集合起來成一包,在 teminal 打 golangci-lint linters 就會列出一堆 linter,挑選自己想要的 linter 放入 g:go_metalinter_enabled 陣列裡。

let g:go_metalinter_command = "golangci-lint"
let g:go_metalinter_enabled = ['vet', 'errcheck', 'staticcheck', 'gosimple']

使用 :GoMetaLinter 開始對專案進行檢查。

當然 code 越大或是 linter 放得越多檢查越慢,所以我通常寫到一個段落才下此命令,linter 檢查完後 quickfix list 則會跳出,請看demo:

code navigation 設定,不使用 vim-go 預設的 key mapping,使用自己習慣的方式:

let g:go_def_mapping_enabled = 0
autocmd Filetype go nmap <buffer> gd <Plug>(go-def)
autocmd Filetype go nmap <buffer> gD <Plug>(go-describe)
autocmd Filetype go nmap <buffer> gR <Plug>(go-rename)
autocmd Filetype go nmap <buffer> gr <Plug>(go-referrers)

最屌的是 vim-go 能直接跑 Testing (:GoTest), Debugger (:GoDebugStart), Run (:GoRun), Build (:GoBuild)

autocmd Filetype go nmap <buffer> <f4> <Plug>(go-test)
autocmd Filetype go nmap <buffer> <f5> <Plug>(go-build)
autocmd Filetype go nmap <buffer> <f6> <Plug>(go-run)

:GoTest demo:

vim-go 設定一定要看過 vim-go-tutorial,由 vim-go 作者提供的設定教學。

結語

如果要一個適合自己的 (neo)vim 環境,當然這篇文章只是冰山一角而已。這裡提到的 Plugin,每一個都可以開一篇文章來介紹,所以每一個 Plugin 我盡量用最簡單的範例設定,展現它的功能和精髓。另外還有 fuzzy finder, auto pair, indent line guide, snippet ..等 Plugin, 還沒提到,之後會分好幾篇文章來寫。

在配置設定的一開始,盡量先去抄別人的,直接看看抄了設定會發生什麼效果, 或是使用 :help 查詢看看。

Q & A

要去哪裡查到別人的 vim 設定? 在 github 上查詢關鍵字:dotfile, dotfiles, vimrc, vim-config, nvimrc 等等的字。 這裡我就提供兩個大神的 vim 設定:

Reference