https://github.com/notashelf/rns
Exprimental library for enabling Neovim configuration in C
https://github.com/notashelf/rns
Last synced: 11 months ago
JSON representation
Exprimental library for enabling Neovim configuration in C
- Host: GitHub
- URL: https://github.com/notashelf/rns
- Owner: NotAShelf
- Created: 2025-02-09T17:25:24.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2025-02-09T23:50:10.000Z (over 1 year ago)
- Last Synced: 2025-02-19T13:48:22.063Z (over 1 year ago)
- Language: Rust
- Homepage:
- Size: 11.7 KB
- Stars: 13
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# RNS
A library for writing Neovim configurations in systems programming languages
([C](#c), [Zig](#zig), or with Rust). RNS provides a consistent C-compatible FFI
layer that lets you craft your Neovim setup using compiled languages instead of
Lua. _Highly_ experimental, almost fully untested. If you look at this and go
"_wow what a great idea, I will start using this project right this instance!_"
I have unfortunate news for your friends and family and it involves you being
sent to a mental hospital.
Greatly inspired by the awesome [CatNvim](https://github.com/rewhile/CatNvim),
redesigned as a more generic and extensible
## Features
- Write Neovim configurations in C, Zig, or Rust
- Plugin management API (register, install, update)
- Structured plugin configuration system
- LSP server configuration helpers
- Keymapping creation with a clean API
- Autocommand and autogroup management
- Option setting with appropriate type enforcement
## Usage
> [!WARNING]
> The API might change at any given time and without _any_ notice. This is not
> intended for public use (or any use, really) and I will not make any attempts
> to keep the library or its API stable.
Build RNS as a shared library:
```bash
# This will create target/release/librns.so
cargo build --release
```
Move it to your working directory, and you may begin interfacing with the
resulting shared library in C.
```bash
# Copy to working directory and create include directory
# You could also symlink it if you plan to hack on RNS.
cp target/release/librns.so ./librns.so
mkdir -p include
cp rns.h include/
```
Write your configuration in C, and then compile it with `librns` in your library
path. Note that you will also need `librns.so` in Neovim's `LD_LIBRARY_PATH`
later on.
```bash
gcc -o config.so -shared -fPIC init.c -Ltarget/release -lrns -Wl,-rpath,./ -I./include
```
Now you can load your new, compiled configuration:
```lua
-- init.lua
package.cpath = package.cpath .. ";./?.so"
local config = require("config")
```
```bash
# Ensure that librns.so is in Neovim's library path before
# you run this. Otherwise your configuration will not be
# loaded. Modifying cpath is a way of doing this but you
# might also want to set it manually.
nvim --clean -u path/to/your/init.lua
```
Now you may evaluate your life choices and consider how you even got here!
Enjoy.
### C
This is an example in C, just to give you an idea. I haven't done much to make
installation or consumption of this library ergonomic, but you are welcome to
submit pull requests or open issues to discuss your ideas :)
Just to give you an idea of the truly awesome API. Please consider writing your
own configurations if you end up using this :)
```c
#include "include/rns.h"
#include
#include
#include
#include
#include
#include
extern int luaopen_init(lua_State *L);
static int set_config(lua_State *L) {
nvim_set_global("mapleader", " ");
nvim_set_global("maplocalleader", ",");
nvim_set_option_bool("number", 1);
nvim_set_option_bool("relativenumber", 1);
nvim_set_option_bool("expandtab", 1);
nvim_set_option_int("tabstop", 2);
nvim_set_option_int("shiftwidth", 2);
nvim_set_option_bool("cursorline", 1);
nvim_set_option_string("signcolumn", "yes");
nvim_exec_command("colorscheme habamax");
return 0;
}
static int setup_keymaps(lua_State *L) {
nvim_create_keymap("n", "w", ":w", NULL);
nvim_create_keymap("n", "q", ":q", NULL);
nvim_create_keymap("n", "", "h", NULL);
nvim_create_keymap("n", "", "j", NULL);
nvim_create_keymap("n", "", "k", NULL);
nvim_create_keymap("n", "", "l", NULL);
return 0;
}
static int setup_autocmds(lua_State *L) {
nvim_create_augroup_lua("MySettings", 1);
nvim_create_autocmd_lua("FileType", "markdown,text",
"setlocal wrap linebreak", "MySettings");
nvim_create_autocmd_lua("FileType", "rust", "setlocal tabstop=4 shiftwidth=4",
"MySettings");
return 0;
}
static int register_plugins(lua_State *L) {
register_plugin("nvim-lspconfig", "https://github.com/neovim/nvim-lspconfig");
register_plugin("telescope",
"https://github.com/nvim-telescope/telescope.nvim");
register_plugin("plenary", "https://github.com/nvim-lua/plenary.nvim");
return 0;
}
static int setup_plugin_install(lua_State *L) {
install_plugins();
return 0;
}
static int configure_plugins(lua_State *L) {
// Configure LSP in a structured way
plugin_config_begin("nvim-lspconfig");
plugin_config_add_server("rust_analyzer");
plugin_config_set_server_option("rust_analyzer", "checkOnSave.command",
"clippy");
plugin_config_end();
// Configure telescope in a structured way
plugin_config_begin("telescope");
plugin_config_set_mapping("telescope", "i", "", "move_selection_next");
plugin_config_add_keymap("n", "ff", "telescope", "find_files");
plugin_config_end();
// Load all plugin configurations
load_plugin_configs();
return 0;
}
__attribute__((visibility("default"))) int luaopen_config(lua_State *L) {
luaopen_init(L);
luaL_Reg funcs[] = {{"set_options", set_config},
{"setup_keymaps", setup_keymaps},
{"setup_autocmds", setup_autocmds},
{"register_plugins", register_plugins},
{"install_plugins", setup_plugin_install},
{"configure_plugins", configure_plugins},
{NULL, NULL}};
luaL_newlib(L, funcs);
return 1;
}
```
**compile your configuration** and then load it in your `init.lua`:
```lua
-- Basic setup
package.cpath = package.cpath .. ";./?.so"
-- Load the config module
local config = require("config")
-- Print debug info
print("Configuration process completed")
```
If everything went well, Neovim will load without errors.
### For Nix Users
A basic flake.nix is provided by this repository. It is the only supported
version of consuming RNS as a 3rd party library.
```nix
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs?ref=nixos-unstable";
rns = {
url = "github:NotAShelf/rns";
inputs.nixpkgs.follows = "nixpkgs";
};
};
}
```
You may build `inputs.rns.packages..default` to access the dynamic
library and the header found in `include/`
### Zig
Zig "support" is still experimental, because I'm quite new to Zig. If you know
anything better, feel free to submit a pull request :)
```zig
// build.zig
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const lib = b.addSharedLibrary(.{
.name = "config",
.root_source_file = b.path("config.zig"),
.target = target,
.optimize = optimize,
});
// Add include path for rns.h
lib.addIncludePath(b.path("."));
// Link against local librns.so shared library
const lib_path = b.path("./librns.so");
lib.addObjectFile(lib_path);
// Add C library dependency
lib.linkLibC();
// Install the artifact
b.installArtifact(lib);
}
```
```zig
// config.zig
const std = @import("std");
const c = @cImport({
@cDefine("_GNU_SOURCE", "1");
@cInclude("include/rns.h");
});
export fn configure() c_int {
// Set basic options
_ = c.nvim_set_global("mapleader", " ");
_ = c.nvim_set_option_bool("number", 1);
_ = c.nvim_set_option_bool("relativenumber", 1);
_ = c.nvim_set_option_bool("expandtab", 1);
// Set colorscheme
_ = c.nvim_exec_command("colorscheme habamax");
// Register plugins
_ = c.register_plugin("telescope", "https://github.com/nvim-telescope/telescope.nvim");
_ = c.register_plugin("plenary", "https://github.com/nvim-lua/plenary.nvim");
// Install plugins
_ = c.install_plugins();
// Configure telescope
_ = c.plugin_config_begin("telescope");
_ = c.plugin_config_set_mapping("telescope", "i", "", "move_selection_next");
_ = c.plugin_config_add_keymap("n", "ff", "telescope", "find_files");
_ = c.plugin_config_end();
return 0;
}
// Lua module entry point
export fn luaopen_libconfig(L: ?*anyopaque) c_int {
// Actually use the parameter to avoid compiler warning
if (L != null) {}
_ = configure();
return 1;
}
```
```lua
-- init.lua
-- Load the RNS library
local ffi = require("ffi")
-- Load the configuration library
local config = package.loadlib("./zig-out/lib/libconfig.so", "luaopen_libconfig")
if not config then
error("Failed to load libconfig.so")
end
config() -- Initialize the config
-- Or alternatively, load it directly without handling the configuration via Lua
-- local config = require('libconfig')
-- You can also call individual RNS functions if needed
-- Example:
-- ffi.cdef[[
-- int nvim_set_option_bool(const char *name, int value);
-- int register_plugin(const char *name, const char *url);
-- ]]
-- local rns = ffi.load('./librns.so')
-- rns.nvim_set_option_bool("number", 1)
-- rns.register_plugin("telescope", "https://github.com/nvim-telescope/telescope.nvim")
```
### Assembly
Are you thinking to yourself "_what the fuck?_" right now? I am.
```asm
; config.asm
section .data
mapleader db " ", 0
lua_cmd db "vim.g.mapleader = ' '", 0
number_cmd db "vim.opt.number = true", 0
expandtab_cmd db "vim.opt.expandtab = true", 0
colorscheme_cmd db "vim.cmd('colorscheme habamax')", 0
section .note.GNU-stack
section .text
global configure:function
global luaopen_config:function
extern exec_lua
configure:
push rbp
mov rbp, rsp
; Use exec_lua to run Lua commands instead of direct C functions
lea rdi, [rel lua_cmd]
call exec_lua wrt ..plt
lea rdi, [rel number_cmd]
call exec_lua wrt ..plt
lea rdi, [rel expandtab_cmd]
call exec_lua wrt ..plt
lea rdi, [rel colorscheme_cmd]
call exec_lua wrt ..plt
xor eax, eax
pop rbp
ret
luaopen_config:
push rbp
mov rbp, rsp
call configure
mov eax, 1
pop rbp
ret
```
Compile your configuration
```bash
nasm -f elf64 -o config.o config.asm # For x86_64 Linux
```
and ccompile with embedded library path
```bash
gcc -shared -o config.so config.o -L. -lrns -Wl,-rpath,'$ORIGIN'
```
```lua
-- init.lua
-- Configure library paths
package.cpath = package.cpath .. ";./?.so"
-- Use FFI to preload librns.so
local ffi = require("ffi")
ffi.cdef[[void *dlopen(const char *filename, int flag);]]
local lib = ffi.C.dlopen("./librns.so", 2) -- RTLD_NOW = 2
-- Load our assembly config
local status, config = pcall(require, "config")
if not status then
error("Failed to load config: " .. tostring(config))
end
```
## Why?
Teehee.