#![allow(unsafe_code)]
use std::{ffi::c_void, path::PathBuf};
use crate::{
BufferId, BufferUpdateEvent, ChromePosition, ClientModule, ClientModuleError,
ClientModuleProbe, ModuleContext, OptionValue, ProbeResult, Version,
};
pub type ClientProbeFn = unsafe extern "C" fn() -> ClientModuleProbe;
pub type ClientEntryFn = unsafe extern "C" fn() -> *mut c_void;
pub type ClientInitFn = unsafe extern "C" fn(*mut c_void, *const c_void) -> i32;
pub type ClientExitFn = unsafe extern "C" fn(*mut c_void) -> i32;
pub type ClientDestroyFn = unsafe extern "C" fn(*mut c_void);
pub type ClientOnAllLoadedFn = unsafe extern "C" fn(*mut c_void, *const c_void);
pub type ClientOnNotificationFn = unsafe extern "C" fn(*mut c_void, *const u8, usize);
pub type ClientOnModeChangeFn = unsafe extern "C" fn(*mut c_void, *const u8, usize);
pub type ClientOnCursorUpdateFn = unsafe extern "C" fn(*mut c_void, usize, usize, usize);
pub type ClientOnBufferFocusFn = unsafe extern "C" fn(*mut c_void, usize);
pub type ClientOnBufferUpdateFn =
unsafe extern "C" fn(*mut c_void, usize, u64, usize, usize, usize);
pub type ClientOnOptionChangedFn =
unsafe extern "C" fn(*mut c_void, *const u8, usize, i32, i64, *const u8, usize);
pub type ClientTickFn = unsafe extern "C" fn(*mut c_void) -> i32;
pub type ClientHasChromeFn = unsafe extern "C" fn(*mut c_void) -> i32;
pub type ClientHasBufferContribFn = unsafe extern "C" fn(*mut c_void) -> i32;
pub type ClientHasAnnotationsFn = unsafe extern "C" fn(*mut c_void) -> i32;
pub type ClientChromePositionFn = unsafe extern "C" fn(*mut c_void) -> i32;
pub type ClientChromeRequestedSizeFn = unsafe extern "C" fn(*mut c_void) -> u16;
pub type ClientChromePriorityFn = unsafe extern "C" fn(*mut c_void) -> u16;
pub type ClientChromeZOrderFn = unsafe extern "C" fn(*mut c_void) -> u16;
pub type ClientBufferContribPriorityFn = unsafe extern "C" fn(*mut c_void) -> u16;
pub type ClientAnnotationPriorityFn = unsafe extern "C" fn(*mut c_void) -> u16;
pub struct ClientFfiSymbols {
pub init: ClientInitFn,
pub exit: ClientExitFn,
pub destroy: ClientDestroyFn,
pub on_all_loaded: Option<ClientOnAllLoadedFn>,
pub on_notification: Option<ClientOnNotificationFn>,
pub on_mode_change: Option<ClientOnModeChangeFn>,
pub on_cursor_update: Option<ClientOnCursorUpdateFn>,
pub on_buffer_focus: Option<ClientOnBufferFocusFn>,
pub on_buffer_update: Option<ClientOnBufferUpdateFn>,
pub on_option_changed: Option<ClientOnOptionChangedFn>,
pub tick: Option<ClientTickFn>,
pub has_chrome: Option<ClientHasChromeFn>,
pub has_buffer_contrib: Option<ClientHasBufferContribFn>,
pub has_annotations: Option<ClientHasAnnotationsFn>,
pub chrome_position: Option<ClientChromePositionFn>,
pub chrome_requested_size: Option<ClientChromeRequestedSizeFn>,
pub chrome_priority: Option<ClientChromePriorityFn>,
pub chrome_z_order: Option<ClientChromeZOrderFn>,
pub buffer_contrib_priority: Option<ClientBufferContribPriorityFn>,
pub annotation_priority: Option<ClientAnnotationPriorityFn>,
}
pub struct ClientModuleHandle {
kind: &'static str,
name: String,
version: Version,
deps: Vec<String>,
optional_deps: Vec<String>,
#[allow(dead_code)]
probe: Option<ClientModuleProbe>,
static_module: Option<Box<dyn ClientModule>>,
dynamic_ptr: Option<*mut c_void>,
#[allow(dead_code)]
library: Option<libloading::Library>,
path: Option<PathBuf>,
ffi: Option<ClientFfiSymbols>,
}
unsafe impl Send for ClientModuleHandle {}
unsafe impl Sync for ClientModuleHandle {}
#[cfg_attr(coverage_nightly, coverage(off))]
impl ClientModuleHandle {
#[must_use]
pub fn from_static(module: Box<dyn ClientModule>) -> Self {
let kind: &'static str = Box::leak(module.kind().to_string().into_boxed_str());
let name = module.name().to_string();
let version = module.version();
let deps = module
.dependencies()
.iter()
.map(|s| (*s).to_string())
.collect();
let optional_deps = module
.optional_dependencies()
.iter()
.map(|s| (*s).to_string())
.collect();
Self {
kind,
name,
version,
deps,
optional_deps,
probe: None,
static_module: Some(module),
dynamic_ptr: None,
library: None,
path: None,
ffi: None,
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[must_use]
pub unsafe fn from_dynamic(
library: libloading::Library,
path: PathBuf,
probe: ClientModuleProbe,
ptr: *mut c_void,
ffi: ClientFfiSymbols,
) -> Self {
let kind: &'static str = Box::leak(probe.id_str().to_string().into_boxed_str());
let name = probe.name_str().to_string();
let version = probe.version;
let deps = probe
.required_deps()
.iter()
.map(|s| (*s).to_string())
.collect();
let optional_deps = probe
.optional_deps()
.iter()
.map(|s| (*s).to_string())
.collect();
Self {
kind,
name,
version,
deps,
optional_deps,
probe: Some(probe),
static_module: None,
dynamic_ptr: Some(ptr),
library: Some(library),
path: Some(path),
ffi: Some(ffi),
}
}
#[must_use]
pub const fn kind(&self) -> &'static str {
self.kind
}
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
#[must_use]
pub const fn version(&self) -> Version {
self.version
}
#[must_use]
pub const fn is_static(&self) -> bool {
self.static_module.is_some()
}
#[must_use]
pub const fn is_dynamic(&self) -> bool {
self.dynamic_ptr.is_some()
}
#[must_use]
pub fn as_module(&self) -> Option<&dyn ClientModule> {
self.static_module.as_deref()
}
pub fn as_module_mut(&mut self) -> Option<&mut dyn ClientModule> {
self.static_module.as_deref_mut()
}
pub fn dependencies(&self) -> Vec<&str> {
self.deps.iter().map(String::as_str).collect()
}
pub fn optional_dependencies(&self) -> Vec<&str> {
self.optional_deps.iter().map(String::as_str).collect()
}
#[must_use]
pub const fn path(&self) -> Option<&PathBuf> {
self.path.as_ref()
}
pub fn init(&mut self, ctx: &ModuleContext) -> ProbeResult {
if let Some(module) = &mut self.static_module {
return module.init(ctx);
}
if let (Some(ptr), Some(ffi)) = (self.dynamic_ptr, &self.ffi) {
let ctx_ptr = std::ptr::from_ref(ctx).cast::<c_void>();
let code = unsafe { (ffi.init)(ptr, ctx_ptr) };
return match code {
0 => ProbeResult::Success,
1 => ProbeResult::Defer("dynamic module deferred".to_string()),
_ => ProbeResult::Failed(ClientModuleError::init_failed(
format!("dynamic init returned code {code}"),
None,
)),
};
}
ProbeResult::Failed(ClientModuleError::init_failed(
"handle has neither static nor dynamic module",
None,
))
}
pub fn exit(&mut self) -> Result<(), ClientModuleError> {
if let Some(module) = &mut self.static_module {
return module.exit();
}
if let (Some(ptr), Some(ffi)) = (self.dynamic_ptr, &self.ffi) {
let code = unsafe { (ffi.exit)(ptr) };
return if code == 0 {
Ok(())
} else {
Err(ClientModuleError::exit_failed(format!("dynamic exit returned code {code}")))
};
}
Ok(())
}
pub fn on_all_loaded(&mut self, ctx: &ModuleContext) {
if let Some(module) = &mut self.static_module {
module.on_all_loaded(ctx);
return;
}
if let (Some(ptr), Some(ffi)) = (self.dynamic_ptr, &self.ffi)
&& let Some(on_all_loaded) = ffi.on_all_loaded
{
let ctx_ptr = std::ptr::from_ref(ctx).cast::<c_void>();
unsafe {
on_all_loaded(ptr, ctx_ptr);
}
}
}
pub fn on_notification(&mut self, data: &str) {
if let Some(module) = &mut self.static_module {
module.on_notification(data);
return;
}
if let (Some(ptr), Some(ffi)) = (self.dynamic_ptr, &self.ffi)
&& let Some(f) = ffi.on_notification
{
unsafe {
(f)(ptr, data.as_ptr(), data.len());
}
}
}
pub fn on_mode_change(&mut self, mode: &str) {
if let Some(module) = &mut self.static_module {
module.on_mode_change(mode);
return;
}
if let (Some(ptr), Some(ffi)) = (self.dynamic_ptr, &self.ffi)
&& let Some(f) = ffi.on_mode_change
{
unsafe {
(f)(ptr, mode.as_ptr(), mode.len());
}
}
}
pub fn on_cursor_update(&mut self, buffer_id: BufferId, line: usize, col: usize) {
if let Some(module) = &mut self.static_module {
module.on_cursor_update(buffer_id, line, col);
return;
}
if let (Some(ptr), Some(ffi)) = (self.dynamic_ptr, &self.ffi)
&& let Some(f) = ffi.on_cursor_update
{
unsafe {
(f)(ptr, buffer_id.0, line, col);
}
}
}
pub fn on_buffer_focus(&mut self, buffer_id: BufferId) {
if let Some(module) = &mut self.static_module {
module.on_buffer_focus(buffer_id);
return;
}
if let (Some(ptr), Some(ffi)) = (self.dynamic_ptr, &self.ffi)
&& let Some(f) = ffi.on_buffer_focus
{
unsafe {
(f)(ptr, buffer_id.0);
}
}
}
pub fn on_buffer_update(&mut self, event: &BufferUpdateEvent) {
if let Some(module) = &mut self.static_module {
module.on_buffer_update(event);
return;
}
if let (Some(ptr), Some(ffi)) = (self.dynamic_ptr, &self.ffi)
&& let Some(f) = ffi.on_buffer_update
{
unsafe {
(f)(
ptr,
event.buffer_id.0,
event.revision,
event.changed_range.start,
event.changed_range.end,
event.total_lines,
);
}
}
}
pub fn on_option_changed(&mut self, name: &str, value: &OptionValue) {
if let Some(module) = &mut self.static_module {
module.on_option_changed(name, value);
return;
}
if let (Some(ptr), Some(ffi)) = (self.dynamic_ptr, &self.ffi)
&& let Some(f) = ffi.on_option_changed
{
let (tag, i64_val, str_ptr, str_len) = match value {
OptionValue::Bool(b) => (0i32, i64::from(*b), std::ptr::null(), 0),
OptionValue::Integer(i) => (1, *i, std::ptr::null(), 0),
OptionValue::String(s) => (2, 0, s.as_ptr(), s.len()),
};
unsafe {
(f)(ptr, name.as_ptr(), name.len(), tag, i64_val, str_ptr, str_len);
}
}
}
pub fn tick(&mut self) -> bool {
if let Some(module) = &mut self.static_module {
return module.tick();
}
if let (Some(ptr), Some(ffi)) = (self.dynamic_ptr, &self.ffi)
&& let Some(f) = ffi.tick
{
let code = unsafe { (f)(ptr) };
return code == 1;
}
false
}
#[must_use]
pub fn has_chrome(&self) -> bool {
if let Some(module) = &self.static_module {
return module.has_chrome();
}
if let (Some(ptr), Some(ffi)) = (self.dynamic_ptr, &self.ffi)
&& let Some(f) = ffi.has_chrome
{
let code = unsafe { (f)(ptr) };
return code == 1;
}
false
}
#[must_use]
pub fn has_buffer_contrib(&self) -> bool {
if let Some(module) = &self.static_module {
return module.has_buffer_contrib();
}
if let (Some(ptr), Some(ffi)) = (self.dynamic_ptr, &self.ffi)
&& let Some(f) = ffi.has_buffer_contrib
{
let code = unsafe { (f)(ptr) };
return code == 1;
}
false
}
#[must_use]
pub fn has_annotations(&self) -> bool {
if let Some(module) = &self.static_module {
return module.has_annotations();
}
if let (Some(ptr), Some(ffi)) = (self.dynamic_ptr, &self.ffi)
&& let Some(f) = ffi.has_annotations
{
let code = unsafe { (f)(ptr) };
return code == 1;
}
false
}
#[must_use]
pub fn chrome_position(&self) -> ChromePosition {
if let Some(module) = &self.static_module {
return module.chrome_position();
}
if let (Some(ptr), Some(ffi)) = (self.dynamic_ptr, &self.ffi)
&& let Some(f) = ffi.chrome_position
{
let code = unsafe { (f)(ptr) };
return match code {
0 => ChromePosition::Top,
2 => ChromePosition::Left,
3 => ChromePosition::Right,
4 => ChromePosition::Overlay,
_ => ChromePosition::Bottom, };
}
ChromePosition::Bottom
}
#[must_use]
pub fn chrome_requested_size(&self) -> u16 {
if let Some(module) = &self.static_module {
return module.chrome_requested_size(&NullCaps);
}
if let (Some(ptr), Some(ffi)) = (self.dynamic_ptr, &self.ffi)
&& let Some(f) = ffi.chrome_requested_size
{
return unsafe { (f)(ptr) };
}
1
}
#[must_use]
pub fn chrome_priority(&self) -> u16 {
if let Some(module) = &self.static_module {
return module.chrome_priority();
}
if let (Some(ptr), Some(ffi)) = (self.dynamic_ptr, &self.ffi)
&& let Some(f) = ffi.chrome_priority
{
return unsafe { (f)(ptr) };
}
0
}
#[must_use]
pub fn chrome_z_order(&self) -> u16 {
if let Some(module) = &self.static_module {
return module.chrome_z_order();
}
if let (Some(ptr), Some(ffi)) = (self.dynamic_ptr, &self.ffi)
&& let Some(f) = ffi.chrome_z_order
{
return unsafe { (f)(ptr) };
}
0
}
#[must_use]
pub fn buffer_contrib_priority(&self) -> u16 {
if let Some(module) = &self.static_module {
return module.buffer_contrib_priority();
}
if let (Some(ptr), Some(ffi)) = (self.dynamic_ptr, &self.ffi)
&& let Some(f) = ffi.buffer_contrib_priority
{
return unsafe { (f)(ptr) };
}
0
}
#[must_use]
pub fn annotation_priority(&self) -> u16 {
if let Some(module) = &self.static_module {
return module.annotation_priority();
}
if let (Some(ptr), Some(ffi)) = (self.dynamic_ptr, &self.ffi)
&& let Some(f) = ffi.annotation_priority
{
return unsafe { (f)(ptr) };
}
0
}
}
struct NullCaps;
#[cfg_attr(coverage_nightly, coverage(off))]
impl crate::traits::PlatformCapabilities for NullCaps {
fn rendering_model(&self) -> crate::RenderingModel {
crate::RenderingModel::CellGrid
}
fn grid_size(&self) -> Option<(u16, u16)> {
None
}
fn color_depth(&self) -> crate::ColorDepth {
crate::ColorDepth::TrueColor
}
fn pixel_size(&self) -> Option<(u32, u32)> {
None
}
fn reliable_unicode_width(&self) -> bool {
true
}
fn dark_mode(&self) -> bool {
false
}
fn smooth_scroll(&self) -> bool {
false
}
fn pointer_events(&self) -> bool {
false
}
fn touch_input(&self) -> bool {
false
}
fn haptic(&self) -> bool {
false
}
fn safe_area(&self) -> crate::Insets {
crate::Insets::ZERO
}
fn has_focus(&self) -> bool {
true
}
fn clipboard_available(&self) -> bool {
false
}
fn screen_reader_active(&self) -> bool {
false
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
impl Drop for ClientModuleHandle {
fn drop(&mut self) {
if let (Some(ptr), Some(ffi)) = (self.dynamic_ptr.take(), &self.ffi) {
unsafe {
(ffi.destroy)(ptr);
}
}
}
}
impl std::fmt::Debug for ClientModuleHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ClientModuleHandle")
.field("kind", &self.kind)
.field("name", &self.name)
.field("version", &self.version)
.field("is_static", &self.is_static())
.field("is_dynamic", &self.is_dynamic())
.field("path", &self.path)
.finish_non_exhaustive()
}
}
#[cfg(test)]
#[path = "handle_tests.rs"]
mod tests;