# znvim Zig bindings for Neovim's internal C functions. The ABI follows Neovim's master branch! ## Usage 1. Add the dependency to your project's `build.zig.zon`: ```zig .{ .name = "my-config", .version = "0.0.0", .dependencies = .{ .znvim = .{ .url = "https://mzte.de/git/LordMZTE/znvim/archive/.tar.gz", .hash = "", }, }, } ``` 2. Structure your `build.zig` something like this: ```zig 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); } ``` 3. In your `main.zig`, you want a function called `luaopen_my_config` (where `my_config` is your module name) This function is loaded by neovim's LuaJIT runtime via `dlsym` and called as an entry point on each `require` of your module. ```zig 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 } ``` 4. Build your config to `~/.local` ```bash 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. ``` 5. In Lua, add the path to your shared object to `package.cpath`: ```lua 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`.