Skip to content
Go back
Table of Contents

從零開始的 neovim 配置

講講主要開發相關的特性配置

LSP

診斷, 補全和高亮等特性主要依賴 lsp 實現, 現行的配置依賴 nvim >= 0.11, 因為 lsp 是使用 nvim 原生的接口進行配置的. 以前我維護 nvim-lspconfig 的另一個舊分支, 現在已經完全遷移, 那個分支不再維護.

  • 安裝器

我使用 mason 安裝某些 lsp, 只有 clangd 和 tinymist 是通過系統包管理安裝的, 其他都經過 mason 安裝.

mason 並沒有提供自動安裝 lsp 的功能, 需要 mason-lspconfigensure_installed 來自動安裝某些 lsp. 我自己實現了這個函數, 避免引入了多餘的插件.

local function ensure_installed(list)
    local registry = require 'mason-registry'

    local function install_package(pkg_name)
        local ok, pkg = pcall(registry.get_package, pkg_name)
        if not ok then
            vim.notify(('Package %s not found'):format(pkg_name), vim.log.levels.WARN)
            return
        end
        if not pkg:is_installed() then
            vim.notify('Installing LSP: ' .. pkg_name, vim.log.levels.INFO)
            pkg:install():once('closed', function()
                if pkg:is_installed() then
                    vim.schedule(function()
                        vim.notify('LSP installed: ' .. pkg_name, vim.log.levels.INFO)
                    end)
                else
                    vim.schedule(function()
                        vim.notify('Failed to install LSP: ' .. pkg_name, vim.log.levels.ERROR)
                    end)
                end
            end)
        end
    end

    if not registry.refresh then
        -- Old Mason version fallback
        for _, name in ipairs(list) do
            install_package(name)
        end
    else
        -- Newer Mason: async registry refresh
        registry.refresh(function()
            for _, name in ipairs(list) do
                install_package(name)
            end
        end)
    end
end
  • lsp 配置

所有 lsp 的配置都在 nvim 配置根目錄下的 lsp/ 下.

local clangd = {
    filetypes = { 'c', 'cpp' },

    -- 識別項目根目錄的文件
    root_markers = {
        '.git/',
        'clice.toml',
        '.clang-tidy',
        '.clang-format',
        'compile_commands.json',
        'compile_flags.txt',
        'configure.ac', -- AutoTools
    },

    cmd = {
        'clangd',
        '--background-index',
        '--clang-tidy',
        '--header-insertion=iwyu',
        '--completion-style=detailed',
        '--function-arg-placeholders=true',
        '-j=4',
        '--fallback-style="{BasedOnStyle: LLVM, IndentWidth: 4}"',
    },

    capabilities = {
        textDocument = {
            completion = {
                editsNearCursor = true,
                completionItem = { snippetSupport = false },
            },
        },
        offsetEncoding = { 'utf-8', 'utf-16' },
    },
    reuse_client = function(client, config)
        return client.name == config.name
    end,
    settings = {
        clangd = {
            Completion = {
                CodePatterns = 'NONE',
            },
        },
    },

    ---@class ClangdInitializeResult: lsp.InitializeResult
    ---@field offsetEncoding? string

    ---@param init_result ClangdInitializeResult
    on_init = function(client, init_result)
        if init_result.offsetEncoding then
            client.offset_encoding = init_result.offsetEncoding
        end
    end,

    on_attach = function(_, buf)
        vim.api.nvim_buf_create_user_command(buf, 'LspClangdSwitchSourceHeader', function()
            switch_source_header(buf)
        end, { desc = 'Switch between source/header' })

        vim.api.nvim_buf_create_user_command(buf, 'LspClangdSymbolInfo', function()
            symbol_info()
        end, { desc = 'Show symbol info' })
    end,
}

return clangd

大部分 lsp 的配置都可以在 nvim-lspconfig 的倉庫下找到默認的, 直接抄就行了.

Tree-sitter

tree-sitter 主要作为辅助的高亮手段, 也提供提纲(outline) 之类的功能. 使用 nvim-treesitter 插件.

代码补全和自定義片段

代码补全需要使用 blink 插件進行增強. 同時使用 blink.cmp 提供的 snippet 來自定義片段, 例如刷 leetcode 需要的代碼框架和 html 標籤等.

local CodeCompletion = {
    'saghen/blink.cmp',
    dependencies = {
        'catppuccin/nvim',
    },

    event = { 'BufReadPost', 'BufNewFile' },

    version = '1.*',
    opts = {
        keymap = {
            preset = 'default',
            -- Conflict with cursor move under insert mode?
            -- ['<C-g>'] = { 'show', 'show_documentation', 'hide_documentation' },
            ['<C-k>'] = { 'show', 'show_documentation', 'hide_documentation' },
            ['<C-e>'] = { 'hide' },
            ['<Up>'] = { 'select_prev', 'fallback' },
            ['<Down>'] = { 'select_next', 'fallback' },
            ['<C-p>'] = { 'snippet_backward', 'fallback_to_mappings' },
            ['<C-n>'] = { 'snippet_forward', 'fallback_to_mappings' },
            ['<Tab>'] = { 'select_next', 'fallback' },
            ['<S-Tab>'] = { 'select_prev', 'fallback_to_mappings' },
            ['<C-b>'] = { 'scroll_documentation_up', 'fallback' },
            ['<C-f>'] = { 'scroll_documentation_down', 'fallback' },
            ['<CR>'] = { 'accept', 'fallback' },
        },

        appearance = {
            nerd_font_variant = 'normal',
        },

        completion = {
            menu = {
                border = 'rounded',
            },
            documentation = {
                auto_show = true,
                window = {
                    border = 'rounded',
                },
            },
            list = {
                selection = {
                    preselect = false,
                    auto_insert = true,
                },
            },
        },

        sources = {
            default = { 'snippets', 'lsp', 'path', 'buffer' },
            providers = {
                snippets = {
                    opts = {
                        friendly_snippets = false,
                        extended_filetypes = {
                            astro = { 'html' },
                            markdown = { 'blog', 'html' },
                            zsh = { 'sh' },
                        },
                    },
                },
            },
        },

        -- (Default) Rust fuzzy matcher for typo resistance and significantly better performance
        -- You may use a lua implementation instead by using `implementation = "lua"` or fallback to the lua implementation,
        -- when the Rust fuzzy matcher is not available, by using `implementation = "prefer_rust"`
        --
        -- See the fuzzy documentation for more information
        fuzzy = { implementation = 'prefer_rust_with_warning' },
    },
    opts_extend = { 'sources.default' },
}

return CodeCompletion

格式化

格式化依賴 conform.nvim 插件.


local CodeFormatter = {
    'stevearc/conform.nvim',
    event = { 'BufReadPost', 'BufNewFile' },
    opts = {},

    config = function()
        require('conform').setup {
            formatters_by_ft = {
                lua = { 'stylua' },
                cpp = { 'clang-format' },
                c = { 'clang-format' },
                json = { 'prettier' },
                jsonc = { 'prettier' },
                html = { 'prettier' },
                css = { 'prettier' },
                astro = { 'prettier' },
                typescript = { 'prettier' },
                javascript = { 'prettier' },
            },
        }

        local do_format = function()
            require('conform').format { async = true, lsp_fallback = true }
        end

        vim.keymap.set('n', '<leader>lf', do_format, { desc = 'Format Current Buffer', noremap = true, silent = true })

        vim.api.nvim_create_user_command('Format', do_format, { desc = 'Format Current Buffer' })
    end,
}

return CodeFormatter

工作區補丁

vscode 這樣的編輯器在項目的根目錄下有一個 .vscode 目錄作為工作區的本地配置, 覆蓋用戶的默認配置. 這在某些時候非常有用. 我主要做 c/cpp 開發, 很多項目的縮進規則都不一樣, 就需要在工作區進行配置. 很多項目下都可以配置一些預設的命令在工作區執行, 比如代碼格式化和編譯, 單元測試. leetcode 也需要運行測試命令. 工作區補丁會被放置在項目根目錄下的 .nvim.vscode/nvim 下. 設置後者的原因是大部分項目的 .gitignore 都有 .vscode 但是沒有 .nvim.

.nvim
├──  ftplugin
└──  init.lua

工作區補丁的結構很簡單, ftplugin 用來存放文件類型加載的代碼, init.lua是入口點.

其他

Markdown

主要使用 render-markdownmarkdown-preview, 我不推薦 在終端內渲染 Markdown 文檔中的圖片和公式, 儘管有插件能夠做到這些, 但是這些特性的效果很大程度上是取決於終端的.