dokee's site

Back

配置适用于嵌入式开发的 NeovimBlur image

在使用了 Neovim 将近一年后, 我决定重新配置我的 Neovim. 我之前使用的是 NvChad, 它是一个非常好用的 Neovim 配置. 但是由于之前只会装插件, 抄别人配置的时候总有一种稀里糊涂的感觉. 现在正好趁着空闲学习一下 Neovim 的配置, 顺便将它打造得更轻量化和适合自己.

此处非常感谢 Fledge 在 B 站上的教程, 我的配置基本上是根据这个教程来的.

开始之前#

Install#

Arch Linux 下直接使用 pacman 安装即可:

shell
sudo pacman -S neovim # or yay -S neovim-git
shell

其他系统的安装方式见官方文档.

如果之前安装过, 请先备份之前的数据:

shell
mv ~/.config/nvim{,.bak}
mv ~/.local/share/nvim{,.bak}
mv ~/.local/state/nvim{,.bak}
mv ~/.cache/nvim{,.bak}
shell

此外, 你可能想要设置 Neovim 为默认编辑器:

shell
# ~/.zshrc
export VISUAL=nvim
export EDITOR=nvim
shell

使用 :checkhealth 检查安装状态.

Neovim 与 Lua#

Neovim 使用 Lua 作为配置文件的语言. 在开始配置 Neovim 之前, 你可能需要先学习一下 Lua 基本语法. 我的 Wiki 中有一个基础的教程.

你可以从 Neovim 的命令行运行 Lua 代码, 每个 :lua 命令都有自己的作用域.

:lua print("Hello")
:lua ="Hello"
:lua =package
:source ~/programs/baz/myluafile.lua
:source # current buffer
plaintext

Neovim 的配置文件位于 ~/.config/nvim/init.lua, 在这个文件中 require 其他的 module. 在 Neovim 中,lua 目录自动包含在搜索路径 package.path 中. 模块名对应文件名的规则如下:

require("mod") -- lua/mod.lua or lua/mod/init.lua
lua

基础配置#

首先配置一些基础的选项, 在 init.lua 中添加如下语句即可. 使用 :help <option> 获取它们的含义.

这里有几个有意思的配置项:

  • clipboard: 默认情况下按 yd 等是不会将其复制到 wl-clipboard 等系统的剪贴板的, 需要将其设置为 'unnamedplus' 才可以
  • undofileundodir: 默认按 u 撤销只能撤销打开文件之后的操作, 这个配置可以让你在重新打开文件后可以撤销之前的操作

配置按键映射也是类似的, 调用 api 即可. 以下是一些我个人喜好的按键映射.

我们可以将不同类型的配置移到单独的文件中以更好地组织配置文件. 我个人的配置文件组织是这样的:

插件安装#

插件管理器 Lazy.nvim#

Neovim 可以使用 :packadd 命令加载插件, 之后便可以使用 require('the_plugin_name').setup 启用和配置在 runtimepath 中的插件了. 但是这些东西自己管理非常麻烦, 于是我们可以使用 Lazy.nvim 管理和配置插件.

在 Lazy.nvim 的官方文档的安装教程中, 有安装引导程序示例. 将其复制到自己的配置文件中, 保存并重新打开 Neovim, Lazy 会自动安装. 下面是安装引导程序的示例:

上面的部分是用来安装 Lazy 的. 下面的 require("lazy").setup() 是一个常见的表达, 用来传递插件的配置和初始化插件. 配置的表 (即 setup() 的参数) 通常可以在插件的文档中找到. Lazy 本身也是一个插件, 它的全部配置可以在这里找到.

用 Lazy.nvim 安装和配置插件#

有了 Lazy 之后, 我们就可以非常方便地安装和配置插件了. 现在来安装一个主题插件, 以 rose-pine 为例, 只需要将下面这一行添加到 spec 中即可:

{ "rose-pine/neovim", name = "rose-pine" }
lua

这个表中前面的字符串实际上是 Github 仓库的地址. 有了它 Lazy 就可以自动安装插件了. 现在再次重启 Neovim, 安装好过后, 命令行输入 :colorscheme rose-pine-moon 就可以切换主题了. 打开 /home/dokee/.local/share/nvim/lazy 文件夹可以看到你的插件被安装在了这里.

和 Lazy 一样, 我们也可以在文档中找到该插件的所有配置选项. 但是现在的插件的配置和加载是有 Lazy 管理的, 不需要我们来 require, 所以想要传递配置参数的话可以在表中添加 opt 参数, 这就相当于 require("the_plugin_name").setup(opts).

{
    "rose-pine/neovim", 
    priority = 1000,
    name = "rose-pine",
    opts = { 
        styles = {
            bold = true,
            italic = true,
            transparency = true,
        },
        -- other...
    },
}
lua

只要有 opts 参数, Lazy 就会调用 require('the_plugin_name').setup(opts), 哪怕它是 opts = {}. 现在我们想要让这个主题插件加载时自动调用 :colorscheme rose-pine-moon 该怎么办呢? Lazy 提供了另一种配置方式 config, 它的值可以是插件加载后调用的函数, 因此我们可以这样写:

{
    "rose-pine/neovim",
    priority = 1000,
    name = "rose-pine",
    config = function() 
        require("rose-pine").setup {
            styles = {
                bold = true,
                italic = true,
                transparency = true,
            },
        -- other...
        vim.cmd("colorscheme rose-pine-moon")
    end,
}
lua

这样就可以做除了传入配置表之外的事情了. 此外, config 的值如果是 true 的话就会直接执行 require("rose-pine").setup({}).

为了更好地组织配置文件, 我们可以将插件表放在 lua/plugins 文件夹中, 在 spec 中添加 { import = "plugins" } 这一行即可让 Lazy 找到该文件夹下的所有插件配置. 关于插件表的其他描述可以在这里找到.

插件推荐#

下面列举了我个人安装的插件:

感谢所有为 Neovim 插件做出贡献的人!

配置 clangd#

clangd 常见报错及解决#

有关 LSP 等的配置过于复杂, 这里不再赘述. 对于嵌入式设备通常使用 GCC, 但是 GCC 对 LSP 没有支持, 因此 clangd 几乎是唯一的选择. 也有一些常使用 clang 的, 比如 Espressif, 但 GCC 还是多数. 现在 Arm 也有了对 clang 工具链的支持, 但用的人比较少.

混合使用 GCC 和 clangd 可能遇到很多问题, 很不幸的是这些问题暂时可能还没有解决方案, 可以参见 clangd 在 Github 上的 issue.

以一个 STM32 项目为例. 这个项目使用 arm-none-eabi-gccarm-none-eabi-g++ 编译. 在项目能正常编译的情况下, 使用 clangd 却可能出现很多报错.

一个常见的错误可能是 clangd 找不到 (标准库的) 头文件. 根据 clangd 官网的指导, clangd 的正确解析需要在项目的根目录下提供 compile_commands.json 文件, 如果使用 make 可以通过 bear -- make 来获得, 如果使用 cmake 则需要 set(CMAKE_EXPORT_COMPILE_COMMANDS ON). 一般来说此时的头文件就都可以正确识别了.

此时可能还有标准库头文件找不到 (或使用了错误的标准库) 的情况. 官网有对这种情况的说明. 首先, 检查类似下面命令的输出:

shell
arm-none-eabi-gcc -v -c -xc++ /dev/null
shell

在它的输出中可以找到标准库的路径. 然后你可以使用下面的命令来检查它是否正确识别到了 compile_commands.json:

shell
clangd --check=/path/to/a/file/in/your/project.cc [--log=verbose]
shell

一般来说 clangd 会根据 compile_commands.json 中的命令来获得标准库, 如果它正确读取了 compile_commands.json 文件但仍然出错的话, 你可以手动为将 flag -isystem 添加到编译命令中. 创建 .clangd 文件, 添加下面的语句:

CompileFlags:
    Add: [
        -isystem, 
        /usr/lib/gcc/arm-none-eabi/14.2.0/../../../../arm-none-eabi/include/c++/14.2.0, 
        -isystem, 
        /usr/lib/gcc/arm-none-eabi/14.2.0/../../../../arm-none-eabi/include/c++/14.2.0/arm-none-eabi, 
        -isystem, 
        /usr/lib/gcc/arm-none-eabi/14.2.0/../../../../arm-none-eabi/include/c++/14.2.0/backward, 
        -isystem, 
        /usr/lib/gcc/arm-none-eabi/14.2.0/include,
        -isystem, 
        /usr/lib/gcc/arm-none-eabi/14.2.0/include-fixed, 
        -isystem, 
        /usr/lib/gcc/arm-none-eabi/14.2.0/../../../../arm-none-eabi/include
    ]
yaml

现在 clangd 应该就可以正确找到标准库了. 另外一种方法是为 clangd 命令添加 --query-driver="/usr/bin/arm-none-eabi-gcc,/usr/bin/arm-none-eabi-g++" 这样的参数, 你可以使用下面的命令测试它是否正常工作:

shell
clangd --query-driver="/usr/bin/arm-none-eabi-gcc,/usr/bin/arm-none-eabi-g++" --check=/path/to/a/file/in/your/project.cc [--log=verbose]
shell

另一个错误是由于 clangd 实际使用的是内置的 clang parser (cc1), 因此它无法识别 GCC 的内置宏 (如 __GNUC__). 很遗憾, 这个问题暂时没有太好的解决方法 (见 clangd 的 issue #533 等). 我遇到的问题是在 STM32 项目的 cmsis_compiler.h 中由于 clangd 识别 __clang__ 宏导致找不到头文件 cmsis_clang.hm-profile/cmsis_clang_m.h. 解决方法只是将这两个头文件手动复制进去.

还有一种可能的错误是使用了 clang 不兼容的语法, 这个解决方法只能是修改代码了.

插件: clangd_extensions.nvim#

这个插件可以提供许多对于 C/C++ 方便的功能:

  • 头文件/源文件快速切换
  • Inlay hints: 显示 auto 实际类型, 显示参数实际类型等
  • Symbol info: 查看 container
  • Type hierarchy: 显示类型的结构并快速跳转
  • AST: 抽象语法树
  • Completion scores
  • Memory Usage

我目前只用了前三个功能, 下面分享我个人的插件配置, 它放在 clangd 的 on_attach 中.

Reference#

配置适用于嵌入式开发的 Neovim
https://dokee.moe/blog/neovim/neovim-for-embedded-dev
Author dokee
Published at January 20, 2025
Comment seems to stuck. Try to refresh?✨