alma 0.1.1

A Bevy-native modal text editor with Vim-style navigation.
Documentation
use crate::plugin::{
    PluginBufferHandle, PluginHandleKind, PluginHostImportError, PluginHostResourceKind,
    PluginResourceHandleRejectionReason, PluginViewHandle,
};
use wasmtime::component::{Resource, ResourceTable, ResourceTableError};

/// Borrowed resources supplied to one update call.
#[derive(Clone, Copy, Debug)]
pub(super) struct WasmtimeUpdateResources {
    /// Source view resource.
    view: WasmtimeUpdateViewResource,
    /// Source buffer resource.
    buffer: WasmtimeUpdateBufferResource,
}

impl WasmtimeUpdateResources {
    /// Captures host-owned update resources by typed table handle.
    pub(super) const fn new(
        view: WasmtimeUpdateViewResource,
        buffer: WasmtimeUpdateBufferResource,
    ) -> Self {
        Self { view, buffer }
    }

    /// Source view borrow.
    pub(super) fn view_borrow(self) -> Resource<PluginViewHandle> {
        self.view.borrow()
    }

    /// Source buffer borrow.
    pub(super) fn buffer_borrow(self) -> Resource<PluginBufferHandle> {
        self.buffer.borrow()
    }

    /// Returns borrowed resources for a guest call.
    pub(super) fn borrows(self) -> WasmtimeUpdateResourceBorrows {
        WasmtimeUpdateResourceBorrows {
            view: self.view_borrow(),
            buffer: self.buffer_borrow(),
        }
    }

    /// Deletes update roots that were not already dropped by canonical ABI cleanup.
    pub(super) fn delete_from(
        self,
        table: &mut ResourceTable,
    ) -> Result<(), PluginHostImportError> {
        let view = delete_update_resource(table, self.view.owned(), PluginHandleKind::View);
        let buffer = delete_update_resource(table, self.buffer.owned(), PluginHandleKind::Buffer);
        view?;
        buffer
    }
}

/// Borrowed update resources passed to the guest export.
pub(super) struct WasmtimeUpdateResourceBorrows {
    /// Source view borrow.
    pub(super) view: Resource<PluginViewHandle>,
    /// Source buffer borrow.
    pub(super) buffer: Resource<PluginBufferHandle>,
}

/// Table rep for an update-scoped view resource.
#[derive(Clone, Copy, Debug)]
pub(super) struct WasmtimeUpdateViewResource {
    /// Wasmtime resource-table index.
    pub(super) rep: u32,
}

impl WasmtimeUpdateViewResource {
    /// Captures an owned table resource.
    pub(super) fn from_owned(resource: &Resource<PluginViewHandle>) -> Self {
        Self {
            rep: resource.rep(),
        }
    }

    /// Rebuilds a borrow for the guest call.
    pub(super) fn borrow(self) -> Resource<PluginViewHandle> {
        Resource::new_borrow(self.rep)
    }

    /// Rebuilds the owned handle for host cleanup.
    pub(super) fn owned(self) -> Resource<PluginViewHandle> {
        Resource::new_own(self.rep)
    }
}

/// Table rep for an update-scoped buffer resource.
#[derive(Clone, Copy, Debug)]
pub(super) struct WasmtimeUpdateBufferResource {
    /// Wasmtime resource-table index.
    pub(super) rep: u32,
}

impl WasmtimeUpdateBufferResource {
    /// Captures an owned table resource.
    pub(super) fn from_owned(resource: &Resource<PluginBufferHandle>) -> Self {
        Self {
            rep: resource.rep(),
        }
    }

    /// Rebuilds a borrow for the guest call.
    pub(super) fn borrow(self) -> Resource<PluginBufferHandle> {
        Resource::new_borrow(self.rep)
    }

    /// Rebuilds the owned handle for host cleanup.
    pub(super) fn owned(self) -> Resource<PluginBufferHandle> {
        Resource::new_own(self.rep)
    }
}

/// Deletes one update resource, tolerating canonical ABI cleanup that already ran.
pub(super) fn delete_update_resource<T: 'static>(
    table: &mut ResourceTable,
    resource: Resource<T>,
    kind: PluginHandleKind,
) -> Result<(), PluginHostImportError> {
    match table.delete(resource) {
        Ok(_value) => Ok(()),
        Err(ResourceTableError::NotPresent) => Ok(()),
        Err(source) => Err(resource_handle_error(kind.into(), &source)),
    }
}

/// Converts component resource-table errors into redacted import rejections.
pub(super) const fn resource_handle_error(
    kind: PluginHostResourceKind,
    source: &ResourceTableError,
) -> PluginHostImportError {
    let reason = match source {
        ResourceTableError::NotPresent => PluginResourceHandleRejectionReason::Missing,
        ResourceTableError::WrongType => PluginResourceHandleRejectionReason::WrongType,
        ResourceTableError::Full | ResourceTableError::HasChildren => {
            PluginResourceHandleRejectionReason::Unavailable
        }
    };
    PluginHostImportError::ResourceHandle { kind, reason }
}

/// Converts workspace observation task resource-table errors into redacted import rejections.
pub(super) const fn workspace_observe_task_resource_error(
    source: &ResourceTableError,
) -> PluginHostImportError {
    resource_handle_error(PluginHostResourceKind::WorkspaceObserveTask, source)
}

/// Rejects an internally inconsistent task resource without exposing table state.
pub(super) const fn workspace_observe_task_resource_unavailable() -> PluginHostImportError {
    PluginHostImportError::ResourceHandle {
        kind: PluginHostResourceKind::WorkspaceObserveTask,
        reason: PluginResourceHandleRejectionReason::Unavailable,
    }
}