Neovim 簡介

neovim 從 vim 專案 fork 之後,新增了 async 功能,所有工作不會再被單一線程給擋住 (在過去 linter 檢查時,是無法寫程式碼的),大大提升了可用性和使用者體驗,朝現代編輯器躍進了一大步。neovim 並沒有因此停下腳步,在之後的版本陸續推出了

  • remote plugin:使得寫 plugin 不在受限於 vimscript,可以使用其他語言並且基於 client server 架構和 neovim 溝通,這樣就能好好的利用各個語言生態系的工具,製作出更多有彈性的套件。ex: deoplete.nvim 和 defx.nvim 皆是 python 所撰寫的 remote plugin

  • treesitter 的支援:過去 vim 的 syntax highlight 都是靠 regex,現在可以使用 treesitter ,效能更好,抓詞的時候更準確

  • 支援 lua 寫套件:可以完全使用 lua 撰寫套件 ,脫離怪異的 vimscript,和 remote plugin 不同,remote plugin 是基於 client server 架構通訊

  • built-in LSP client (0.5):內建 LSP client,使用者可以透過 neovim LSP api 和 LSP server 互動

  • 支援 init.lua:可以用 lua 撰寫 vimrc,本章也會用到此功能

2021 的 0.5 版本發佈,除了古老的文字介面,neovim 可說是不折不扣的現代編輯器。

編輯器基本功能

一個現代的編輯器應該有以下幾種基本功能:

  • 客製化 Color scheme

  • Auto completion - 自動補全引擎

  • File manager - 可以在側邊欄看到專案的結構

  • Async linter - 在寫程式碼犯錯的同時,有錯誤提示

  • Background compile - 編譯的同時不應該卡住整個 Editor,要讓使用者能做其他事情

  • Code navigation - 包含跳轉到定義,跳轉到型態定義

  • Code outliner - 當一個檔案寫得很長的時候,怎麼綜觀全局,並且在這些函式中快速跳轉

  • Fuzzy Finder - 透過 fuzzy finder 找到想要的檔案或是專案裡的字串等等

本篇將會使用短小的 lua 設定檔達到基礎 go 語言的開發環境,並產出 init.lua 檔案

預備動作

  • 安裝 neovim 0.5 stable brew install neovim

  • 如果不知道在 neovim 裏如何使用 lua,可參考 nanotee/nvim-lua-guide

  • 本章使用到的 packer.nvim ,可以翻閱一下 README,知道基本用法和操作

init.lua 設置

Color scheme

選一個適合自己眼睛的主題吧,推薦 gruvbox-material

use {
    'sainnhe/gruvbox-material',
    setup = function()
        vim.g.gruvbox_material_background = 'soft'
    end,
    config = function()
        vim.cmd[[colorscheme gruvbox-material]]
    end
}

File explorer - nerdtree

nerdtree

雖然在上篇我不推薦使用 nerdtree,並且端出了需要高度手動配置的 defx.nvim。經過仔細思考之後發現這是個人偏好,不應該拉讀者一起折騰,沒有什麼特殊需求的話,大部分的情況下 nerdtree 是很夠用的,使用者體驗也不錯。老牌的 nerdtree 使用上相對穩定,也會找到較多的討論。

安裝 nerdtree

use {
	'preservim/nerdtree',
    setup = function()
    	-- Read the following nerdtree section and add what you need
    end
}

映射 <F4> 打開或關閉 nerdtree

vim.api.nvim_set_keymap("n", "<F4>", "<cmd>NERDTreeToggle<cr>" ,{silent = true, noremap = true})

在 nerdtree 只要會使用兩個按鍵就能存活

  • ? 打開 quick menu 列出所有基本的案鍵映射

  • m 對游標下的檔案或檔案夾列出基本操作選單

vim-go

強大的 vim-go 提供了各式各樣 go 語言的開發功能,每一個使用 vim 開發 go 的人一定都用過。它包括了 auto completion, code navigation, background compilation, code format…功能,我將會介紹 vim-go 如何使用這些功能

安裝 vim-go

use {
    'fatih/vim-go',
    run = ':GoUpdateBinaries',
    ft = 'go',
    setup = function()
    	-- Read the following section and add what you need
	end
}

Auto-complete

vim-go 提供的 auto completion 使用 omnifunc 實作 (:help omnifunc),omnifunc 使用 <c-x><c-o> 觸發並彈出候選補全選單,按起來很不順手,為了更好的體驗,我將設定成:

  • <tab> 觸發候補選單或是跳到下一個候選,沒有候選時就輸出 tab 字元

  • <s-tab> 觸發候補選單或跳到上一個候選,沒有候選時就刪除 tab 字元

  • <enter> 完成補全,沒有候選時就輸出下一行

local t = function(str)
    return vim.api.nvim_replace_termcodes(str, true, true, true)
end

local check_back_space = function()
    local col = vim.fn.col('.') - 1
    if col == 0 or vim.fn.getline('.'):sub(col, col):match('%s') then
        return true
    else
        return false
    end
end

_G.tab_complete = function()
    if vim.fn.pumvisible() == 1 then
        return t "<C-n>"
    elseif check_back_space() then
        return t "<Tab>"
    else
        return t "<C-x><C-o>"
    end
end

_G.s_tab_complete = function()
    if vim.fn.pumvisible() == 1 then
        return t "<C-p>"
    else
        return t "<C-h>"
    end
end

_G.enter_key = function()
    if vim.fn.pumvisible() == 1 then
        return t "<C-y>"
    else
        return t "<CR>"
    end
end

vim.api.nvim_set_keymap("i", "<tab>", "<C-R>=v:lua.tab_complete()<CR>" ,{silent = true, noremap = true})
vim.api.nvim_set_keymap("i", "<s-tab>", "<C-R>=v:lua.s_tab_complete()<CR>" ,{silent = true, noremap = true})
vim.api.nvim_set_keymap('i', '<enter>', '<C-R>=v:lua.enter_key()<CR>' ,{silent = true, noremap = true})

Code navigation - vim-go

  • :GoDef 或是 gd 跳轉到定義 (gd 映射是 vim-go 內建,不需額外設定)

  • [[ ]] motion

    • [[ 跳到上一個 function 宣告處
    • ]] 跳到下一個 function 宣告處
  • 使用 :GoRefer 或是映射 gr 列出 referrer 到 location list

vim.api.nvim_set_keymap('n', 'gr', '<Plug>(go-referrers)')

Background compile - vim-go

vim-go 提供了編譯和執行的指令,可以輕鬆在 vim 裏編譯、執行

  • :GoRun 跑起 main package

  • :GoBuild 編譯

  • 如果專案使用 Makefile,可以使用 vim-dispatch 參考我另一篇文章

Code format - vim-go

  • :GoFmt 排版程式碼

  • 不想手動可以在每次儲存時自動排版 vim.g.go_fmt_autosave = 1

Async linter - 使用 nvim-lspconfig

diagnostic

安裝 nvim-lspconfig 並且設定 gopls

use {
    'neovim/nvim-lspconfig',
     config = function()
		require('lspconfig').gopls.setup{}
  		-- Read the following lspconfig section and add what you need
     end
}

將 diagnostic 資訊顯示在程式碼旁

diagnostic_config = {
    -- Enable underline, use default values
    underline = true,
    -- Enable virtual text, override spacing to 2
    virtual_text = {
        spacing = 2,
        prefix = '<',
    },
    -- Use a function to dynamically turn signs off
    -- and on, using buffer local variables
    signs = function(bufnr, client_id)
        local ok, result = pcall(vim.api.nvim_buf_get_var, bufnr, 'show_signs')
        -- No buffer local variable set, so just enable by default
        if not ok then
            return true
        end

        return result
    end,
    -- Disable a feature
    update_in_insert = false,
}

vim.lsp.handlers["textDocument/publishDiagnostics"] = vim.lsp.with(
    vim.lsp.diagnostic.on_publish_diagnostics, diagnostic_config)```
        -  diagnostic 之間跳轉
            - 映射 `[d` `]d` 跳轉到上一個和下一個 diagnostic
            - ```plain text
vim.api.nvim_set_keymap('n', '[d', '<cmd>lua vim.lsp.diagnostic.goto_prev()<CR>',{silent = true, noremap = true})
vim.api.nvim_set_keymap('n', ']d', '<cmd>lua vim.lsp.diagnostic.goto_next()<CR>',{silent = true, noremap = true})

Code outline - tagbar

tagbar

安裝 gotags:

$ go get -u github.com/jstemmer/gotags

直接照 gotags 文件的設定

use {
    'preservim/tagbar',
    cmd = 'TagbarToggle',
    setup = function()
    	-- Read the following tagbar section and add what you need
    end
}

直接照 gotags 文件的設定

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

映射 開啟或是關閉 tagbar

vim.api.nvim_set_keymap("n", "<F8>", "<cmd>TagbarToggle<cr>" ,{silent = true, noremap = true})

Fuzzy finder - Telescope

telescope

作為 neovim built-in LSP client 的主要開發者 tjdevries 所主導開發的 fuzzy finder,早晚會成為 neovim 界 fuzzy finder 的主流,在大部分的使用情境下可以取代 fzf.vim,並且使用 lua 開發。

Telescope 的 live_grep 和 find_files 是預設使用 ripgrep 安裝 rigrep

$ brew install ripgrep

安裝 telescope

use {
    'nvim-telescope/telescope.nvim',
    requires = {{'nvim-lua/popup.nvim'}, {'nvim-lua/plenary.nvim'}},
    cmd = 'Telescope',
    setup = function()
    -- Read the following telescope section and add what you need
    end
}

映射 <leader>fg 即時文字搜索

vim.api.nvim_set_keymap('n', '<leader>fg', '<cmd>lua require('telescope.builtin').live_grep()<cr>', {noremap = true})

映射 <leader>ff 尋找 file

vim.api.nvim_set_keymap('n', '<leader>ff', '<cmd>lua require('telescope.builtin').find_files()<cr>', {noremap = true})

結語

最後完成的 init.lua,貼上內容重開 neovim 後,記得使用 PackerSync 安裝套件

本篇的用意和上篇一樣,盡量用精簡的設定檔和少量的套件打造一個短小精幹的 go 語言基礎開發環境,但使用效果和體驗和上篇效果相當,大部分的 go 語言開發功能都透用 vim-go 達成。

雖然我針對了了七個從 coloscheme 到 fuzzy finder 重點功能,但這其實只是個開始,你應該把這篇所寫的設定檔當成一個骨架繼續改進,甚至只要記這七個重點功能的用意,替換成你覺得更棒的,比如說你覺得 fzf 比 telescope 更棒,或是你覺得 ycm 補全比 vim-go 內建的更優秀,那就換吧,往"打造成自己最合適的開發環境"這條道路前進。

參考來源