use std::{
cell::RefCell,
ffi::CString,
mem::MaybeUninit,
};
use once_cell::sync::OnceCell;
use emacs_module::{emacs_env, emacs_runtime, emacs_value};
use crate::{subr, error, Value, Result, IntoLisp, call::IntoLispArgs, GlobalRef};
pub static HAS_FIXED_GC_BUG_31238: OnceCell<bool> = OnceCell::new();
#[derive(Debug)]
pub struct Env {
pub(crate) raw: *mut emacs_env,
pub(crate) protected: Option<RefCell<Vec<emacs_value>>>,
}
impl Env {
#[doc(hidden)]
pub unsafe fn new(raw: *mut emacs_env) -> Self {
let protected = if *HAS_FIXED_GC_BUG_31238.get().unwrap_or(&false) {
None
} else {
Some(RefCell::new(vec![]))
};
Self { raw, protected }
}
#[doc(hidden)]
pub unsafe fn from_runtime(runtime: *mut emacs_runtime) -> Self {
let get_env = (*runtime).get_environment.expect("Cannot get Emacs environment");
let raw = get_env(runtime);
Self::new(raw)
}
#[doc(hidden)]
pub fn raw(&self) -> *mut emacs_env {
self.raw
}
#[doc(hidden)]
pub unsafe fn free_last_protected(&self) -> Result<()>{
if let Some(protected) = &self.protected {
let gr = GlobalRef::from_raw(*protected.borrow().last().unwrap());
gr.free(self)?;
}
Ok(())
}
pub fn intern(&self, name: &str) -> Result<Value<'_>> {
unsafe_raw_call_value!(self, intern, CString::new(name)?.as_ptr())
}
pub fn type_of<'e>(&'e self, value: Value<'e>) -> Result<Value<'_>> {
unsafe_raw_call_value!(self, type_of, value.raw)
}
#[deprecated(since = "0.10.0", note = "Please use `value.is_not_nil()` instead")]
pub fn is_not_nil<'e>(&'e self, value: Value<'e>) -> bool {
unsafe_raw_call_no_exit!(self, is_not_nil, value.raw)
}
#[deprecated(since = "0.10.0", note = "Please use `==` instead")]
pub fn eq<'e>(&'e self, a: Value<'e>, b: Value<'e>) -> bool {
unsafe_raw_call_no_exit!(self, eq, a.raw, b.raw)
}
pub fn cons<'e, A, B>(&'e self, car: A, cdr: B) -> Result<Value<'_>> where A: IntoLisp<'e>, B: IntoLisp<'e> {
self.call(subr::cons, (car, cdr))
}
pub fn list<'e, A>(&'e self, args: A) -> Result<Value<'_>> where A: IntoLispArgs<'e> {
self.call(subr::list, args)
}
pub fn provide(&self, name: &str) -> Result<Value<'_>> {
let name = self.intern(name)?;
self.call("provide", [name])
}
pub fn message<T: AsRef<str>>(&self, text: T) -> Result<Value<'_>> {
self.call(subr::message, (text.as_ref(),))
}
#[cfg(all(feature = "emacs-28"))]
pub fn open_channel<'e>(&'e self, pipe_process: Value<'e>)
-> Result<impl std::io::Write + std::fmt::Debug + Send + Sync>
{
let raw_fd: i32 = unsafe_raw_call!(self, open_channel, pipe_process.raw)?;
#[cfg(target_os = "windows")]
{
use std::os::windows::io::{FromRawHandle, RawHandle};
let handle = unsafe { libc::get_osfhandle(raw_fd) as RawHandle };
Ok(unsafe { std::io::PipeWriter::from_raw_handle(handle) })
}
#[cfg(not(target_os = "windows"))]
{
use std::os::unix::io::FromRawFd;
Ok(unsafe { std::io::PipeWriter::from_raw_fd(raw_fd) })
}
}
}
impl Drop for Env {
fn drop(&mut self) {
if let Some(protected) = &self.protected {
#[cfg(feature = "debug")]
println!("Unrooting {} values protected by {:?}", protected.borrow().len(), self);
let mut symbol = MaybeUninit::uninit();
let mut data = MaybeUninit::uninit();
let status = self.non_local_exit_get(&mut symbol, &mut data);
if status == error::SIGNAL || status == error::THROW {
self.non_local_exit_clear();
}
for raw in protected.borrow().iter() {
unsafe_raw_call_no_exit!(self, free_global_ref, *raw);
}
match status {
error::SIGNAL => unsafe { self.non_local_exit_signal(symbol.assume_init(), data.assume_init()); }
error::THROW => unsafe { self.non_local_exit_throw(symbol.assume_init(), data.assume_init()); }
_ => ()
}
}
}
}