4.3 KiB
znvim
Zig bindings for Neovim's internal C functions. The ABI follows Neovim's master branch!
Usage
- Add the dependency to your project's
build.zig.zon
:
.{
.name = "my-config",
.version = "0.0.0",
.dependencies = .{
.znvim = .{
.url = "https://mzte.de/git/LordMZTE/znvim/archive/<latest commit hash>.tar.gz",
.hash = "<paste hash here; leave this field out when pasting the URL>",
},
},
}
- Structure your
build.zig
something like this:
const std = @import("std");
pub fn build(b: *std.build.Builder) !void {
const target = b.standardTargetOptions(.{});
if (target.os_tag orelse @import("builtin").os.tag == .windows)
// windows is an error in many ways
return error.Windows;
const mode = b.standardOptimizeOption(.{});
// Build your config as a dynamically linked library.
const lib = b.addSharedLibrary(.{
.name = "my-config",
.root_source_file = .{ .path = "src/main.zig" },
.target = target,
.optimize = mode,
});
// Declare the znvim dependency here.
const znvim_dep = b.dependency("znvim", .{ .target = target, .optimize = mode });
// Add both the nvim_c and znvim modules. You can import these independantly.
lib.addModule("nvim", znvim_dep.module("nvim_c"));
lib.addModule("znvim", znvim_dep.module("znvim"));
// Link libc.
lib.linkLibC();
// LuaJIT is required in order to create a native Lua module neovim can load as well as
// for your Lua API.
lib.linkSystemLibrary("luajit");
// IMPORTANT: without this, lua errors inside your module will cause nvim to SIGABRT!
lib.unwind_tables = true;
// Set the output path to be something sensical.
b.getInstallStep().dependOn(&b.addInstallFile(lib.getOutputSource(), "share/nvim/mzte-nv.so").step);
b.installArtifact(lib);
}
- In your
main.zig
, you want a function calledluaopen_my_config
(wheremy_config
is your module name) This function is loaded by neovim's LuaJIT runtime viadlsym
and called as an entry point on eachrequire
of your module.
const znvim = @import("znvim");
// cImport LuaJIT
const c = @cImport({
@cInclude("lua.h");
@cInclude("lualib.h");
@cInclude("lauxlib.h");
});
pub const std_options = struct {
// You may want to override logFn here in order to redirect std.log log messages to, for example
// nvim notifications. Not doing so will clutter the GUI on log messages.
};
var is_initialized = false;
export fn luaopen_my_config(l: ?*c.lua_State) c_int {
// Make sure we only run initiailzation code once.
// I also recommend putting this into a seperate function you call from Lua once, but that's
// outside the scope of this quick guide.
if (!is_initialized) {
is_initialized = true;
// For example, you could set an option:
// See docs of types inside the znvim module.
znvim.OptionValue.of(true).setLog("number", .both) catch {};
}
c.lua_newtable(l); // create a new lua table to return from `require`
// ... add stuff to that table here
return 1; // 1 indicates one value returned from the lua function
}
- Build your config to
~/.local
zig build \
-Doptimize=ReleaseFast \ # Use ReleaseSafe for easier to debug segfaults :D
-p ~/.local # The output path you set in build.zig will be appended to this.
- In Lua, add the path to your shared object to
package.cpath
:
package.cpath = package.cpath .. ";" .. vim.loop.os_homedir() .. "/.loca/share/nvim/my-config.so"
-- You can now require("my_config")!
WTF???
The whole point of this is so I (and you too!) can write your neovim config in Zig, without going
through the Lua API (or the rather bulky nvim_*
API functions).
The way this works is by compiling your config's Zig part to a native Lua module, which is then loaded using just a few lines of Lua code. Here, you can also create a Lua API to call into.
Structure
znvim is split into 2 modules: nvim_c
and znvim
. The former is a translate-c
'd file of all
of neovim's headers while the latter contains a (work in progress) Zig API for them.
Development
Apart from writing the Zig API, we need to keep nvim_c.zig
updated. This file contains the raw C
bindings. It's generated by generate_bindings.sh
.