Neovim 新闻 #12 - Neovim 0.7 的新特性

2022 年 4 月

原文链接:https://gpanders.com/blog/whats-new-in-neovim-0-7

Neovim 0.7 刚刚发布,带来了很多新功能(当然还有大量的错误修复)。你可以在这里找到完整的发布说明这里,但在本文中,我将只介绍一些新功能。

目录

Lua 无处不在!

Neovim 0.5 引入了 Lua 作为 Neovim 生态系统中的一等公民:现在可以在用户的 init 文件、插件、配色方案、ftplugins 等中使用 Lua。基本上,任何可以使用 .vim 文件的地方,现在都可以使用 .lua 来代替。

但是,当时的 Lua API 仍然存在一些不足。特别缺少的是在 Lua 中创建自动命令的能力,以及将键映射直接绑定到 Lua 函数的能力。为了完成这两件事,用户需要诉诸涉及通过 Vimscript 转换的往返操作的解决方法,这有点笨拙。

-- Using a Lua function in a key mapping prior to 0.7
local function say_hello()
    print("Hello world!")
end

_G.my_say_hello = say_hello

vim.api.nvim_set_keymap("n", "<leader>H", "<Cmd>call v:lua.my_say_hello()<CR>", {noremap = true})

自动命令和自定义用户命令的情况类似。

在 Neovim 0.7 中,现在可以直接在 Lua 中使用所有常见的配置原语(键映射、自动命令、用户命令等),无需 Vimscript 转换。这也使得将键映射和自动命令直接绑定到局部 Lua 函数成为可能。

-- Using a Lua function in a key mapping in 0.7
vim.api.nvim_set_keymap("n", "<leader>H", "", {
    noremap = true,
    callback = function()
        print("Hello world!")
    end,
})

-- Creating an autocommand in 0.7
vim.api.nvim_create_autocmd("BufEnter", {
    pattern = "*",
    callback = function(args)
        print("Entered buffer " .. args.buf .. "!")
    end,
    desc = "Tell me when I enter a buffer",
})

-- Creating a custom user command in 0.7
vim.api.nvim_create_user_command("SayHello", function(args)
    print("Hello " .. args.args)
end, {
    nargs = "*",
    desc = "Say hi to someone",
})

你可能注意到 nvim_set_keymap 必须将 Lua 回调设置为最终表参数中的键,而 nvim_create_user_command 可以直接将回调函数作为位置参数传递。这是 Neovim 严格的 API 契约的结果,该契约要求 API 函数在进入稳定版本后,其签名不得以任何方式更改。但是,因为 nvim_create_user_command 是一个新的 API 函数,我们能够通过使其第二个参数接受字符串或函数来增加一点便利性。

Neovim 0.7 还包括一个仅供 Lua 使用的便捷函数 vim.keymap.set,用于轻松创建新的键映射。

vim.keymap.set("n", "<leader>H", function() print("Hello world!") end)

vim.keymap.setnvim_set_keymap 的区别在于以下几点:

  • 它可以接受字符串或 Lua 函数作为其第三个参数。
  • 它默认情况下设置 noremap,因为这是用户 99% 的时间想要的东西。

帮助文档包含更多信息:在 Neovim 中运行 :h vim.keymap.set 以了解更多信息。

最后,用户现在可以使用 API 函数 nvim_set_hl 来修改全局高亮组(等同于使用 :hi),为纯 Lua 配色方案打开了大门。

区分修饰键

作为一款基于终端的应用程序,Neovim 长期以来一直受到终端模拟器的限制,其中之一就是很多键的编码相同,因此对于在终端中运行的应用程序来说无法区分。例如,<Tab><C-I> 使用相同的表示方式,<CR><C-M> 也是如此。这长期以来意味着无法分别映射 <C-I><Tab>:映射一个就必然映射了另一个。

这长期以来一直是让人感到困扰的一点,而且在野外有许多解决方案来解决这个问题。Neovim 使用了 Paul Evans 的 libtermkey,该库反过来利用了 Evans 自己提出的 fixterms 方案,以一种明确的方式对修饰键进行编码。只要控制 Neovim 的终端模拟器以这种方式发送编码的键,Neovim 就可以正确地解释它们。

Neovim 0.7 现在可以正确地区分这些修饰键组合,以便用户现在可以分别映射例如 <Tab><C-I>。此外,Neovim 在启动时发送了一个转义序列,向控制终端模拟器发出信号,表明它支持这种键编码风格。一些终端模拟器(如 iTerm2、foot 和 tmux)使用此序列来以编程方式启用不同的编码。

需要注意的是,这种方式是双向的!你可能会发现,以前映射到 <Tab><C-I>(或 <CR>/<C-M>)的映射不再起作用。然而,修复起来很简单;只需修改你的映射,使用你想要使用的实际键即可。

除了区分这些修饰键对之外,这也启用了以前无法实现的新键映射,例如 <C-;><C-1>

对此的支持在很大程度上取决于你使用的终端,因此这不会影响所有用户。

全局状态栏

Neovim 0.7 引入了一个新的“全局”状态栏,可以通过设置 laststatus=3 来启用。与每个窗口都有一个状态栏不同,全局状态栏始终运行在 Neovim 容器窗口的整个可用宽度上。这使得它对于显示不随窗口更改的信息非常有用,例如 VCS 信息或当前工作目录。许多状态栏插件已经开始利用这个新功能。

filetype.lua

在 Neovim 0.7 中,有一种新的(实验性的)方法可以进行文件类型检测。关于文件类型检测的简要说明:当您第一次启动 Neovim 时,它会在 $VIMRUNTIME 目录中源文件名为 filetype.vim 的文件。此文件创建了数百个 BufRead,BufNewFile 自动命令,其唯一目的是根据有关文件的信息推断文件的类型,最常见的是文件的名字或扩展名,但有时也使用文件的内容。

如果您使用 nvim --startuptime 对启动时间进行分析,您会注意到 filetype.vim 是加载速度最慢的文件之一。这是因为创建如此多的自动命令是昂贵的。另一种进行文件类型检测的方法是,创建一个只针对每个新缓冲区触发的单一自动命令,然后通过一系列顺序步骤尝试匹配文件类型。这就是新的 filetype.lua 所做的事情。

除了使用单个自动命令之外,filetype.lua 还使用基于表的查找结构,这意味着在很多情况下,文件类型检测是在恒定时间内完成的。如果您的 Neovim 是使用 LuaJIT 编译的(很可能就是这样),您还将获得此文件类型匹配的即时编译的好处。

此功能目前是选择加入的,因为它还没有完全匹配 filetype.vim 所覆盖的所有文件类型,尽管它非常接近(我已经独占使用它很多个月,没有任何问题)。有两种方法可以加入此功能

  1. 使用 filetype.lua,但回退到 filetype.vim

    let g:do_filetype_lua = 1 添加到您的 init.vim 文件中。这可以防止文件类型匹配出现任何回归,并确保文件类型始终至少与 filetype.vim 一样好地被检测到。但是,您将为 filetype.luafiletype.vim 都支付启动时间成本。

  2. 只使用 filetype.lua,根本不加载 filetype.vim

    let g:do_filetype_lua = 1let g:did_load_filetypes = 0 添加到您的 init.vim 文件中。这将独占使用 filetype.lua 进行文件类型匹配,并提供上面概述的所有性能优势,但也存在错过文件类型检测的(很小)风险。

除了性能优势外,filetype.lua 还让添加自定义文件类型变得很容易。只需创建一个新的文件 ~/.config/nvim/filetype.lua,然后调用 vim.filetype.add 来创建新的匹配规则。例如

vim.filetype.add({
    extension = {
        foo = "fooscript",
    },
    filename = {
        ["Foofile"] = "fooscript",
    },
    pattern = {
        ["~/%.config/foo/.*"] = "fooscript",
    }
})

vim.filetype.add 接受一个包含 3 个(可选)键的表,分别对应“扩展名”、“文件名”和“模式”匹配。每个表项的值可以是字符串(在这种情况下,它被解释为文件类型)或函数。例如,您可能希望覆盖 Neovim 的默认行为,即始终将 .h 文件分类为 C++ 头文件,方法是使用启发式方法,只有在头文件包含另一个 C++ 风格的头文件(即没有尾随 .h 的头文件)时才将文件类型设置为 C++

vim.filetype.add({
    extension = {
        h = function(path, bufnr)
            if vim.fn.search("\\C^#include <[^>.]\\+>$", "nw") ~= 0 then
                return "cpp"
            end
            return "c"
        end,
    },
})

我们每天都在让 filetype.lua 更接近与 filetype.vim 的完全一致性。目标是在 Neovim 0.8 中将其设为默认值(并能够选择退出使用传统的 filetype.vim)。

客户端-服务器通信

Neovim 0.7 将 neovim-remote 的一些功能引入到核心编辑器中。您现在可以使用 nvim --remote 在已经运行的 Neovim 实例中打开文件。举个例子

# In one shell session
nvim --listen /tmp/nvim.sock

# In another shell session, opens foo.txt in the first Nvim instance
nvim --server /tmp/nvim.sock --remote foo.txt

新的远程功能的一个用例是,能够从主 Neovim 实例的嵌入式终端模拟器中打开文件,而不是在 Neovim 本身内部创建嵌入式 Neovim 实例。

展望 0.8

Neovim 是一个由热心人士组成的松散结构的项目,他们出于兴趣进行工作;因此,任何路线图都始终有点像猜谜游戏。但是,已经有一些正在酝酿中的东西,你可能会在 Neovim 0.8 中看到

新闻

新闻存档中查找更多更新。还有一个RSS 订阅

什么是 Neovim?

Neovim 是一款基于 Vim 的文本编辑器,专为可扩展性可用性而设计,旨在鼓励新的应用程序和贡献

讨论

访问#neovim:matrix.org或 irc.libera.chat 上的 #neovim,与团队聊天。