use std::ffi::{CStr, CString};
use std::os::raw::{c_char, c_int, c_void};
pub const ABI_VERSION: u32 = 4;
pub type CompFn =
extern "C" fn(host: *const HostApi, argc: usize, argv: *const *const c_char) -> c_int;
pub const INIT_SYMBOL: &[u8] = b"znative_init\0";
pub type BuiltinFn =
extern "C" fn(host: *const HostApi, argc: usize, argv: *const *const c_char) -> c_int;
pub type InitFn = extern "C" fn(host: *const HostApi) -> *const PluginInfo;
#[repr(C)]
pub struct HostApi {
pub abi_version: u32,
pub ctx: *mut c_void,
pub register_builtin:
extern "C" fn(host: *const HostApi, name: *const c_char, handler: BuiltinFn) -> c_int,
pub print: extern "C" fn(host: *const HostApi, text: *const c_char),
pub eval: extern "C" fn(host: *const HostApi, code: *const c_char) -> c_int,
pub getvar: extern "C" fn(host: *const HostApi, name: *const c_char) -> *mut c_char,
pub setvar:
extern "C" fn(host: *const HostApi, name: *const c_char, value: *const c_char) -> c_int,
pub free_cstring: extern "C" fn(host: *const HostApi, s: *mut c_char),
pub register_completion:
extern "C" fn(host: *const HostApi, cmd: *const c_char, generator: *const c_char) -> c_int,
pub getfunction: extern "C" fn(host: *const HostApi, name: *const c_char) -> *mut c_char,
pub addfunction:
extern "C" fn(host: *const HostApi, name: *const c_char, body: *const c_char) -> c_int,
pub register_compfn:
extern "C" fn(host: *const HostApi, name: *const c_char, handler: CompFn) -> c_int,
pub comp_dispatch: extern "C" fn(
host: *const HostApi,
name: *const c_char,
argc: usize,
argv: *const *const c_char,
) -> c_int,
pub empty_command_hash: extern "C" fn(host: *const HostApi),
}
#[repr(C)]
pub struct PluginInfo {
pub abi_version: u32,
pub name: *const c_char,
pub version: *const c_char,
}
unsafe impl Sync for PluginInfo {}
pub struct Host {
api: *const HostApi,
}
impl Host {
pub unsafe fn from_raw(api: *const HostApi) -> Self {
Host { api }
}
#[inline]
fn t(&self) -> &HostApi {
unsafe { &*self.api }
}
pub fn register_builtin(&self, name: &str, handler: BuiltinFn) -> bool {
let Ok(cname) = CString::new(name) else {
return false;
};
((self.t().register_builtin)(self.api, cname.as_ptr(), handler)) == 0
}
pub fn print(&self, text: &str) {
if let Ok(c) = CString::new(text) {
(self.t().print)(self.api, c.as_ptr());
}
}
pub fn eval(&self, code: &str) -> i32 {
match CString::new(code) {
Ok(c) => (self.t().eval)(self.api, c.as_ptr()),
Err(_) => 1,
}
}
pub fn getvar(&self, name: &str) -> Option<String> {
let cname = CString::new(name).ok()?;
let raw = (self.t().getvar)(self.api, cname.as_ptr());
if raw.is_null() {
return None;
}
let s = unsafe { CStr::from_ptr(raw) }
.to_string_lossy()
.into_owned();
(self.t().free_cstring)(self.api, raw);
Some(s)
}
pub fn setvar(&self, name: &str, value: &str) -> bool {
let (Ok(cn), Ok(cv)) = (CString::new(name), CString::new(value)) else {
return false;
};
(self.t().setvar)(self.api, cn.as_ptr(), cv.as_ptr()) == 0
}
pub fn add_match(&self, word: &str) {
self.print(word);
self.print("\n");
}
pub fn install_completion(&self, cmd: &str, generator_builtin: &str) -> bool {
let (Ok(cc), Ok(cg)) = (CString::new(cmd), CString::new(generator_builtin)) else {
return false;
};
(self.t().register_completion)(self.api, cc.as_ptr(), cg.as_ptr()) == 0
}
pub fn getfunction(&self, name: &str) -> Option<String> {
let cname = CString::new(name).ok()?;
let raw = (self.t().getfunction)(self.api, cname.as_ptr());
if raw.is_null() {
return None;
}
let s = unsafe { CStr::from_ptr(raw) }
.to_string_lossy()
.into_owned();
(self.t().free_cstring)(self.api, raw);
Some(s)
}
pub fn addfunction(&self, name: &str, body: &str) -> bool {
let (Ok(cn), Ok(cb)) = (CString::new(name), CString::new(body)) else {
return false;
};
(self.t().addfunction)(self.api, cn.as_ptr(), cb.as_ptr()) == 0
}
pub fn register_compfn(&self, name: &str, handler: CompFn) -> bool {
let Ok(cname) = CString::new(name) else {
return false;
};
(self.t().register_compfn)(self.api, cname.as_ptr(), handler) == 0
}
pub fn comp_dispatch(&self, name: &str, args: &[&str]) -> i32 {
let Ok(cname) = CString::new(name) else {
return 1;
};
let mut cargs: Vec<CString> = Vec::with_capacity(args.len() + 1);
cargs.push(cname);
for a in args {
match CString::new(*a) {
Ok(c) => cargs.push(c),
Err(_) => return 1,
}
}
let argv: Vec<*const c_char> = cargs.iter().map(|c| c.as_ptr()).collect();
(self.t().comp_dispatch)(self.api, argv[0], argv.len(), argv.as_ptr())
}
pub fn empty_command_hash(&self) {
(self.t().empty_command_hash)(self.api);
}
}
pub struct Args {
items: Vec<String>,
}
impl Args {
pub unsafe fn from_raw(argc: usize, argv: *const *const c_char) -> Self {
let mut items = Vec::with_capacity(argc);
if !argv.is_null() {
for i in 0..argc {
let p = *argv.add(i);
if p.is_null() {
break;
}
items.push(CStr::from_ptr(p).to_string_lossy().into_owned());
}
}
Args { items }
}
pub fn name(&self) -> &str {
self.items.first().map(String::as_str).unwrap_or("")
}
pub fn rest(&self) -> &[String] {
if self.items.is_empty() {
&[]
} else {
&self.items[1..]
}
}
pub fn to_vec(&self) -> &[String] {
&self.items
}
}
#[macro_export]
macro_rules! declare_plugin {
(
name: $name:literal,
version: $version:literal,
$(builtins: { $($cmd:literal => $handler:path),+ $(,)? } $(,)?)?
$(completions: { $($ccmd:literal => $cgen:path),+ $(,)? } $(,)?)?
$(compfns: { $($cfname:literal => $cfhandler:path),+ $(,)? } $(,)?)?
) => {
static __ZSHRS_PLUGIN_INFO: $crate::PluginInfo = $crate::PluginInfo {
abi_version: $crate::ABIVERSION_FOR_MACRO,
name: concat!($name, "\0").as_ptr() as *const ::std::os::raw::c_char,
version: concat!($version, "\0").as_ptr() as *const ::std::os::raw::c_char,
};
#[no_mangle]
pub extern "C" fn znative_init(
host: *const $crate::HostApi,
) -> *const $crate::PluginInfo {
if host.is_null() {
return ::std::ptr::null();
}
let ver = unsafe { (*host).abi_version };
if ver != $crate::ABI_VERSION {
return ::std::ptr::null();
}
let h = unsafe { $crate::Host::from_raw(host) };
$($(
{
extern "C" fn __trampoline(
host: *const $crate::HostApi,
argc: usize,
argv: *const *const ::std::os::raw::c_char,
) -> ::std::os::raw::c_int {
let h = unsafe { $crate::Host::from_raw(host) };
let a = unsafe { $crate::Args::from_raw(argc, argv) };
$handler(&h, &a)
}
h.register_builtin($cmd, __trampoline);
}
)+)?
$($(
{
extern "C" fn __cgen(
host: *const $crate::HostApi,
argc: usize,
argv: *const *const ::std::os::raw::c_char,
) -> ::std::os::raw::c_int {
let h = unsafe { $crate::Host::from_raw(host) };
let a = unsafe { $crate::Args::from_raw(argc, argv) };
$cgen(&h, &a)
}
let gen_name = concat!("zshrs_complete_", $ccmd);
h.register_builtin(gen_name, __cgen);
h.install_completion($ccmd, gen_name);
}
)+)?
$($(
{
extern "C" fn __compfn(
host: *const $crate::HostApi,
argc: usize,
argv: *const *const ::std::os::raw::c_char,
) -> ::std::os::raw::c_int {
let h = unsafe { $crate::Host::from_raw(host) };
let a = unsafe { $crate::Args::from_raw(argc, argv) };
$cfhandler(&h, &a)
}
h.register_compfn($cfname, __compfn);
}
)+)?
&__ZSHRS_PLUGIN_INFO as *const $crate::PluginInfo
}
};
}
#[doc(hidden)]
pub const ABIVERSION_FOR_MACRO: u32 = ABI_VERSION;