重新配置NeoVim IDE

我之前 NeoVim IDE 参考 从零开始配置 Neovim(Nvim) ,原作者更新了博客,配置更为精简。正好我遇到了在 FreeBSD平台配置NeoVim IDE ,所以再次参考这篇博客进行配置。

配置文件路径

nvim 配置目录 ~/.config/nvim ,默认读取 ~/.config/nvim/init.lua ,为方便维护,从 init.lua 划分出不同目标的配置:

nvim 配置目录 ~/.config/nvim 结构
.
├── init.lua
└── lua
    ├── colorscheme.lua
    ├── keymaps.lua
    ├── lsp.lua
    ├── options.lua
    └── plugins.lua

选项配置

  • ~/.config/nvim/lua/options.lua 配置实现功能:

    • 默认采用系统剪贴板,同时支持鼠标操控 Nvim

    • Tab 和空格的换算

    • UI 界面

    • “智能”搜索

~/.config/nvim/lua/options.lua
-- Hint: 如果需要,使用 `:h <option>` 来查找配置含义
vim.opt.clipboard = 'unnamedplus'   -- 使用系统剪贴板
vim.opt.completeopt = {'menu', 'menuone', 'noselect'}
vim.opt.mouse = 'r'                 -- 允许在Nvim中使用鼠标,原文使用 'a' ,不过这样只能在vim内部使用,退出vim就丢失
                                       -- 我修改为 'r' 这样退出vim依然保留剪贴板内容;如果 'r' 无效,则可以尝试 'v' ,实际取决于 vimrc
                                       -- 参考 https://unix.stackexchange.com/questions/139578/copy-paste-for-vim-is-not-working-when-mouse-set-mouse-a-is-on

-- Tab
vim.opt.tabstop = 4                 -- 每个Tab代表的虚拟空格数量
vim.opt.softtabstop = 4             -- 当编辑时空间tab(spacesin tab)代表的空格数量
vim.opt.shiftwidth = 4              -- 在一个tab中插入4个空格
vim.opt.expandtab = true            -- 将tabs转换为空格,这在python有用

-- UI config
vim.opt.number = true               -- 显示绝对数值(也就是行号)
vim.opt.relativenumber = true       -- 在左边显示没一行的行号
vim.opt.cursorline = true           -- 高亮光标水平行下方显示横线
vim.opt.splitbelow = true           -- 打开新的垂直分割底部
vim.opt.splitright = true           -- 在水平分割右方打开
-- vim.opt.termguicolors = true        -- 在TUI激活24位RGB颜色
vim.opt.showmode = false            -- 根据经验,我们不需要 "-- INSERT --" 模式提示

-- Searching
vim.opt.incsearch = true            -- 在输入字符时搜索
vim.opt.hlsearch = false            -- 不要高亮匹配项
vim.opt.ignorecase = true           -- 默认搜索时不区分大小写
vim.opt.smartcase = true            -- 如果搜索时输入一个大写字母则表示搜索区分大小写
  • init.lua 中添加以下配置激活使用 options.lua :

~/.config/nvim/lua/init.lua 中激活 options.lua
require('options')
require('keymaps')
require('plugins')
require('colorscheme')
require('lsp')

键盘映射配置

  • 以下配置 ~/.config/nvim/lua/keymaps.lua 实现如下键盘映射:

    • 使用 <C-h/j/k/l> 在窗口间移动光标

    • 使用 Ctrl + 方向键 来调整窗口大小

    • 在select选择模式,可以使用 Tab 或者 Shift-Tab 来更改连续缩排(indentation repeatedly)

~/.config/nvim/lua/keymaps.lua
-- 定义常用选项
local opts = {
    noremap = true,      -- 非递归
    silent = true,       -- 不显示消息
}

---------------------------
-- 常规模式(Normal mode) --
---------------------------

-- 提示: 查看 `:h vim.map.set()`
-- 最佳窗口导航
vim.keymap.set('n', '<C-h>', '<C-w>h', opts)
vim.keymap.set('n', '<C-j>', '<C-w>j', opts)
vim.keymap.set('n', '<C-k>', '<C-w>k', opts)
vim.keymap.set('n', '<C-l>', '<C-w>l', opts)

-- 通过箭头调整窗口大小
-- 变量: 2 行
vim.keymap.set('n', '<C-Up>', ':resize -2<CR>', opts)
vim.keymap.set('n', '<C-Down>', ':resize +2<CR>', opts)
vim.keymap.set('n', '<C-Left>', ':vertical resize -2<CR>', opts)
vim.keymap.set('n', '<C-Right>', ':vertical resize +2<CR>', opts)

---------------------------
-- 可视模式(Visual mode) --
---------------------------

-- 提示: 以之前区域和相同模式启动相同区域的可视模式
vim.keymap.set('v', '<', '<gv', opts)
vim.keymap.set('v', '>', '>gv', opts)
  • 同样在 init.lua 中添加以下配置激活使用 keymaps.lua :

~/.config/nvim/lua/init.lua 中激活 keymaps.lua
require('options')
require('keymaps')
require('plugins')
require('colorscheme')
require('lsp')

安装插件管理器

nvim 通过第三方插件提供了强大的能力。有多种插件管理器,其中 lazy.nvim 非常受欢迎,提供了很多神奇功能:

  • 修正以来顺序

  • 锁文件 lazy-lock.json 跟踪安装的插件

  • ...

  • 创建 ~/.config/nvim/lua/plugins.lua (这里的案例只完成 lazy.nvim 自身安装,没有指定其他第三方插件)):

~/.config/nvim/lua/plugins.lua 管理插件
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not (vim.uv or vim.loop).fs_stat(lazypath) then
  vim.fn.system({
    "git",
    "clone",
    "--filter=blob:none",
    "https://github.com/folke/lazy.nvim.git",
    "--branch=stable", -- latest stable release
    lazypath,
  })
end
vim.opt.rtp:prepend(lazypath)

require("lazy").setup({})
  • 同样在 init.lua 中添加以下配置激活使用 plugins.lua :

~/.config/nvim/lua/init.lua 中激活 plugins.lua
require('options')
require('keymaps')
require('plugins')
require('colorscheme')
require('lsp')

主题配置

备注

Monokai Pro 开发的 Monokai color scheme 是开发IDE中最流行的语法高亮配色,在 THE HISTORY OF Monokai 一文中有详细的介绍:

  • 2006年荷兰设计师兼开发者Wimer Hazenberg开发出最初的Monokai,主要是TextMate on macOS上暗黑背景的活泼色彩

  • 随后被各个主要IDE所接纳,并且用于终端色彩

  • 2017年发布了Monokai Pro,进一步采用了现代色彩系列,并且包含了用户接口设计和定制图标,提供了色彩过滤器,例如 Spectrum , RistrettoMonokai Classic

  • 2024年发布了Monokai Pro Light,采用了新的 Sun filter,适配了明亮环境,也就是说经过多年发展,Monokai已经完成了主流的 dark 和 light 两种环境适配

在完成了上文 lazy.nvim 配置之后,就可以安装配色插件,这里参考原文使用了 monokai.nvim 插件,并且选择了我对比之后认为较为美观的 monokai 风格:

  • 修订 ~/.config/nvim/lua/plugins.lua ,增加安装 monokai.nvim 的配置行:

~/.config/nvim/lua/plugins.lua 增加 monokai.nvim 插件管理配色
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not (vim.uv or vim.loop).fs_stat(lazypath) then
  vim.fn.system({
    "git",
    "clone",
    "--filter=blob:none",
    "https://github.com/folke/lazy.nvim.git",
    "--branch=stable", -- latest stable release
    lazypath,
  })
end
vim.opt.rtp:prepend(lazypath)

require("lazy").setup({
    "tanvirtin/monokai.nvim",
})
  • 创建一个 ~/.config/nvim/lua/colorscheme.lua 来定制 monokai.nvim 插件:

~/.config/nvim/lua/colorscheme.lua 定制 monokai.nvim 插件
-- define your colorscheme here
local colorscheme = 'monokai'
-- local colorscheme = 'monokai_pro'
-- local colorscheme = 'monokai_soda'
-- local colorscheme = 'monokai_ristretto'

local is_ok, _ = pcall(vim.cmd, "colorscheme " .. colorscheme)
if not is_ok then
    vim.notify('colorscheme ' .. colorscheme .. ' not found!')
    return
end
  • 最后在 ~/.config/nvim/init.lua 激活配置

~/.config/nvim/lua/init.lua 中激活 colorscheme.lua
require('options')
require('keymaps')
require('plugins')
require('colorscheme')
require('lsp')

自动代码补全(Auto-completion)

备注

2024年之后 从零开始配置 Neovim(Nvim) 采用了 blink.cmp 替代了之前使用的自动补全插件 nvim-cmp 。配置更为简单且自动补全快(Rust代码)。

警告

blink.cmp 混合了部分Rust程序代码,所以安装是有平台要求的。如果不是常用平台Linux(x86和arm)/macOS/Windows,那么安装会比较麻烦,需要从源代码编译安装。

之前使用的 nvim-cmp 由于是纯Lua代码,就没有这个困难。

另外, blink.cmp 官方发布的release实际上对 nvim 版本要求很高,我在ARM平台的 Raspberry Pi 系统上安装就遇到 nvim 版本过低无法config的问题。

总之,有利有弊

备注

  • 修订 ~/.config/nvim/lua/plugins.lua 添加:

~/.config/nvim/lua/plugins.lua 添加 blink.cmp 插件配置
... -- 省略其他行
require("lazy").setup({
    ... -- 省略其他行
    {
        "saghen/blink.cmp",
        -- optional: provides snippets for the snippet source
        dependencies = { "rafamadriz/friendly-snippets" },

        -- use a release tag to download pre-built binaries
        version = "*",
        -- AND/OR build from source, requires nightly: https://rust-lang.github.io/rustup/concepts/channels.html#working-with-nightly-rust
        -- build = 'cargo build --release',
        -- If you use nix, you can build from source using the latest nightly rust with:
        -- build = 'nix run .#build-plugin',

        opts = {
            -- 'default' (recommended) for mappings similar to built-in completions (C-y to accept)
            -- 'super-tab' for mappings similar to VSCode (tab to accept)
            -- 'enter' for enter to accept
            -- 'none' for no mappings
            --
            -- All presets have the following mappings:
            -- C-space: Open menu or open docs if already open
            -- C-n/C-p or Up/Down: Select next/previous item
            -- C-e: Hide menu
            -- C-k: Toggle signature help (if signature.enabled = true)
            --
            -- See :h blink-cmp-config-keymap for defining your own keymap
            keymap = {
                -- Each keymap may be a list of commands and/or functions
                preset = "enter",
                -- Select completions
                ["<Up>"] = { "select_prev", "fallback" },
                ["<Down>"] = { "select_next", "fallback" },
                ["<Tab>"] = { "select_next", "fallback" },
                ["<S-Tab>"] = { "select_prev", "fallback" },
                -- Scroll documentation
                ["<C-b>"] = { "scroll_documentation_up", "fallback" },
                ["<C-f>"] = { "scroll_documentation_down", "fallback" },
                -- Show/hide signature
                ["<C-k>"] = { "show_signature", "hide_signature", "fallback" },
            },

            appearance = {
                -- 'mono' (default) for 'Nerd Font Mono' or 'normal' for 'Nerd Font'
                -- Adjusts spacing to ensure icons are aligned
                nerd_font_variant = "mono",
            },

            sources = {
                -- `lsp`, `buffer`, `snippets`, `path`, and `omni` are built-in
                -- so you don't need to define them in `sources.providers`
                default = { "lsp", "path", "snippets", "buffer" },

                -- Sources are configured via the sources.providers table
            },

            -- (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" },
            completion = {
                -- The keyword should only match against the text before
                keyword = { range = "prefix" },
                menu = {
                    -- Use treesitter to highlight the label text for the given list of sources
                    draw = {
                        treesitter = { "lsp" },
                    },
                },
                -- Show completions after typing a trigger character, defined by the source
                trigger = { show_on_trigger_character = true },
                documentation = {
                    -- Show documentation automatically
                    auto_show = true,
                },
            },

            -- Signature help when tying
            signature = { enabled = true },
        },
        opts_extend = { "sources.default" },
    }
})
  • 重启 nvim 后就会自动安装插件并得到初步的自动补全功能

异常排查

我在配置了 blink.cmp 之后,启动遇到一个报错

启动nvim遇到的 blink.cmp 报错
Failed to run `config` for blink.cmp

...share/nvim/lazy/blink.cmp/lua/blink/cmp/config/utils.lua:16: present: expected function: 0x7ffecef73a38, got string (enter)

# stacktrace:
  - vim/shared.lua:0 _in_ **validate**
  - /blink.cmp/lua/blink/cmp/config/utils.lua:16 _in_ **_validate**
  - /blink.cmp/lua/blink/cmp/config/keymap.lua:224 _in_ **validate**
  - /blink.cmp/lua/blink/cmp/config/init.lua:51 _in_ **validate**
  - /blink.cmp/lua/blink/cmp/config/init.lua:116 _in_ **merge_with**
  - /blink.cmp/lua/blink/cmp/init.lua:20 _in_ **setup**
  - plugins.lua:14
  - ~/.config/nvim/init.lua:3

这个问题似乎和 Config failing - preset: expected function: 0x7ff7cc3683b8, got string (default) #881 类似,issue中说明要升级nvim。报告issue的nvim是 NVIM v0.11.0-dev-979+g84623dbe9 ,比我的发行版使用的 NVIM v0.11.0-dev-790+g0fe4362e2 还要新一些,报告升级到 NVIM v0.11.0-dev-1479+g548f19ccc3 解决。

我重新构建了 Debian镜像(tini进程管理器) (包含了编译 neovim 步骤),获得了最新的 NVIM v0.12.0-dev-695+g63a7b92e58 解决了这个异常问题。

LSP

要将 Nvim 作为IDE,需要依赖LSP实现。但是手动安装和配置LSP很麻烦,因为不同的LSP有不同的安装步骤,对后期的管理来说很不方便。所以就有了 mason.nvimmason-ispconfig.nvim 来简化配置:

  • mason.nvim : LSP 管理器,可以实现 LSP 的下载、更新等

  • mason-ispconfig.nvim : 主要功能是处理 mason.nvim 和 nvim-lspconfig 之间名字不一致的问题; mason-lspconfig.nvim 还会自动调用 vim.lsp.enable 启动安装好的 LSP

  • 修改 plugins.lua 添加如下行:

~/.config/nvim/lua/plugins.lua 增加 nason.nvim 相关设置
... -- 省略其他行
require("lazy").setup({
     -- LSP manager
	{ "mason-org/mason.nvim", opts = {} },
    {
        "mason-org/mason-lspconfig.nvim",
        dependencies = {
            "mason-org/mason.nvim",
            "neovim/nvim-lspconfig",
	    	config = function()
	    	    local lspconfig = require("lspconfig")

	    	    lspconfig.pylsp.setup({})
	        end,
        },
        opts = {
            ensure_installed = { "pylsp", "lua_ls", "bashls", "ruby_lsp", "clangd"  },
        },
    },
    ... -- 省略其他行
})

说明:

  • mason.nvim 采用默认配置即可,所以用的是 opts = {}

  • mason-lspconfig.nvim 配置项使用 ensure_installed 确保 pylsp 会被自动安装(pylsp 是 Python 语言的一个 LSP)

  • 通过 nvim-lspconfigpylsp 进行配置

    • 这里没有采用 opts = { ... } 进行插件配置,而是使用 config = function() ... end 自定义一个配置函数,该函数会被自动执行

    • 主要的功能是调用 lspconfig.pylsp.setup({}) ,这里的 {} 表示采用该 LSP 的默认行为

完成上面的配置之后,LSP 已经可用了: 默认安装了 pylsp

  • 编辑 ~/.config/nvim/lua/lsp.lua ,添加一些Mason的特定配置:

配置 LSP ~/.config/nvim/lua/lsp.lua
-- Note: The order matters: mason -> mason-lspconfig -> lspconfig
require("mason").setup({
	ui = {
		icons = {
			package_installed = "✓",
			package_pending = "➜",
			package_uninstalled = "✗",
		},
	},
})

typescript LSP

警告

Mason的LSP支持列表实际上比 nvim-lspconfig 少,所以有些LSP无法通过Mason安装。此外 Mason 是强Linux绑定,对FreeBSD支持不佳,所以在 FreeBSD编程工具 ,我后续将尝试直接使用 nvim-lspconfig 。或者改为使用 helix 编辑器。

要支持更多的LSP,则参考 nvim-lspconfig/doc/configs.md

  • 现在配置非常简单,只需要只需要修订 opts 配置段,在 ensure_installed 加入对应LSP名字就可以了

  • 支持 JavaScriptTypeScript 可以使用 typescript-tools.nvim ,修订 lua/plugins.lua :

安装 typescript-tools.nvim
require("lazy").setup({
    {
      "pmizio/typescript-tools.nvim",
      dependencies = { "nvim-lua/plenary.nvim", "neovim/nvim-lspconfig" },
      opts = {},
    },
})

参考