1#![cfg_attr(target_arch = "wasm32", no_std)]
19
20#[cfg(target_arch = "wasm32")]
21extern crate alloc;
22
23#[cfg(not(target_arch = "wasm32"))]
25extern crate std as alloc;
26
27use alloc::collections::BTreeMap;
28use alloc::string::{String, ToString};
29use core::future::Future;
30use core::pin::Pin;
31use core::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
32
33use oo_protocol::{
34 CommandOp, HostRequest, Message, Operation, Request, Response, ResponsePayload, TerminalOp,
35 WindowOp, WorkspaceOp,
36};
37
38#[unsafe(no_mangle)]
46pub unsafe extern "C" fn oo_alloc(size: i32) -> i32 {
47 let layout = core::alloc::Layout::from_size_align(size as usize, 1).unwrap();
48 let ptr = unsafe { alloc::alloc::alloc(layout) };
49 ptr as i32
50}
51
52#[unsafe(no_mangle)]
56pub unsafe extern "C" fn oo_free(ptr: i32, size: i32) {
57 let layout = core::alloc::Layout::from_size_align(size as usize, 1).unwrap();
58 unsafe { alloc::alloc::dealloc(ptr as *mut u8, layout) };
59}
60
61#[link(wasm_import_module = "oo")]
66unsafe extern "C" {
67 fn __oo_host_call(ptr: i32, len: i32);
71}
72
73struct Pending {
78 result: Option<ResponsePayload>,
79 waker: Option<Waker>,
80}
81
82static mut NEXT_ID: u32 = 1;
83static mut PENDING: Option<BTreeMap<u32, Pending>> = None;
84
85fn pending_map() -> &'static mut BTreeMap<u32, Pending> {
86 unsafe {
87 let p = core::ptr::addr_of_mut!(PENDING);
88 if (*p).is_none() {
89 *p = Some(BTreeMap::new());
90 }
91 (*p).as_mut().unwrap()
92 }
93}
94
95fn next_id() -> u32 {
96 unsafe {
97 let p = core::ptr::addr_of_mut!(NEXT_ID);
98 let id = *p;
99 *p = id.wrapping_add(1);
100 id
101 }
102}
103
104pub fn send_request(payload: HostRequest) -> ResponseFuture {
107 let id = next_id();
108 pending_map().insert(
109 id,
110 Pending {
111 result: None,
112 waker: None,
113 },
114 );
115
116 let msg = Message::Request(Request { id, payload });
117 let bytes = rmp_serde::to_vec(&msg).unwrap_or_default();
118
119 unsafe { __oo_host_call(bytes.as_ptr() as i32, bytes.len() as i32) };
120
121 ResponseFuture { id }
122}
123
124pub struct ResponseFuture {
126 id: u32,
127}
128
129impl Future for ResponseFuture {
130 type Output = ResponsePayload;
131
132 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
133 let map = pending_map();
134 if let Some(entry) = map.get_mut(&self.id) {
135 if let Some(result) = entry.result.take() {
136 map.remove(&self.id);
137 return Poll::Ready(result);
138 }
139 entry.waker = Some(cx.waker().clone());
140 }
141 Poll::Pending
142 }
143}
144
145#[unsafe(no_mangle)]
152pub extern "C" fn oo_on_message(ptr: i32, len: i32) {
153 let bytes = unsafe { core::slice::from_raw_parts(ptr as *const u8, len as usize) };
154 let msg: Message = match rmp_serde::from_slice(bytes) {
155 Ok(m) => m,
156 Err(_) => return,
157 };
158 if let Message::Response(Response { id, result }) = msg
159 && let Some(entry) = pending_map().get_mut(&id)
160 {
161 entry.result = Some(result);
162 if let Some(waker) = entry.waker.take() {
163 waker.wake();
164 }
165 }
166}
167
168pub fn block_on<F: Future>(mut f: F) -> F::Output {
175 let mut f = unsafe { Pin::new_unchecked(&mut f) };
177 let waker = noop_waker();
178 let mut cx = Context::from_waker(&waker);
179 loop {
180 match f.as_mut().poll(&mut cx) {
181 Poll::Ready(v) => return v,
182 Poll::Pending => {}
183 }
184 }
185}
186
187fn noop_waker() -> Waker {
188 const VTABLE: RawWakerVTable =
189 RawWakerVTable::new(|p| RawWaker::new(p, &VTABLE), |_| {}, |_| {}, |_| {});
190 unsafe { Waker::from_raw(RawWaker::new(core::ptr::null(), &VTABLE)) }
191}
192
193pub fn show_notification(message: &str, level: &str) {
199 block_on(send_request(HostRequest::Operation(Operation::Window(
200 WindowOp::ShowNotification {
201 message: message.to_string(),
202 level: level.to_string(),
203 },
204 ))));
205}
206
207pub fn open_file(path: &str) {
209 block_on(send_request(HostRequest::Operation(Operation::Workspace(
210 WorkspaceOp::OpenFile {
211 path: path.to_string(),
212 },
213 ))));
214}
215
216pub fn update_status_bar(text: &str, slot: Option<&str>) {
218 block_on(send_request(HostRequest::Operation(Operation::Window(
219 WindowOp::UpdateStatusBar {
220 text: text.to_string(),
221 slot: slot.map(|s| s.to_string()),
222 },
223 ))));
224}
225
226pub fn create_terminal(command: &str) {
228 block_on(send_request(HostRequest::Operation(Operation::Terminal(
229 TerminalOp::Create {
230 command: command.to_string(),
231 },
232 ))));
233}
234
235pub fn run_command(id: &str) {
237 block_on(send_request(HostRequest::Operation(Operation::Command(
238 CommandOp::Run { id: id.to_string() },
239 ))));
240}
241
242pub fn log(message: &str) {
244 block_on(send_request(HostRequest::Log {
245 message: message.to_string(),
246 }));
247}
248
249pub fn get_config(key: &str) -> Option<String> {
254 let future = send_request(HostRequest::GetConfig {
255 key: key.to_string(),
256 });
257 match block_on(future) {
258 ResponsePayload::String(s) => Some(s),
259 _ => None,
260 }
261}
262
263pub fn has_language(lang_id: &str) -> bool {
267 let future = send_request(HostRequest::HasLanguage { lang: lang_id.to_string() });
268 match block_on(future) {
269 ResponsePayload::String(s) => s == "true",
270 _ => false,
271 }
272}
273
274
275#[cfg(test)]
276pub fn __test_inject_response(resp: ResponsePayload) {
277 let id = *pending_map().keys().last().unwrap();
278 if let Some(entry) = pending_map().get_mut(&id) {
279 entry.result = Some(resp);
280 if let Some(w) = entry.waker.take() {
281 w.wake();
282 }
283 }
284}
285
286#[cfg(test)]
287mod tests {
288 use super::*;
289 use std::sync::atomic::{AtomicUsize, Ordering};
290
291 static TEST_MODE: AtomicUsize = AtomicUsize::new(0);
292
293 #[unsafe(no_mangle)]
294 extern "C" fn __oo_host_call(_ptr: i32, _len: i32) {
295 let mode = TEST_MODE.load(Ordering::SeqCst);
296 let val = match mode {
297 1 => Some("true"),
298 2 => Some("false"),
299 _ => None,
300 };
301 if let Some(s) = val {
302 let id = *pending_map().keys().last().unwrap();
303 if let Some(entry) = pending_map().get_mut(&id) {
304 entry.result = Some(ResponsePayload::String(s.to_string()));
305 if let Some(w) = entry.waker.take() {
306 w.wake();
307 }
308 }
309 }
310 }
311
312 #[test]
313 fn has_language_true() {
314 TEST_MODE.store(1, Ordering::SeqCst);
315 assert!(has_language("rust"));
316 }
317
318 #[test]
319 fn has_language_false() {
320 TEST_MODE.store(2, Ordering::SeqCst);
321 assert!(!has_language("python"));
322 }
323}
324