#![cfg_attr(target_arch = "wasm32", no_std)]
#[cfg(target_arch = "wasm32")]
extern crate alloc;
#[cfg(not(target_arch = "wasm32"))]
extern crate std as alloc;
use alloc::collections::BTreeMap;
use alloc::string::{String, ToString};
use core::future::Future;
use core::pin::Pin;
use core::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
use oo_protocol::{
CommandOp, HostRequest, Message, Operation, Request, Response, ResponsePayload, TerminalOp,
WindowOp, WorkspaceOp,
};
#[unsafe(no_mangle)]
pub unsafe extern "C" fn oo_alloc(size: i32) -> i32 {
let layout = core::alloc::Layout::from_size_align(size as usize, 1).unwrap();
let ptr = unsafe { alloc::alloc::alloc(layout) };
ptr as i32
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn oo_free(ptr: i32, size: i32) {
let layout = core::alloc::Layout::from_size_align(size as usize, 1).unwrap();
unsafe { alloc::alloc::dealloc(ptr as *mut u8, layout) };
}
#[link(wasm_import_module = "oo")]
unsafe extern "C" {
fn __oo_host_call(ptr: i32, len: i32);
}
struct Pending {
result: Option<ResponsePayload>,
waker: Option<Waker>,
}
static mut NEXT_ID: u32 = 1;
static mut PENDING: Option<BTreeMap<u32, Pending>> = None;
fn pending_map() -> &'static mut BTreeMap<u32, Pending> {
unsafe {
let p = core::ptr::addr_of_mut!(PENDING);
if (*p).is_none() {
*p = Some(BTreeMap::new());
}
(*p).as_mut().unwrap()
}
}
fn next_id() -> u32 {
unsafe {
let p = core::ptr::addr_of_mut!(NEXT_ID);
let id = *p;
*p = id.wrapping_add(1);
id
}
}
pub fn send_request(payload: HostRequest) -> ResponseFuture {
let id = next_id();
pending_map().insert(
id,
Pending {
result: None,
waker: None,
},
);
let msg = Message::Request(Request { id, payload });
let bytes = rmp_serde::to_vec(&msg).unwrap_or_default();
unsafe { __oo_host_call(bytes.as_ptr() as i32, bytes.len() as i32) };
ResponseFuture { id }
}
pub struct ResponseFuture {
id: u32,
}
impl Future for ResponseFuture {
type Output = ResponsePayload;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let map = pending_map();
if let Some(entry) = map.get_mut(&self.id) {
if let Some(result) = entry.result.take() {
map.remove(&self.id);
return Poll::Ready(result);
}
entry.waker = Some(cx.waker().clone());
}
Poll::Pending
}
}
#[unsafe(no_mangle)]
pub extern "C" fn oo_on_message(ptr: i32, len: i32) {
let bytes = unsafe { core::slice::from_raw_parts(ptr as *const u8, len as usize) };
let msg: Message = match rmp_serde::from_slice(bytes) {
Ok(m) => m,
Err(_) => return,
};
if let Message::Response(Response { id, result }) = msg
&& let Some(entry) = pending_map().get_mut(&id)
{
entry.result = Some(result);
if let Some(waker) = entry.waker.take() {
waker.wake();
}
}
}
pub fn block_on<F: Future>(mut f: F) -> F::Output {
let mut f = unsafe { Pin::new_unchecked(&mut f) };
let waker = noop_waker();
let mut cx = Context::from_waker(&waker);
loop {
match f.as_mut().poll(&mut cx) {
Poll::Ready(v) => return v,
Poll::Pending => {}
}
}
}
fn noop_waker() -> Waker {
const VTABLE: RawWakerVTable =
RawWakerVTable::new(|p| RawWaker::new(p, &VTABLE), |_| {}, |_| {}, |_| {});
unsafe { Waker::from_raw(RawWaker::new(core::ptr::null(), &VTABLE)) }
}
pub fn show_notification(message: &str, level: &str) {
block_on(send_request(HostRequest::Operation(Operation::Window(
WindowOp::ShowNotification {
message: message.to_string(),
level: level.to_string(),
},
))));
}
pub fn open_file(path: &str) {
block_on(send_request(HostRequest::Operation(Operation::Workspace(
WorkspaceOp::OpenFile {
path: path.to_string(),
},
))));
}
pub fn update_status_bar(text: &str, slot: Option<&str>) {
block_on(send_request(HostRequest::Operation(Operation::Window(
WindowOp::UpdateStatusBar {
text: text.to_string(),
slot: slot.map(|s| s.to_string()),
},
))));
}
pub fn create_terminal(command: &str) {
block_on(send_request(HostRequest::Operation(Operation::Terminal(
TerminalOp::Create {
command: command.to_string(),
},
))));
}
pub fn run_command(id: &str) {
block_on(send_request(HostRequest::Operation(Operation::Command(
CommandOp::Run { id: id.to_string() },
))));
}
pub fn log(message: &str) {
block_on(send_request(HostRequest::Log {
message: message.to_string(),
}));
}
pub fn get_config(key: &str) -> Option<String> {
let future = send_request(HostRequest::GetConfig {
key: key.to_string(),
});
match block_on(future) {
ResponsePayload::String(s) => Some(s),
_ => None,
}
}