mirror of
https://github.com/hexedtech/codemp.git
synced 2024-11-25 00:24:48 +01:00
feat(java): callback API, buffer send
Co-authored-by: alemi <me@alemi.dev>
This commit is contained in:
parent
a59d2c4648
commit
963f2b698c
4 changed files with 157 additions and 19 deletions
|
@ -1,6 +1,6 @@
|
||||||
use jni::{objects::{JClass, JObject, JValueGen}, sys::{jlong, jobject, jstring}, JNIEnv};
|
use jni::{objects::{JClass, JObject, JValueGen}, sys::{jlong, jobject, jstring}, JNIEnv};
|
||||||
|
|
||||||
use crate::api::Controller;
|
use crate::{api::Controller, ffi::java::handle_callback};
|
||||||
|
|
||||||
use super::{JExceptable, RT};
|
use super::{JExceptable, RT};
|
||||||
|
|
||||||
|
@ -87,3 +87,70 @@ fn recv_jni(env: &mut JNIEnv, change: Option<crate::api::TextChange>) -> jobject
|
||||||
}
|
}
|
||||||
}.as_raw()
|
}.as_raw()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "system" fn Java_mp_code_BufferController_callback<'local>(
|
||||||
|
mut env: JNIEnv,
|
||||||
|
_class: JClass<'local>,
|
||||||
|
self_ptr: jlong,
|
||||||
|
cb: JObject<'local>,
|
||||||
|
) {
|
||||||
|
handle_callback!("mp/code/BufferController", env, self_ptr, cb, crate::buffer::Controller);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Receive from Java, converts and sends a [crate::api::TextChange].
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "system" fn Java_mp_code_BufferController_send<'local>(
|
||||||
|
mut env: JNIEnv,
|
||||||
|
_class: JClass<'local>,
|
||||||
|
self_ptr: jlong,
|
||||||
|
input: JObject<'local>,
|
||||||
|
) {
|
||||||
|
let Ok(start) = env.get_field(&input, "start", "I")
|
||||||
|
.and_then(|sr| sr.i())
|
||||||
|
.jexcept(&mut env)
|
||||||
|
.try_into()
|
||||||
|
else {
|
||||||
|
env.throw_new("java/lang/IllegalArgumentException", "Start index cannot be negative!")
|
||||||
|
.expect("Failed to throw exception!");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Ok(end) = env.get_field(&input, "end", "I")
|
||||||
|
.and_then(|er| er.i())
|
||||||
|
.jexcept(&mut env)
|
||||||
|
.try_into()
|
||||||
|
else {
|
||||||
|
env.throw_new("java/lang/IllegalArgumentException", "End index cannot be negative!")
|
||||||
|
.expect("Failed to throw exception!");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let content = env.get_field(&input, "content", "Ljava/lang/String;")
|
||||||
|
.and_then(|b| b.l())
|
||||||
|
.map(|b| b.into())
|
||||||
|
.jexcept(&mut env);
|
||||||
|
let content = env.get_string(&content)
|
||||||
|
.map(|b| b.into())
|
||||||
|
.jexcept(&mut env);
|
||||||
|
|
||||||
|
let hash = env.get_field(&input, "hash", "Ljava/util/OptionalLong")
|
||||||
|
.and_then(|hash| hash.l())
|
||||||
|
.and_then(|hash| {
|
||||||
|
if env.call_method(&hash, "isPresent", "()Z", &[]).and_then(|r| r.z()).jexcept(&mut env) {
|
||||||
|
env.call_method(&hash, "getAsLong", "()J", &[])
|
||||||
|
.and_then(|r| r.j())
|
||||||
|
.map(Some)
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}).jexcept(&mut env);
|
||||||
|
|
||||||
|
let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::buffer::Controller)) };
|
||||||
|
RT.block_on(controller.send(crate::api::TextChange {
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
content,
|
||||||
|
hash,
|
||||||
|
})).jexcept(&mut env);
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use jni::{objects::{JClass, JObject, JString, JValueGen}, sys::{jlong, jobject}, JNIEnv};
|
use jni::{objects::{JClass, JObject, JString, JValueGen}, sys::{jlong, jobject}, JNIEnv};
|
||||||
use crate::api::Controller;
|
use crate::api::Controller;
|
||||||
|
|
||||||
use super::{JExceptable, RT};
|
use super::{handle_callback, JExceptable, RT};
|
||||||
|
|
||||||
/// Try to fetch a [crate::api::Cursor], or returns null if there's nothing.
|
/// Try to fetch a [crate::api::Cursor], or returns null if there's nothing.
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
|
@ -56,6 +56,16 @@ fn jni_recv(env: &mut JNIEnv, cursor: Option<crate::api::Cursor>) -> jobject {
|
||||||
}.as_raw()
|
}.as_raw()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "system" fn Java_mp_code_CursorController_callback<'local>(
|
||||||
|
mut env: JNIEnv,
|
||||||
|
_class: JClass<'local>,
|
||||||
|
self_ptr: jlong,
|
||||||
|
cb: JObject<'local>,
|
||||||
|
) {
|
||||||
|
handle_callback!("mp/code/CursorController", env, self_ptr, cb, crate::cursor::Controller);
|
||||||
|
}
|
||||||
|
|
||||||
/// Receive from Java, converts and sends a [crate::api::Cursor].
|
/// Receive from Java, converts and sends a [crate::api::Cursor].
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "system" fn Java_mp_code_CursorController_send<'local>(
|
pub extern "system" fn Java_mp_code_CursorController_send<'local>(
|
||||||
|
|
|
@ -105,7 +105,7 @@ impl<T> JExceptable<T> for Result<T, uuid::Error> where T: Default {
|
||||||
/// This is essentially the same as [TryInto], but that can't be emplemented on non-local types.
|
/// This is essentially the same as [TryInto], but that can't be emplemented on non-local types.
|
||||||
pub(crate) trait JObjectify<'local> {
|
pub(crate) trait JObjectify<'local> {
|
||||||
/// The error type, likely to be [jni::errors::Error].
|
/// The error type, likely to be [jni::errors::Error].
|
||||||
type Error;
|
type Error: std::fmt::Debug;
|
||||||
|
|
||||||
/// Attempt to convert the given object to a [jni::objects::JObject].
|
/// Attempt to convert the given object to a [jni::objects::JObject].
|
||||||
fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result<jni::objects::JObject<'local>, Self::Error>;
|
fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result<jni::objects::JObject<'local>, Self::Error>;
|
||||||
|
@ -113,7 +113,7 @@ pub(crate) trait JObjectify<'local> {
|
||||||
|
|
||||||
impl<'local> JObjectify<'local> for uuid::Uuid {
|
impl<'local> JObjectify<'local> for uuid::Uuid {
|
||||||
type Error = jni::errors::Error;
|
type Error = jni::errors::Error;
|
||||||
fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result<jni::objects::JObject<'local>, jni::errors::Error> {
|
fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result<jni::objects::JObject<'local>, Self::Error> {
|
||||||
env.find_class("java/util/UUID").and_then(|class| {
|
env.find_class("java/util/UUID").and_then(|class| {
|
||||||
let (msb, lsb) = self.as_u64_pair();
|
let (msb, lsb) = self.as_u64_pair();
|
||||||
let msb = i64::from_ne_bytes(msb.to_ne_bytes());
|
let msb = i64::from_ne_bytes(msb.to_ne_bytes());
|
||||||
|
@ -122,3 +122,71 @@ impl<'local> JObjectify<'local> for uuid::Uuid {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'local> JObjectify<'local> for crate::cursor::Controller {
|
||||||
|
type Error = jni::errors::Error;
|
||||||
|
|
||||||
|
fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result<jni::objects::JObject<'local>, Self::Error> {
|
||||||
|
let class = env.find_class("mp/code/CursorController")?;
|
||||||
|
env.new_object(
|
||||||
|
class,
|
||||||
|
"(J)V",
|
||||||
|
&[
|
||||||
|
jni::objects::JValueGen::Long(Box::into_raw(Box::new(&self)) as jni::sys::jlong)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'local> JObjectify<'local> for crate::buffer::Controller {
|
||||||
|
type Error = jni::errors::Error;
|
||||||
|
|
||||||
|
fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result<jni::objects::JObject<'local>, Self::Error> {
|
||||||
|
let class = env.find_class("mp/code/BufferController")?;
|
||||||
|
env.new_object(
|
||||||
|
class,
|
||||||
|
"(J)V",
|
||||||
|
&[
|
||||||
|
jni::objects::JValueGen::Long(Box::into_raw(Box::new(&self)) as jni::sys::jlong)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! handle_callback {
|
||||||
|
($jtype:literal, $env:ident, $self_ptr:ident, $cb:ident, $t:ty) => {
|
||||||
|
let controller = unsafe { Box::leak(Box::from_raw($self_ptr as *mut $t)) };
|
||||||
|
|
||||||
|
let Ok(jvm) = $env.get_java_vm() else {
|
||||||
|
$env.throw_new("mp/code/exceptions/JNIException", "Failed to get JVM reference!")
|
||||||
|
.expect("Failed to throw exception!");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Ok(cb_ref) = $env.new_global_ref($cb) else {
|
||||||
|
$env.throw_new("mp/code/exceptions/JNIException", "Failed to pin callback reference!")
|
||||||
|
.expect("Failed to throw exception!");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
controller.callback(move |controller: $t| {
|
||||||
|
use std::ops::DerefMut;
|
||||||
|
use crate::ffi::java::JObjectify;
|
||||||
|
let mut guard = jvm.attach_current_thread().unwrap();
|
||||||
|
let jcontroller = match controller.jobjectify(guard.deref_mut()) {
|
||||||
|
Err(e) => return tracing::error!("could not convert callback argument: {e:?}"),
|
||||||
|
Ok(x) => x,
|
||||||
|
};
|
||||||
|
let sig = format!("(L{};)V", $jtype);
|
||||||
|
if let Err(e) = guard.call_method(&cb_ref,
|
||||||
|
"invoke",
|
||||||
|
&sig,
|
||||||
|
&[jni::objects::JValueGen::Object(&jcontroller)]
|
||||||
|
) {
|
||||||
|
tracing::error!("error invoking callback: {e:?}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) use handle_callback;
|
||||||
|
|
||||||
|
|
|
@ -22,9 +22,7 @@ pub extern "system" fn Java_mp_code_Workspace_get_1cursor<'local>(
|
||||||
self_ptr: jlong
|
self_ptr: jlong
|
||||||
) -> jobject {
|
) -> jobject {
|
||||||
let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) };
|
let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) };
|
||||||
env.find_class("mp/code/CursorController").and_then(|class|
|
workspace.cursor().jobjectify(&mut env).jexcept(&mut env).as_raw()
|
||||||
env.new_object(class, "(J)V", &[JValueGen::Long(Box::into_raw(Box::new(workspace.cursor())) as jlong)])
|
|
||||||
).jexcept(&mut env).as_raw()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a buffer controller by name and returns a pointer to it.
|
/// Get a buffer controller by name and returns a pointer to it.
|
||||||
|
@ -39,12 +37,10 @@ pub extern "system" fn Java_mp_code_Workspace_get_1buffer<'local>(
|
||||||
let path = unsafe { env.get_string_unchecked(&input) }
|
let path = unsafe { env.get_string_unchecked(&input) }
|
||||||
.map(|path| path.to_string_lossy().to_string())
|
.map(|path| path.to_string_lossy().to_string())
|
||||||
.jexcept(&mut env);
|
.jexcept(&mut env);
|
||||||
|
workspace.buffer_by_name(&path)
|
||||||
workspace.buffer_by_name(&path).map(|buf| {
|
.map(|buf| buf.jobjectify(&mut env).jexcept(&mut env))
|
||||||
env.find_class("mp/code/BufferController").and_then(|class|
|
.unwrap_or_default()
|
||||||
env.new_object(class, "(J)V", &[JValueGen::Long(Box::into_raw(Box::new(buf)) as jlong)])
|
.as_raw()
|
||||||
).jexcept(&mut env)
|
|
||||||
}).unwrap_or_default().as_raw()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new buffer.
|
/// Create a new buffer.
|
||||||
|
@ -108,12 +104,9 @@ pub extern "system" fn Java_mp_code_Workspace_attach_1to_1buffer<'local>(
|
||||||
.map(|path| path.to_string_lossy().to_string())
|
.map(|path| path.to_string_lossy().to_string())
|
||||||
.jexcept(&mut env);
|
.jexcept(&mut env);
|
||||||
RT.block_on(workspace.attach(&path))
|
RT.block_on(workspace.attach(&path))
|
||||||
.map(|buffer| Box::into_raw(Box::new(buffer)) as jlong)
|
.map(|buffer| buffer.jobjectify(&mut env).jexcept(&mut env))
|
||||||
.map(|ptr| {
|
|
||||||
env.find_class("mp/code/BufferController")
|
|
||||||
.and_then(|class| env.new_object(class, "(J)V", &[JValueGen::Long(ptr)]))
|
|
||||||
.jexcept(&mut env)
|
.jexcept(&mut env)
|
||||||
}).jexcept(&mut env).as_raw()
|
.as_raw()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Detach from a buffer.
|
/// Detach from a buffer.
|
||||||
|
|
Loading…
Reference in a new issue