mirror of
https://github.com/hexedtech/codemp.git
synced 2024-11-21 23:04:49 +01:00
chore: cargo fmt
This commit is contained in:
parent
5e2e711b55
commit
400d87cdb1
32 changed files with 718 additions and 406 deletions
4
build.rs
4
build.rs
|
@ -20,7 +20,7 @@ fn main() {
|
||||||
{
|
{
|
||||||
if let Ok("macos") = std::env::var("CARGO_CFG_TARGET_OS").as_deref() {
|
if let Ok("macos") = std::env::var("CARGO_CFG_TARGET_OS").as_deref() {
|
||||||
println!("cargo:rustc-cdylib-link-arg=-undefined");
|
println!("cargo:rustc-cdylib-link-arg=-undefined");
|
||||||
println!("cargo:rustc-cdylib-link-arg=dynamic_lookup");
|
println!("cargo:rustc-cdylib-link-arg=dynamic_lookup");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
//! # Controller
|
//! # Controller
|
||||||
//!
|
//!
|
||||||
//! A bidirectional stream handler to easily manage asynchronous operations between local buffers
|
//! A bidirectional stream handler to easily manage asynchronous operations between local buffers
|
||||||
//! and the server.
|
//! and the server.
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ use crate::errors::ControllerResult;
|
||||||
///
|
///
|
||||||
/// This generic trait is implemented by actors managing stream procedures, and will generally
|
/// This generic trait is implemented by actors managing stream procedures, and will generally
|
||||||
/// imply a background worker.
|
/// imply a background worker.
|
||||||
///
|
///
|
||||||
/// Events can be enqueued for dispatching without blocking with [`Controller::send`].
|
/// Events can be enqueued for dispatching without blocking with [`Controller::send`].
|
||||||
///
|
///
|
||||||
/// For receiving events from the server, an asynchronous API with [`Controller::recv`] is
|
/// For receiving events from the server, an asynchronous API with [`Controller::recv`] is
|
||||||
|
@ -25,7 +25,7 @@ use crate::errors::ControllerResult;
|
||||||
/// [`crate::ext::select_buffer`] may provide a useful helper for managing multiple controllers.
|
/// [`crate::ext::select_buffer`] may provide a useful helper for managing multiple controllers.
|
||||||
#[allow(async_fn_in_trait)]
|
#[allow(async_fn_in_trait)]
|
||||||
#[cfg_attr(feature = "async-trait", async_trait::async_trait)]
|
#[cfg_attr(feature = "async-trait", async_trait::async_trait)]
|
||||||
pub trait Controller<T : Sized + Send + Sync> : Sized + Send + Sync {
|
pub trait Controller<T: Sized + Send + Sync>: Sized + Send + Sync {
|
||||||
/// Enqueue a new value to be sent to all other users.
|
/// Enqueue a new value to be sent to all other users.
|
||||||
async fn send(&self, x: T) -> ControllerResult<()>;
|
async fn send(&self, x: T) -> ControllerResult<()>;
|
||||||
|
|
||||||
|
@ -54,7 +54,6 @@ pub trait Controller<T : Sized + Send + Sync> : Sized + Send + Sync {
|
||||||
async fn try_recv(&self) -> ControllerResult<Option<T>>;
|
async fn try_recv(&self) -> ControllerResult<Option<T>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Type wrapper for Boxed dynamic callback.
|
/// Type wrapper for Boxed dynamic callback.
|
||||||
pub struct ControllerCallback<T>(pub Box<dyn Sync + Send + Fn(T)>);
|
pub struct ControllerCallback<T>(pub Box<dyn Sync + Send + Fn(T)>);
|
||||||
|
|
||||||
|
|
|
@ -19,9 +19,9 @@ pub mod event;
|
||||||
/// data structure for remote users
|
/// data structure for remote users
|
||||||
pub mod user;
|
pub mod user;
|
||||||
|
|
||||||
pub use controller::Controller;
|
|
||||||
pub use change::TextChange;
|
pub use change::TextChange;
|
||||||
pub use config::Config;
|
pub use config::Config;
|
||||||
|
pub use controller::Controller;
|
||||||
pub use cursor::Cursor;
|
pub use cursor::Cursor;
|
||||||
pub use event::Event;
|
pub use event::Event;
|
||||||
pub use user::User;
|
pub use user::User;
|
||||||
|
|
|
@ -30,7 +30,12 @@ struct BufferWorker {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BufferController {
|
impl BufferController {
|
||||||
pub(crate) fn spawn(user_id: Uuid, path: &str, tx: mpsc::Sender<Operation>, rx: Streaming<BufferEvent>) -> Self {
|
pub(crate) fn spawn(
|
||||||
|
user_id: Uuid,
|
||||||
|
path: &str,
|
||||||
|
tx: mpsc::Sender<Operation>,
|
||||||
|
rx: Streaming<BufferEvent>,
|
||||||
|
) -> Self {
|
||||||
let init = diamond_types::LocalVersion::default();
|
let init = diamond_types::LocalVersion::default();
|
||||||
|
|
||||||
let (latest_version_tx, latest_version_rx) = watch::channel(init.clone());
|
let (latest_version_tx, latest_version_rx) = watch::channel(init.clone());
|
||||||
|
@ -68,21 +73,25 @@ impl BufferController {
|
||||||
callback: cb_rx,
|
callback: cb_rx,
|
||||||
};
|
};
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move { BufferController::work(worker, tx, rx).await });
|
||||||
BufferController::work(worker, tx, rx).await
|
|
||||||
});
|
|
||||||
|
|
||||||
BufferController(controller)
|
BufferController(controller)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn work(mut worker: BufferWorker, tx: mpsc::Sender<Operation>, mut rx: Streaming<BufferEvent>) {
|
async fn work(
|
||||||
|
mut worker: BufferWorker,
|
||||||
|
tx: mpsc::Sender<Operation>,
|
||||||
|
mut rx: Streaming<BufferEvent>,
|
||||||
|
) {
|
||||||
let mut branch = diamond_types::list::Branch::new();
|
let mut branch = diamond_types::list::Branch::new();
|
||||||
let mut oplog = diamond_types::list::OpLog::new();
|
let mut oplog = diamond_types::list::OpLog::new();
|
||||||
let mut timer = Timer::new(10); // TODO configurable!!
|
let mut timer = Timer::new(10); // TODO configurable!!
|
||||||
tracing::debug!("controller worker started");
|
tracing::debug!("controller worker started");
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if worker.controller.upgrade().is_none() { break };
|
if worker.controller.upgrade().is_none() {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
|
||||||
// block until one of these is ready
|
// block until one of these is ready
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
|
|
|
@ -4,8 +4,11 @@ use tokio::sync::{mpsc, oneshot, watch};
|
||||||
use tonic::Streaming;
|
use tonic::Streaming;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{api::{controller::ControllerCallback, Cursor, User}, ext::IgnorableError};
|
use crate::{
|
||||||
use codemp_proto::cursor::{CursorPosition, CursorEvent};
|
api::{controller::ControllerCallback, Cursor, User},
|
||||||
|
ext::IgnorableError,
|
||||||
|
};
|
||||||
|
use codemp_proto::cursor::{CursorEvent, CursorPosition};
|
||||||
|
|
||||||
use super::controller::{CursorController, CursorControllerInner};
|
use super::controller::{CursorController, CursorControllerInner};
|
||||||
|
|
||||||
|
@ -21,7 +24,11 @@ struct CursorWorker {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CursorController {
|
impl CursorController {
|
||||||
pub(crate) fn spawn(user_map: Arc<dashmap::DashMap<Uuid, User>>, tx: mpsc::Sender<CursorPosition>, rx: Streaming<CursorEvent>) -> Self {
|
pub(crate) fn spawn(
|
||||||
|
user_map: Arc<dashmap::DashMap<Uuid, User>>,
|
||||||
|
tx: mpsc::Sender<CursorPosition>,
|
||||||
|
rx: Streaming<CursorEvent>,
|
||||||
|
) -> Self {
|
||||||
// TODO we should tweak the channel buffer size to better propagate backpressure
|
// TODO we should tweak the channel buffer size to better propagate backpressure
|
||||||
let (op_tx, op_rx) = mpsc::channel(64);
|
let (op_tx, op_rx) = mpsc::channel(64);
|
||||||
let (stream_tx, stream_rx) = mpsc::channel(1);
|
let (stream_tx, stream_rx) = mpsc::channel(1);
|
||||||
|
@ -52,11 +59,17 @@ impl CursorController {
|
||||||
CursorController(controller)
|
CursorController(controller)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn work(mut worker: CursorWorker, tx: mpsc::Sender<CursorPosition>, mut rx: Streaming<CursorEvent>) {
|
async fn work(
|
||||||
|
mut worker: CursorWorker,
|
||||||
|
tx: mpsc::Sender<CursorPosition>,
|
||||||
|
mut rx: Streaming<CursorEvent>,
|
||||||
|
) {
|
||||||
loop {
|
loop {
|
||||||
tracing::debug!("cursor worker polling");
|
tracing::debug!("cursor worker polling");
|
||||||
if worker.controller.upgrade().is_none() { break }; // clean exit: all controllers dropped
|
if worker.controller.upgrade().is_none() {
|
||||||
tokio::select!{
|
break;
|
||||||
|
}; // clean exit: all controllers dropped
|
||||||
|
tokio::select! {
|
||||||
biased;
|
biased;
|
||||||
|
|
||||||
// new poller
|
// new poller
|
||||||
|
|
|
@ -61,4 +61,3 @@ impl From<tokio::sync::oneshot::error::RecvError> for ControllerError {
|
||||||
|
|
||||||
/// Wraps [std::result::Result] with a [ControllerError].
|
/// Wraps [std::result::Result] with a [ControllerError].
|
||||||
pub type ControllerResult<T> = std::result::Result<T, ControllerError>;
|
pub type ControllerResult<T> = std::result::Result<T, ControllerError>;
|
||||||
|
|
||||||
|
|
12
src/ext.rs
12
src/ext.rs
|
@ -16,7 +16,7 @@ use tokio::sync::mpsc;
|
||||||
pub async fn select_buffer(
|
pub async fn select_buffer(
|
||||||
buffers: &[crate::buffer::Controller],
|
buffers: &[crate::buffer::Controller],
|
||||||
timeout: Option<std::time::Duration>,
|
timeout: Option<std::time::Duration>,
|
||||||
runtime: &tokio::runtime::Runtime
|
runtime: &tokio::runtime::Runtime,
|
||||||
) -> ControllerResult<Option<crate::buffer::Controller>> {
|
) -> ControllerResult<Option<crate::buffer::Controller>> {
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||||
let mut tasks = Vec::new();
|
let mut tasks = Vec::new();
|
||||||
|
@ -46,13 +46,13 @@ pub async fn select_buffer(
|
||||||
t.abort();
|
t.abort();
|
||||||
}
|
}
|
||||||
return Ok(x);
|
return Ok(x);
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Hash a given byte array with the internally used algorithm.
|
/// Hash a given byte array with the internally used algorithm.
|
||||||
///
|
///
|
||||||
/// Currently, it uses [`xxhash_rust::xxh3::xxh3_64`].
|
/// Currently, it uses [`xxhash_rust::xxh3::xxh3_64`].
|
||||||
pub fn hash(data: impl AsRef<[u8]>) -> i64 {
|
pub fn hash(data: impl AsRef<[u8]>) -> i64 {
|
||||||
let hash = xxhash_rust::xxh3::xxh3_64(data.as_ref());
|
let hash = xxhash_rust::xxh3::xxh3_64(data.as_ref());
|
||||||
|
@ -104,11 +104,13 @@ pub trait IgnorableError {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, E> IgnorableError for std::result::Result<T, E>
|
impl<T, E> IgnorableError for std::result::Result<T, E>
|
||||||
where E : std::fmt::Debug {
|
where
|
||||||
|
E: std::fmt::Debug,
|
||||||
|
{
|
||||||
/// Logs the error as a warning and returns a unit.
|
/// Logs the error as a warning and returns a unit.
|
||||||
fn unwrap_or_warn(self, msg: &str) {
|
fn unwrap_or_warn(self, msg: &str) {
|
||||||
match self {
|
match self {
|
||||||
Ok(_) => {},
|
Ok(_) => {}
|
||||||
Err(e) => tracing::warn!("{}: {:?}", msg, e),
|
Err(e) => tracing::warn!("{}: {:?}", msg, e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
use jni::{objects::JObject, JNIEnv};
|
use jni::{objects::JObject, JNIEnv};
|
||||||
use jni_toolbox::jni;
|
use jni_toolbox::jni;
|
||||||
|
|
||||||
use crate::{api::{Controller, TextChange}, errors::ControllerError};
|
use crate::{
|
||||||
|
api::{Controller, TextChange},
|
||||||
|
errors::ControllerError,
|
||||||
|
};
|
||||||
|
|
||||||
use super::null_check;
|
use super::null_check;
|
||||||
|
|
||||||
/// Get the name of the buffer.
|
/// Get the name of the buffer.
|
||||||
#[jni(package = "mp.code", class = "BufferController")]
|
#[jni(package = "mp.code", class = "BufferController")]
|
||||||
fn get_name(controller: &mut crate::buffer::Controller) -> String {
|
fn get_name(controller: &mut crate::buffer::Controller) -> String {
|
||||||
controller.path().to_string() //TODO: &str is built into the newer version
|
controller.path().to_string() //TODO: &str is built into the newer version
|
||||||
|
@ -19,7 +22,9 @@ fn get_content(controller: &mut crate::buffer::Controller) -> Result<String, Con
|
||||||
|
|
||||||
/// Try to fetch a [TextChange], or return null if there's nothing.
|
/// Try to fetch a [TextChange], or return null if there's nothing.
|
||||||
#[jni(package = "mp.code", class = "BufferController")]
|
#[jni(package = "mp.code", class = "BufferController")]
|
||||||
fn try_recv(controller: &mut crate::buffer::Controller) -> Result<Option<TextChange>, ControllerError> {
|
fn try_recv(
|
||||||
|
controller: &mut crate::buffer::Controller,
|
||||||
|
) -> Result<Option<TextChange>, ControllerError> {
|
||||||
super::tokio().block_on(controller.try_recv())
|
super::tokio().block_on(controller.try_recv())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,23 +36,34 @@ fn recv(controller: &mut crate::buffer::Controller) -> Result<TextChange, Contro
|
||||||
|
|
||||||
/// Send a [TextChange] to the server.
|
/// Send a [TextChange] to the server.
|
||||||
#[jni(package = "mp.code", class = "BufferController")]
|
#[jni(package = "mp.code", class = "BufferController")]
|
||||||
fn send(controller: &mut crate::buffer::Controller, change: TextChange) -> Result<(), ControllerError> {
|
fn send(
|
||||||
|
controller: &mut crate::buffer::Controller,
|
||||||
|
change: TextChange,
|
||||||
|
) -> Result<(), ControllerError> {
|
||||||
super::tokio().block_on(controller.send(change))
|
super::tokio().block_on(controller.send(change))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register a callback for buffer changes.
|
/// Register a callback for buffer changes.
|
||||||
#[jni(package = "mp.code", class = "BufferController")]
|
#[jni(package = "mp.code", class = "BufferController")]
|
||||||
fn callback<'local>(env: &mut JNIEnv<'local>, controller: &mut crate::buffer::Controller, cb: JObject<'local>) {
|
fn callback<'local>(
|
||||||
|
env: &mut JNIEnv<'local>,
|
||||||
|
controller: &mut crate::buffer::Controller,
|
||||||
|
cb: JObject<'local>,
|
||||||
|
) {
|
||||||
null_check!(env, cb, {});
|
null_check!(env, cb, {});
|
||||||
let Ok(cb_ref) = env.new_global_ref(cb) else {
|
let Ok(cb_ref) = env.new_global_ref(cb) else {
|
||||||
env.throw_new("mp/code/exceptions/JNIException", "Failed to pin callback reference!")
|
env.throw_new(
|
||||||
.expect("Failed to throw exception!");
|
"mp/code/exceptions/JNIException",
|
||||||
|
"Failed to pin callback reference!",
|
||||||
|
)
|
||||||
|
.expect("Failed to throw exception!");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
controller.callback(move |controller: crate::buffer::Controller| {
|
controller.callback(move |controller: crate::buffer::Controller| {
|
||||||
let jvm = super::jvm();
|
let jvm = super::jvm();
|
||||||
let mut env = jvm.attach_current_thread_permanently()
|
let mut env = jvm
|
||||||
|
.attach_current_thread_permanently()
|
||||||
.expect("failed attaching to main JVM thread");
|
.expect("failed attaching to main JVM thread");
|
||||||
if let Err(e) = env.with_local_frame(5, |env| {
|
if let Err(e) = env.with_local_frame(5, |env| {
|
||||||
use jni_toolbox::IntoJavaObject;
|
use jni_toolbox::IntoJavaObject;
|
||||||
|
@ -56,7 +72,7 @@ fn callback<'local>(env: &mut JNIEnv<'local>, controller: &mut crate::buffer::Co
|
||||||
&cb_ref,
|
&cb_ref,
|
||||||
"accept",
|
"accept",
|
||||||
"(Ljava/lang/Object;)V",
|
"(Ljava/lang/Object;)V",
|
||||||
&[jni::objects::JValueGen::Object(&jcontroller)]
|
&[jni::objects::JValueGen::Object(&jcontroller)],
|
||||||
) {
|
) {
|
||||||
tracing::error!("error invoking callback: {e:?}");
|
tracing::error!("error invoking callback: {e:?}");
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
|
use crate::{
|
||||||
|
api::Config,
|
||||||
|
client::Client,
|
||||||
|
errors::{ConnectionError, RemoteError},
|
||||||
|
Workspace,
|
||||||
|
};
|
||||||
use jni_toolbox::jni;
|
use jni_toolbox::jni;
|
||||||
use crate::{api::Config, client::Client, errors::{ConnectionError, RemoteError}, Workspace};
|
|
||||||
|
|
||||||
/// Connect using the given credentials to the default server, and return a [Client] to interact with it.
|
/// Connect using the given credentials to the default server, and return a [Client] to interact with it.
|
||||||
#[jni(package = "mp.code", class = "Client", ptr)]
|
#[jni(package = "mp.code", class = "Client", ptr)]
|
||||||
|
@ -33,13 +38,21 @@ fn delete_workspace(client: &mut Client, workspace: String) -> Result<(), Remote
|
||||||
|
|
||||||
/// Invite another user to an owned workspace.
|
/// Invite another user to an owned workspace.
|
||||||
#[jni(package = "mp.code", class = "Client")]
|
#[jni(package = "mp.code", class = "Client")]
|
||||||
fn invite_to_workspace(client: &mut Client, workspace: String, user: String) -> Result<(), RemoteError> {
|
fn invite_to_workspace(
|
||||||
|
client: &mut Client,
|
||||||
|
workspace: String,
|
||||||
|
user: String,
|
||||||
|
) -> Result<(), RemoteError> {
|
||||||
super::tokio().block_on(client.invite_to_workspace(workspace, user))
|
super::tokio().block_on(client.invite_to_workspace(workspace, user))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// List available workspaces.
|
/// List available workspaces.
|
||||||
#[jni(package = "mp.code", class = "Client")]
|
#[jni(package = "mp.code", class = "Client")]
|
||||||
fn list_workspaces(client: &mut Client, owned: bool, invited: bool) -> Result<Vec<String>, RemoteError> {
|
fn list_workspaces(
|
||||||
|
client: &mut Client,
|
||||||
|
owned: bool,
|
||||||
|
invited: bool,
|
||||||
|
) -> Result<Vec<String>, RemoteError> {
|
||||||
super::tokio().block_on(client.list_workspaces(owned, invited))
|
super::tokio().block_on(client.list_workspaces(owned, invited))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +65,7 @@ fn active_workspaces(client: &mut Client) -> Vec<String> {
|
||||||
/// Leave a [Workspace] and return whether or not the client was in such workspace.
|
/// Leave a [Workspace] and return whether or not the client was in such workspace.
|
||||||
#[jni(package = "mp.code", class = "Client")]
|
#[jni(package = "mp.code", class = "Client")]
|
||||||
fn leave_workspace(client: &mut Client, workspace: String) -> bool {
|
fn leave_workspace(client: &mut Client, workspace: String) -> bool {
|
||||||
client.leave_workspace(&workspace)
|
client.leave_workspace(&workspace)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a [Workspace] by name and returns a pointer to it.
|
/// Get a [Workspace] by name and returns a pointer to it.
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
|
use crate::{
|
||||||
|
api::{Controller, Cursor},
|
||||||
|
errors::ControllerError,
|
||||||
|
};
|
||||||
use jni::{objects::JObject, JNIEnv};
|
use jni::{objects::JObject, JNIEnv};
|
||||||
use jni_toolbox::jni;
|
use jni_toolbox::jni;
|
||||||
use crate::{api::{Controller, Cursor}, errors::ControllerError};
|
|
||||||
|
|
||||||
use super::null_check;
|
use super::null_check;
|
||||||
|
|
||||||
|
@ -24,17 +27,25 @@ fn send(controller: &mut crate::cursor::Controller, cursor: Cursor) -> Result<()
|
||||||
|
|
||||||
/// Register a callback for cursor changes.
|
/// Register a callback for cursor changes.
|
||||||
#[jni(package = "mp.code", class = "CursorController")]
|
#[jni(package = "mp.code", class = "CursorController")]
|
||||||
fn callback<'local>(env: &mut JNIEnv<'local>, controller: &mut crate::cursor::Controller, cb: JObject<'local>) {
|
fn callback<'local>(
|
||||||
null_check!(env, cb, {});
|
env: &mut JNIEnv<'local>,
|
||||||
|
controller: &mut crate::cursor::Controller,
|
||||||
|
cb: JObject<'local>,
|
||||||
|
) {
|
||||||
|
null_check!(env, cb, {});
|
||||||
let Ok(cb_ref) = env.new_global_ref(cb) else {
|
let Ok(cb_ref) = env.new_global_ref(cb) else {
|
||||||
env.throw_new("mp/code/exceptions/JNIException", "Failed to pin callback reference!")
|
env.throw_new(
|
||||||
.expect("Failed to throw exception!");
|
"mp/code/exceptions/JNIException",
|
||||||
|
"Failed to pin callback reference!",
|
||||||
|
)
|
||||||
|
.expect("Failed to throw exception!");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
controller.callback(move |controller: crate::cursor::Controller| {
|
controller.callback(move |controller: crate::cursor::Controller| {
|
||||||
let jvm = super::jvm();
|
let jvm = super::jvm();
|
||||||
let mut env = jvm.attach_current_thread_permanently()
|
let mut env = jvm
|
||||||
|
.attach_current_thread_permanently()
|
||||||
.expect("failed attaching to main JVM thread");
|
.expect("failed attaching to main JVM thread");
|
||||||
if let Err(e) = env.with_local_frame(5, |env| {
|
if let Err(e) = env.with_local_frame(5, |env| {
|
||||||
use jni_toolbox::IntoJavaObject;
|
use jni_toolbox::IntoJavaObject;
|
||||||
|
@ -43,7 +54,7 @@ fn callback<'local>(env: &mut JNIEnv<'local>, controller: &mut crate::cursor::Co
|
||||||
&cb_ref,
|
&cb_ref,
|
||||||
"accept",
|
"accept",
|
||||||
"(Ljava/lang/Object;)V",
|
"(Ljava/lang/Object;)V",
|
||||||
&[jni::objects::JValueGen::Object(&jcontroller)]
|
&[jni::objects::JValueGen::Object(&jcontroller)],
|
||||||
) {
|
) {
|
||||||
tracing::error!("error invoking callback: {e:?}");
|
tracing::error!("error invoking callback: {e:?}");
|
||||||
};
|
};
|
||||||
|
@ -58,7 +69,7 @@ fn callback<'local>(env: &mut JNIEnv<'local>, controller: &mut crate::cursor::Co
|
||||||
/// Clear the callback for cursor changes.
|
/// Clear the callback for cursor changes.
|
||||||
#[jni(package = "mp.code", class = "CursorController")]
|
#[jni(package = "mp.code", class = "CursorController")]
|
||||||
fn clear_callback(controller: &mut crate::cursor::Controller) {
|
fn clear_callback(controller: &mut crate::cursor::Controller) {
|
||||||
controller.clear_callback()
|
controller.clear_callback()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Block until there is a new value available.
|
/// Block until there is a new value available.
|
||||||
|
|
|
@ -4,7 +4,7 @@ use jni_toolbox::jni;
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
#[jni(package = "mp.code", class = "Extensions")]
|
#[jni(package = "mp.code", class = "Extensions")]
|
||||||
fn version() -> String {
|
fn version() -> String {
|
||||||
crate::version()
|
crate::version()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate the XXH3 hash for a given String.
|
/// Calculate the XXH3 hash for a given String.
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
pub mod client;
|
|
||||||
pub mod workspace;
|
|
||||||
pub mod cursor;
|
|
||||||
pub mod buffer;
|
pub mod buffer;
|
||||||
|
pub mod client;
|
||||||
|
pub mod cursor;
|
||||||
pub mod ext;
|
pub mod ext;
|
||||||
|
pub mod workspace;
|
||||||
|
|
||||||
/// Gets or creates the relevant [tokio::runtime::Runtime].
|
/// Gets or creates the relevant [tokio::runtime::Runtime].
|
||||||
fn tokio() -> &'static tokio::runtime::Runtime {
|
fn tokio() -> &'static tokio::runtime::Runtime {
|
||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
static RT: OnceLock<tokio::runtime::Runtime> = OnceLock::new();
|
static RT: OnceLock<tokio::runtime::Runtime> = OnceLock::new();
|
||||||
RT.get_or_init(||
|
RT.get_or_init(|| {
|
||||||
tokio::runtime::Builder::new_current_thread()
|
tokio::runtime::Builder::new_current_thread()
|
||||||
.enable_all()
|
.enable_all()
|
||||||
.build()
|
.build()
|
||||||
.expect("could not create tokio runtime")
|
.expect("could not create tokio runtime")
|
||||||
)
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A static reference to [jni::JavaVM] that is set on JNI load.
|
/// A static reference to [jni::JavaVM] that is set on JNI load.
|
||||||
|
@ -27,10 +27,7 @@ pub(crate) fn jvm() -> std::sync::Arc<jni::JavaVM> {
|
||||||
/// Called upon initialisation of the JVM.
|
/// Called upon initialisation of the JVM.
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "system" fn JNI_OnLoad(
|
pub extern "system" fn JNI_OnLoad(vm: jni::JavaVM, _: *mut std::ffi::c_void) -> jni::sys::jint {
|
||||||
vm: jni::JavaVM,
|
|
||||||
_: *mut std::ffi::c_void
|
|
||||||
) -> jni::sys::jint {
|
|
||||||
unsafe { JVM = Some(std::sync::Arc::new(vm)) };
|
unsafe { JVM = Some(std::sync::Arc::new(vm)) };
|
||||||
jni::sys::JNI_VERSION_1_1
|
jni::sys::JNI_VERSION_1_1
|
||||||
}
|
}
|
||||||
|
@ -48,7 +45,11 @@ pub(crate) fn setup_logger(debug: bool, path: Option<String>) {
|
||||||
.with_source_location(false)
|
.with_source_location(false)
|
||||||
.compact();
|
.compact();
|
||||||
|
|
||||||
let level = if debug { tracing::Level::DEBUG } else {tracing::Level::INFO };
|
let level = if debug {
|
||||||
|
tracing::Level::DEBUG
|
||||||
|
} else {
|
||||||
|
tracing::Level::INFO
|
||||||
|
};
|
||||||
|
|
||||||
let builder = tracing_subscriber::fmt()
|
let builder = tracing_subscriber::fmt()
|
||||||
.event_format(format)
|
.event_format(format)
|
||||||
|
@ -58,13 +59,16 @@ pub(crate) fn setup_logger(debug: bool, path: Option<String>) {
|
||||||
let logfile = std::fs::File::create(path).expect("failed creating logfile");
|
let logfile = std::fs::File::create(path).expect("failed creating logfile");
|
||||||
builder.with_writer(std::sync::Mutex::new(logfile)).init();
|
builder.with_writer(std::sync::Mutex::new(logfile)).init();
|
||||||
} else {
|
} else {
|
||||||
builder.with_writer(std::sync::Mutex::new(std::io::stdout())).init();
|
builder
|
||||||
|
.with_writer(std::sync::Mutex::new(std::io::stdout()))
|
||||||
|
.init();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Performs a null check on the given variable and throws a NullPointerException on the Java side
|
/// Performs a null check on the given variable and throws a NullPointerException on the Java side
|
||||||
/// if it is null. Finally, it returns with the given default value.
|
/// if it is null. Finally, it returns with the given default value.
|
||||||
macro_rules! null_check { // TODO replace
|
macro_rules! null_check {
|
||||||
|
// TODO replace
|
||||||
($env: ident, $var: ident, $return: expr) => {
|
($env: ident, $var: ident, $return: expr) => {
|
||||||
if $var.is_null() {
|
if $var.is_null() {
|
||||||
let mut message = stringify!($var).to_string();
|
let mut message = stringify!($var).to_string();
|
||||||
|
@ -79,10 +83,15 @@ pub(crate) use null_check;
|
||||||
|
|
||||||
impl jni_toolbox::JniToolboxError for crate::errors::ConnectionError {
|
impl jni_toolbox::JniToolboxError for crate::errors::ConnectionError {
|
||||||
fn jclass(&self) -> String {
|
fn jclass(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
crate::errors::ConnectionError::Transport(_) => "mp/code/exceptions/ConnectionTransportException",
|
crate::errors::ConnectionError::Transport(_) => {
|
||||||
crate::errors::ConnectionError::Remote(_) => "mp/code/exceptions/ConnectionRemoteException"
|
"mp/code/exceptions/ConnectionTransportException"
|
||||||
}.to_string()
|
}
|
||||||
|
crate::errors::ConnectionError::Remote(_) => {
|
||||||
|
"mp/code/exceptions/ConnectionRemoteException"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,10 +103,15 @@ impl jni_toolbox::JniToolboxError for crate::errors::RemoteError {
|
||||||
|
|
||||||
impl jni_toolbox::JniToolboxError for crate::errors::ControllerError {
|
impl jni_toolbox::JniToolboxError for crate::errors::ControllerError {
|
||||||
fn jclass(&self) -> String {
|
fn jclass(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
crate::errors::ControllerError::Stopped => "mp/code/exceptions/ControllerStoppedException",
|
crate::errors::ControllerError::Stopped => {
|
||||||
crate::errors::ControllerError::Unfulfilled => "mp/code/exceptions/ControllerUnfulfilledException",
|
"mp/code/exceptions/ControllerStoppedException"
|
||||||
}.to_string()
|
}
|
||||||
|
crate::errors::ControllerError::Unfulfilled => {
|
||||||
|
"mp/code/exceptions/ControllerUnfulfilledException"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,12 +120,17 @@ macro_rules! into_java_ptr_class {
|
||||||
($type: ty, $jclass: literal) => {
|
($type: ty, $jclass: literal) => {
|
||||||
impl<'j> jni_toolbox::IntoJavaObject<'j> for $type {
|
impl<'j> jni_toolbox::IntoJavaObject<'j> for $type {
|
||||||
const CLASS: &'static str = $jclass;
|
const CLASS: &'static str = $jclass;
|
||||||
fn into_java_object(self, env: &mut jni::JNIEnv<'j>) -> Result<jni::objects::JObject<'j>, jni::errors::Error> {
|
fn into_java_object(
|
||||||
|
self,
|
||||||
|
env: &mut jni::JNIEnv<'j>,
|
||||||
|
) -> Result<jni::objects::JObject<'j>, jni::errors::Error> {
|
||||||
let class = env.find_class(Self::CLASS)?;
|
let class = env.find_class(Self::CLASS)?;
|
||||||
env.new_object(
|
env.new_object(
|
||||||
class,
|
class,
|
||||||
"(J)V",
|
"(J)V",
|
||||||
&[jni::objects::JValueGen::Long(Box::into_raw(Box::new(self)) as jni::sys::jlong)]
|
&[jni::objects::JValueGen::Long(
|
||||||
|
Box::into_raw(Box::new(self)) as jni::sys::jlong
|
||||||
|
)],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -125,7 +144,10 @@ into_java_ptr_class!(crate::buffer::Controller, "mp/code/BufferController");
|
||||||
|
|
||||||
impl<'j> jni_toolbox::IntoJavaObject<'j> for crate::api::User {
|
impl<'j> jni_toolbox::IntoJavaObject<'j> for crate::api::User {
|
||||||
const CLASS: &'static str = "mp/code/data/User";
|
const CLASS: &'static str = "mp/code/data/User";
|
||||||
fn into_java_object(self, env: &mut jni::JNIEnv<'j>) -> Result<jni::objects::JObject<'j>, jni::errors::Error> {
|
fn into_java_object(
|
||||||
|
self,
|
||||||
|
env: &mut jni::JNIEnv<'j>,
|
||||||
|
) -> Result<jni::objects::JObject<'j>, jni::errors::Error> {
|
||||||
let id_field = self.id.into_java_object(env)?;
|
let id_field = self.id.into_java_object(env)?;
|
||||||
let name_field = env.new_string(self.name)?;
|
let name_field = env.new_string(self.name)?;
|
||||||
let class = env.find_class(Self::CLASS)?;
|
let class = env.find_class(Self::CLASS)?;
|
||||||
|
@ -134,15 +156,18 @@ impl<'j> jni_toolbox::IntoJavaObject<'j> for crate::api::User {
|
||||||
"(Ljava/util/UUID;Ljava/lang/String;)V",
|
"(Ljava/util/UUID;Ljava/lang/String;)V",
|
||||||
&[
|
&[
|
||||||
jni::objects::JValueGen::Object(&id_field),
|
jni::objects::JValueGen::Object(&id_field),
|
||||||
jni::objects::JValueGen::Object(&name_field)
|
jni::objects::JValueGen::Object(&name_field),
|
||||||
]
|
],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'j> jni_toolbox::IntoJavaObject<'j> for crate::api::Event {
|
impl<'j> jni_toolbox::IntoJavaObject<'j> for crate::api::Event {
|
||||||
const CLASS: &'static str = "mp/code/Workspace$Event";
|
const CLASS: &'static str = "mp/code/Workspace$Event";
|
||||||
fn into_java_object(self, env: &mut jni::JNIEnv<'j>) -> Result<jni::objects::JObject<'j>, jni::errors::Error> {
|
fn into_java_object(
|
||||||
|
self,
|
||||||
|
env: &mut jni::JNIEnv<'j>,
|
||||||
|
) -> Result<jni::objects::JObject<'j>, jni::errors::Error> {
|
||||||
let (ordinal, arg) = match self {
|
let (ordinal, arg) = match self {
|
||||||
crate::api::Event::UserJoin(arg) => (0, env.new_string(arg)?),
|
crate::api::Event::UserJoin(arg) => (0, env.new_string(arg)?),
|
||||||
crate::api::Event::UserLeave(arg) => (1, env.new_string(arg)?),
|
crate::api::Event::UserLeave(arg) => (1, env.new_string(arg)?),
|
||||||
|
@ -150,37 +175,44 @@ impl<'j> jni_toolbox::IntoJavaObject<'j> for crate::api::Event {
|
||||||
};
|
};
|
||||||
|
|
||||||
let type_class = env.find_class("mp/code/Workspace$Event$Type")?;
|
let type_class = env.find_class("mp/code/Workspace$Event$Type")?;
|
||||||
let variants: jni::objects::JObjectArray = env.call_method(
|
let variants: jni::objects::JObjectArray = env
|
||||||
type_class,
|
.call_method(type_class, "getEnumConstants", "()[Ljava/lang/Object;", &[])?
|
||||||
"getEnumConstants",
|
.l()?
|
||||||
"()[Ljava/lang/Object;",
|
.into();
|
||||||
&[]
|
|
||||||
)?.l()?.into();
|
|
||||||
let event_type = env.get_object_array_element(variants, ordinal)?;
|
let event_type = env.get_object_array_element(variants, ordinal)?;
|
||||||
|
|
||||||
let event_class = env.find_class(Self::CLASS)?;
|
let event_class = env.find_class(Self::CLASS)?;
|
||||||
env.new_object(
|
env.new_object(
|
||||||
event_class,
|
event_class,
|
||||||
"(Lmp/code/Workspace$Event$Type;Ljava/lang/String;)V",
|
"(Lmp/code/Workspace$Event$Type;Ljava/lang/String;)V",
|
||||||
&[
|
&[
|
||||||
jni::objects::JValueGen::Object(&event_type),
|
jni::objects::JValueGen::Object(&event_type),
|
||||||
jni::objects::JValueGen::Object(&arg)
|
jni::objects::JValueGen::Object(&arg),
|
||||||
]
|
],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'j> jni_toolbox::IntoJavaObject<'j> for crate::api::TextChange {
|
impl<'j> jni_toolbox::IntoJavaObject<'j> for crate::api::TextChange {
|
||||||
const CLASS: &'static str = "mp/code/data/TextChange";
|
const CLASS: &'static str = "mp/code/data/TextChange";
|
||||||
fn into_java_object(self, env: &mut jni::JNIEnv<'j>) -> Result<jni::objects::JObject<'j>, jni::errors::Error> {
|
fn into_java_object(
|
||||||
|
self,
|
||||||
|
env: &mut jni::JNIEnv<'j>,
|
||||||
|
) -> Result<jni::objects::JObject<'j>, jni::errors::Error> {
|
||||||
let content = env.new_string(self.content)?;
|
let content = env.new_string(self.content)?;
|
||||||
|
|
||||||
let hash_class = env.find_class("java/util/OptionalLong")?;
|
let hash_class = env.find_class("java/util/OptionalLong")?;
|
||||||
let hash = if let Some(h) = self.hash {
|
let hash = if let Some(h) = self.hash {
|
||||||
env.call_static_method(hash_class, "of", "(J)Ljava/util/OptionalLong;", &[jni::objects::JValueGen::Long(h)])
|
env.call_static_method(
|
||||||
|
hash_class,
|
||||||
|
"of",
|
||||||
|
"(J)Ljava/util/OptionalLong;",
|
||||||
|
&[jni::objects::JValueGen::Long(h)],
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
env.call_static_method(hash_class, "empty", "()Ljava/util/OptionalLong;", &[])
|
env.call_static_method(hash_class, "empty", "()Ljava/util/OptionalLong;", &[])
|
||||||
}?.l()?;
|
}?
|
||||||
|
.l()?;
|
||||||
|
|
||||||
let class = env.find_class(Self::CLASS)?;
|
let class = env.find_class(Self::CLASS)?;
|
||||||
env.new_object(
|
env.new_object(
|
||||||
|
@ -190,15 +222,18 @@ impl<'j> jni_toolbox::IntoJavaObject<'j> for crate::api::TextChange {
|
||||||
jni::objects::JValueGen::Long(self.start.into()),
|
jni::objects::JValueGen::Long(self.start.into()),
|
||||||
jni::objects::JValueGen::Long(self.end.into()),
|
jni::objects::JValueGen::Long(self.end.into()),
|
||||||
jni::objects::JValueGen::Object(&content),
|
jni::objects::JValueGen::Object(&content),
|
||||||
jni::objects::JValueGen::Object(&hash)
|
jni::objects::JValueGen::Object(&hash),
|
||||||
]
|
],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'j> jni_toolbox::IntoJavaObject<'j> for crate::api::Cursor {
|
impl<'j> jni_toolbox::IntoJavaObject<'j> for crate::api::Cursor {
|
||||||
const CLASS: &'static str = "mp/code/data/Cursor";
|
const CLASS: &'static str = "mp/code/data/Cursor";
|
||||||
fn into_java_object(self, env: &mut jni::JNIEnv<'j>) -> Result<jni::objects::JObject<'j>, jni::errors::Error> {
|
fn into_java_object(
|
||||||
|
self,
|
||||||
|
env: &mut jni::JNIEnv<'j>,
|
||||||
|
) -> Result<jni::objects::JObject<'j>, jni::errors::Error> {
|
||||||
let class = env.find_class("mp/code/data/Cursor")?;
|
let class = env.find_class("mp/code/data/Cursor")?;
|
||||||
let buffer = env.new_string(&self.buffer)?;
|
let buffer = env.new_string(&self.buffer)?;
|
||||||
let user = if let Some(user) = self.user {
|
let user = if let Some(user) = self.user {
|
||||||
|
@ -216,8 +251,8 @@ impl<'j> jni_toolbox::IntoJavaObject<'j> for crate::api::Cursor {
|
||||||
jni::objects::JValueGen::Int(self.end.0),
|
jni::objects::JValueGen::Int(self.end.0),
|
||||||
jni::objects::JValueGen::Int(self.end.1),
|
jni::objects::JValueGen::Int(self.end.1),
|
||||||
jni::objects::JValueGen::Object(&buffer),
|
jni::objects::JValueGen::Object(&buffer),
|
||||||
jni::objects::JValueGen::Object(&user)
|
jni::objects::JValueGen::Object(&user),
|
||||||
]
|
],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -226,7 +261,10 @@ macro_rules! from_java_ptr {
|
||||||
($type: ty) => {
|
($type: ty) => {
|
||||||
impl<'j> jni_toolbox::FromJava<'j> for &mut $type {
|
impl<'j> jni_toolbox::FromJava<'j> for &mut $type {
|
||||||
type From = jni::sys::jobject;
|
type From = jni::sys::jobject;
|
||||||
fn from_java(_env: &mut jni::JNIEnv<'j>, value: Self::From) -> Result<Self, jni::errors::Error> {
|
fn from_java(
|
||||||
|
_env: &mut jni::JNIEnv<'j>,
|
||||||
|
value: Self::From,
|
||||||
|
) -> Result<Self, jni::errors::Error> {
|
||||||
Ok(unsafe { Box::leak(Box::from_raw(value as *mut $type)) })
|
Ok(unsafe { Box::leak(Box::from_raw(value as *mut $type)) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -240,9 +278,14 @@ from_java_ptr!(crate::buffer::Controller);
|
||||||
|
|
||||||
impl<'j> jni_toolbox::FromJava<'j> for crate::api::Config {
|
impl<'j> jni_toolbox::FromJava<'j> for crate::api::Config {
|
||||||
type From = jni::objects::JObject<'j>;
|
type From = jni::objects::JObject<'j>;
|
||||||
fn from_java(env: &mut jni::JNIEnv<'j>, config: Self::From) -> Result<Self, jni::errors::Error> {
|
fn from_java(
|
||||||
|
env: &mut jni::JNIEnv<'j>,
|
||||||
|
config: Self::From,
|
||||||
|
) -> Result<Self, jni::errors::Error> {
|
||||||
let username = {
|
let username = {
|
||||||
let jfield = env.get_field(&config, "username", "Ljava/lang/String;")?.l()?;
|
let jfield = env
|
||||||
|
.get_field(&config, "username", "Ljava/lang/String;")?
|
||||||
|
.l()?;
|
||||||
if jfield.is_null() {
|
if jfield.is_null() {
|
||||||
return Err(jni::errors::Error::NullPtr("Username can never be null!"));
|
return Err(jni::errors::Error::NullPtr("Username can never be null!"));
|
||||||
}
|
}
|
||||||
|
@ -250,7 +293,9 @@ impl<'j> jni_toolbox::FromJava<'j> for crate::api::Config {
|
||||||
};
|
};
|
||||||
|
|
||||||
let password = {
|
let password = {
|
||||||
let jfield = env.get_field(&config, "password", "Ljava/lang/String;")?.l()?;
|
let jfield = env
|
||||||
|
.get_field(&config, "password", "Ljava/lang/String;")?
|
||||||
|
.l()?;
|
||||||
if jfield.is_null() {
|
if jfield.is_null() {
|
||||||
return Err(jni::errors::Error::NullPtr("Password can never be null!"));
|
return Err(jni::errors::Error::NullPtr("Password can never be null!"));
|
||||||
}
|
}
|
||||||
|
@ -258,9 +303,13 @@ impl<'j> jni_toolbox::FromJava<'j> for crate::api::Config {
|
||||||
};
|
};
|
||||||
|
|
||||||
let host = {
|
let host = {
|
||||||
let jfield = env.get_field(&config, "host", "Ljava/util/Optional;")?.l()?;
|
let jfield = env
|
||||||
|
.get_field(&config, "host", "Ljava/util/Optional;")?
|
||||||
|
.l()?;
|
||||||
if env.call_method(&jfield, "isPresent", "()Z", &[])?.z()? {
|
if env.call_method(&jfield, "isPresent", "()Z", &[])?.z()? {
|
||||||
let field = env.call_method(&jfield, "get", "()Ljava/lang/Object;", &[])?.l()?;
|
let field = env
|
||||||
|
.call_method(&jfield, "get", "()Ljava/lang/Object;", &[])?
|
||||||
|
.l()?;
|
||||||
Some(unsafe { env.get_string_unchecked(&field.into()) }?.into())
|
Some(unsafe { env.get_string_unchecked(&field.into()) }?.into())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -268,7 +317,9 @@ impl<'j> jni_toolbox::FromJava<'j> for crate::api::Config {
|
||||||
};
|
};
|
||||||
|
|
||||||
let port = {
|
let port = {
|
||||||
let jfield = env.get_field(&config, "port", "Ljava/util/OptionalInt;")?.l()?;
|
let jfield = env
|
||||||
|
.get_field(&config, "port", "Ljava/util/OptionalInt;")?
|
||||||
|
.l()?;
|
||||||
if env.call_method(&jfield, "isPresent", "()Z", &[])?.z()? {
|
if env.call_method(&jfield, "isPresent", "()Z", &[])?.z()? {
|
||||||
let ivalue = env.call_method(&jfield, "getAsInt", "()I", &[])?.i()?;
|
let ivalue = env.call_method(&jfield, "getAsInt", "()I", &[])?.i()?;
|
||||||
Some(ivalue.clamp(0, 65535) as u16)
|
Some(ivalue.clamp(0, 65535) as u16)
|
||||||
|
@ -278,41 +329,61 @@ impl<'j> jni_toolbox::FromJava<'j> for crate::api::Config {
|
||||||
};
|
};
|
||||||
|
|
||||||
let tls = {
|
let tls = {
|
||||||
let jfield = env.get_field(&config, "host", "Ljava/util/Optional;")?.l()?;
|
let jfield = env
|
||||||
|
.get_field(&config, "host", "Ljava/util/Optional;")?
|
||||||
|
.l()?;
|
||||||
if env.call_method(&jfield, "isPresent", "()Z", &[])?.z()? {
|
if env.call_method(&jfield, "isPresent", "()Z", &[])?.z()? {
|
||||||
let field = env.call_method(&jfield, "get", "()Ljava/lang/Object;", &[])?.l()?;
|
let field = env
|
||||||
let bool_true = env.get_static_field("java/lang/Boolean", "TRUE", "Ljava/lang/Boolean;")?.l()?;
|
.call_method(&jfield, "get", "()Ljava/lang/Object;", &[])?
|
||||||
Some(env.call_method(
|
.l()?;
|
||||||
field,
|
let bool_true = env
|
||||||
"equals",
|
.get_static_field("java/lang/Boolean", "TRUE", "Ljava/lang/Boolean;")?
|
||||||
"(Ljava/lang/Object;)Z",
|
.l()?;
|
||||||
&[jni::objects::JValueGen::Object(&bool_true)]
|
Some(
|
||||||
)?.z()?) // what a joke
|
env.call_method(
|
||||||
|
field,
|
||||||
|
"equals",
|
||||||
|
"(Ljava/lang/Object;)Z",
|
||||||
|
&[jni::objects::JValueGen::Object(&bool_true)],
|
||||||
|
)?
|
||||||
|
.z()?,
|
||||||
|
) // what a joke
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Self { username, password, host, port, tls })
|
Ok(Self {
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
host,
|
||||||
|
port,
|
||||||
|
tls,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'j> jni_toolbox::FromJava<'j> for crate::api::Cursor {
|
impl<'j> jni_toolbox::FromJava<'j> for crate::api::Cursor {
|
||||||
type From = jni::objects::JObject<'j>;
|
type From = jni::objects::JObject<'j>;
|
||||||
fn from_java(env: &mut jni::JNIEnv<'j>, cursor: Self::From) -> Result<Self, jni::errors::Error> {
|
fn from_java(
|
||||||
|
env: &mut jni::JNIEnv<'j>,
|
||||||
|
cursor: Self::From,
|
||||||
|
) -> Result<Self, jni::errors::Error> {
|
||||||
let start_row = env.get_field(&cursor, "startRow", "I")?.i()?;
|
let start_row = env.get_field(&cursor, "startRow", "I")?.i()?;
|
||||||
let start_col = env.get_field(&cursor, "startCol", "I")?.i()?;
|
let start_col = env.get_field(&cursor, "startCol", "I")?.i()?;
|
||||||
let end_row = env.get_field(&cursor, "endRow", "I")?.i()?;
|
let end_row = env.get_field(&cursor, "endRow", "I")?.i()?;
|
||||||
let end_col = env.get_field(&cursor, "endCol", "I")?.i()?;
|
let end_col = env.get_field(&cursor, "endCol", "I")?.i()?;
|
||||||
|
|
||||||
let buffer = {
|
let buffer = {
|
||||||
let jfield = env.get_field(&cursor, "buffer", "Ljava/lang/String;")?.l()?;
|
let jfield = env
|
||||||
|
.get_field(&cursor, "buffer", "Ljava/lang/String;")?
|
||||||
|
.l()?;
|
||||||
if jfield.is_null() {
|
if jfield.is_null() {
|
||||||
return Err(jni::errors::Error::NullPtr("Buffer can never be null!"));
|
return Err(jni::errors::Error::NullPtr("Buffer can never be null!"));
|
||||||
}
|
}
|
||||||
unsafe { env.get_string_unchecked(&jfield.into()) }?.into()
|
unsafe { env.get_string_unchecked(&jfield.into()) }?.into()
|
||||||
};
|
};
|
||||||
|
|
||||||
let user = {
|
let user = {
|
||||||
let jfield = env.get_field(&cursor, "user", "Ljava/lang/String;")?.l()?;
|
let jfield = env.get_field(&cursor, "user", "Ljava/lang/String;")?.l()?;
|
||||||
if jfield.is_null() {
|
if jfield.is_null() {
|
||||||
|
@ -322,18 +393,34 @@ impl<'j> jni_toolbox::FromJava<'j> for crate::api::Cursor {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Self { start: (start_row, start_col), end: (end_row, end_col), buffer, user })
|
Ok(Self {
|
||||||
|
start: (start_row, start_col),
|
||||||
|
end: (end_row, end_col),
|
||||||
|
buffer,
|
||||||
|
user,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'j> jni_toolbox::FromJava<'j> for crate::api::TextChange {
|
impl<'j> jni_toolbox::FromJava<'j> for crate::api::TextChange {
|
||||||
type From = jni::objects::JObject<'j>;
|
type From = jni::objects::JObject<'j>;
|
||||||
fn from_java(env: &mut jni::JNIEnv<'j>, change: Self::From) -> Result<Self, jni::errors::Error> {
|
fn from_java(
|
||||||
let start = env.get_field(&change, "start", "J")?.j()?.clamp(0, u32::MAX.into()) as u32;
|
env: &mut jni::JNIEnv<'j>,
|
||||||
let end = env.get_field(&change, "end", "J")?.j()?.clamp(0, u32::MAX.into()) as u32;
|
change: Self::From,
|
||||||
|
) -> Result<Self, jni::errors::Error> {
|
||||||
|
let start = env
|
||||||
|
.get_field(&change, "start", "J")?
|
||||||
|
.j()?
|
||||||
|
.clamp(0, u32::MAX.into()) as u32;
|
||||||
|
let end = env
|
||||||
|
.get_field(&change, "end", "J")?
|
||||||
|
.j()?
|
||||||
|
.clamp(0, u32::MAX.into()) as u32;
|
||||||
|
|
||||||
let content = {
|
let content = {
|
||||||
let jfield = env.get_field(&change, "content", "Ljava/lang/String;")?.l()?;
|
let jfield = env
|
||||||
|
.get_field(&change, "content", "Ljava/lang/String;")?
|
||||||
|
.l()?;
|
||||||
if jfield.is_null() {
|
if jfield.is_null() {
|
||||||
return Err(jni::errors::Error::NullPtr("Content can never be null!"));
|
return Err(jni::errors::Error::NullPtr("Content can never be null!"));
|
||||||
}
|
}
|
||||||
|
@ -341,13 +428,20 @@ impl<'j> jni_toolbox::FromJava<'j> for crate::api::TextChange {
|
||||||
};
|
};
|
||||||
|
|
||||||
let hash = {
|
let hash = {
|
||||||
let jfield = env.get_field(&change, "hash", "Ljava/util/OptionalLong;")?.l()?;
|
let jfield = env
|
||||||
|
.get_field(&change, "hash", "Ljava/util/OptionalLong;")?
|
||||||
|
.l()?;
|
||||||
if env.call_method(&jfield, "isPresent", "()Z", &[])?.z()? {
|
if env.call_method(&jfield, "isPresent", "()Z", &[])?.z()? {
|
||||||
Some(env.call_method(&jfield, "getAsLong", "()J", &[])?.j()?)
|
Some(env.call_method(&jfield, "getAsLong", "()J", &[])?.j()?)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(Self { start, end, content, hash })
|
Ok(Self {
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
content,
|
||||||
|
hash,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
|
use crate::{
|
||||||
|
errors::{ConnectionError, ControllerError, RemoteError},
|
||||||
|
Workspace,
|
||||||
|
};
|
||||||
use jni_toolbox::jni;
|
use jni_toolbox::jni;
|
||||||
use crate::{errors::{ConnectionError, ControllerError, RemoteError}, Workspace};
|
|
||||||
|
|
||||||
/// Get the workspace id.
|
/// Get the workspace id.
|
||||||
#[jni(package = "mp.code", class = "Workspace")]
|
#[jni(package = "mp.code", class = "Workspace")]
|
||||||
|
@ -45,7 +48,10 @@ fn create_buffer(workspace: &mut Workspace, path: String) -> Result<(), RemoteEr
|
||||||
|
|
||||||
/// Attach to a buffer and return a pointer to its [crate::buffer::Controller].
|
/// Attach to a buffer and return a pointer to its [crate::buffer::Controller].
|
||||||
#[jni(package = "mp.code", class = "Workspace")]
|
#[jni(package = "mp.code", class = "Workspace")]
|
||||||
fn attach_to_buffer(workspace: &mut Workspace, path: String) -> Result<crate::buffer::Controller, ConnectionError> {
|
fn attach_to_buffer(
|
||||||
|
workspace: &mut Workspace,
|
||||||
|
path: String,
|
||||||
|
) -> Result<crate::buffer::Controller, ConnectionError> {
|
||||||
super::tokio().block_on(workspace.attach(&path))
|
super::tokio().block_on(workspace.attach(&path))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +75,10 @@ fn fetch_users(workspace: &mut Workspace) -> Result<(), RemoteError> {
|
||||||
|
|
||||||
/// List users attached to a buffer.
|
/// List users attached to a buffer.
|
||||||
#[jni(package = "mp.code", class = "Workspace")]
|
#[jni(package = "mp.code", class = "Workspace")]
|
||||||
fn list_buffer_users(workspace: &mut Workspace, path: String) -> Result<Vec<crate::api::User>, RemoteError> {
|
fn list_buffer_users(
|
||||||
|
workspace: &mut Workspace,
|
||||||
|
path: String,
|
||||||
|
) -> Result<Vec<crate::api::User>, RemoteError> {
|
||||||
super::tokio().block_on(workspace.list_buffer_users(&path))
|
super::tokio().block_on(workspace.list_buffer_users(&path))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,24 +1,28 @@
|
||||||
use napi::threadsafe_function::{ErrorStrategy::Fatal, ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode};
|
|
||||||
use napi_derive::napi;
|
|
||||||
use crate::api::TextChange;
|
|
||||||
use crate::api::Controller;
|
use crate::api::Controller;
|
||||||
|
use crate::api::TextChange;
|
||||||
use crate::buffer::controller::BufferController;
|
use crate::buffer::controller::BufferController;
|
||||||
|
use napi::threadsafe_function::{
|
||||||
|
ErrorStrategy::Fatal, ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode,
|
||||||
|
};
|
||||||
|
use napi_derive::napi;
|
||||||
|
|
||||||
#[napi]
|
#[napi]
|
||||||
impl BufferController {
|
impl BufferController {
|
||||||
|
|
||||||
/// Register a callback to be invoked every time a new event is available to consume
|
/// Register a callback to be invoked every time a new event is available to consume
|
||||||
/// There can only be one callback registered at any given time.
|
/// There can only be one callback registered at any given time.
|
||||||
#[napi(js_name = "callback", ts_args_type = "fun: (event: BufferController) => void")]
|
#[napi(
|
||||||
pub fn js_callback(&self, fun: napi::JsFunction) -> napi::Result<()>{
|
js_name = "callback",
|
||||||
let tsfn : ThreadsafeFunction<crate::buffer::controller::BufferController, Fatal> =
|
ts_args_type = "fun: (event: BufferController) => void"
|
||||||
fun.create_threadsafe_function(0,
|
)]
|
||||||
|ctx : ThreadSafeCallContext<crate::buffer::controller::BufferController>| {
|
pub fn js_callback(&self, fun: napi::JsFunction) -> napi::Result<()> {
|
||||||
Ok(vec![ctx.value])
|
let tsfn: ThreadsafeFunction<crate::buffer::controller::BufferController, Fatal> = fun
|
||||||
}
|
.create_threadsafe_function(
|
||||||
)?;
|
0,
|
||||||
self.callback(move |controller : BufferController| {
|
|ctx: ThreadSafeCallContext<crate::buffer::controller::BufferController>| {
|
||||||
|
Ok(vec![ctx.value])
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
self.callback(move |controller: BufferController| {
|
||||||
tsfn.call(controller.clone(), ThreadsafeFunctionCallMode::Blocking);
|
tsfn.call(controller.clone(), ThreadsafeFunctionCallMode::Blocking);
|
||||||
//check this with tracing also we could use Ok(event) to get the error
|
//check this with tracing also we could use Ok(event) to get the error
|
||||||
// If it blocks the main thread too many time we have to change this
|
// If it blocks the main thread too many time we have to change this
|
||||||
|
@ -32,7 +36,7 @@ impl BufferController {
|
||||||
pub fn js_clear_callback(&self) {
|
pub fn js_clear_callback(&self) {
|
||||||
self.clear_callback();
|
self.clear_callback();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get buffer path
|
/// Get buffer path
|
||||||
#[napi(js_name = "get_path")]
|
#[napi(js_name = "get_path")]
|
||||||
pub fn js_path(&self) -> &str {
|
pub fn js_path(&self) -> &str {
|
||||||
|
@ -41,7 +45,7 @@ impl BufferController {
|
||||||
|
|
||||||
/// Block until next buffer event without returning it
|
/// Block until next buffer event without returning it
|
||||||
#[napi(js_name = "poll")]
|
#[napi(js_name = "poll")]
|
||||||
pub async fn js_poll(&self) -> napi::Result<()>{
|
pub async fn js_poll(&self) -> napi::Result<()> {
|
||||||
Ok(self.poll().await?)
|
Ok(self.poll().await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,4 +72,4 @@ impl BufferController {
|
||||||
pub async fn js_content(&self) -> napi::Result<String> {
|
pub async fn js_content(&self) -> napi::Result<String> {
|
||||||
Ok(self.content().await?)
|
Ok(self.content().await?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use napi_derive::napi;
|
|
||||||
use crate::{Client, Workspace};
|
use crate::{Client, Workspace};
|
||||||
|
use napi_derive::napi;
|
||||||
|
|
||||||
#[napi(object, js_name = "User")]
|
#[napi(object, js_name = "User")]
|
||||||
pub struct JsUser {
|
pub struct JsUser {
|
||||||
|
@ -28,7 +28,7 @@ impl From<crate::api::User> for JsUser {
|
||||||
|
|
||||||
#[napi]
|
#[napi]
|
||||||
/// connect to codemp servers and return a client session
|
/// connect to codemp servers and return a client session
|
||||||
pub async fn connect(config: crate::api::Config) -> napi::Result<crate::Client>{
|
pub async fn connect(config: crate::api::Config) -> napi::Result<crate::Client> {
|
||||||
Ok(crate::Client::connect(config).await?)
|
Ok(crate::Client::connect(config).await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,13 +48,21 @@ impl Client {
|
||||||
|
|
||||||
#[napi(js_name = "list_workspaces")]
|
#[napi(js_name = "list_workspaces")]
|
||||||
/// list available workspaces
|
/// list available workspaces
|
||||||
pub async fn js_list_workspaces(&self, owned: bool, invited: bool) -> napi::Result<Vec<String>> {
|
pub async fn js_list_workspaces(
|
||||||
|
&self,
|
||||||
|
owned: bool,
|
||||||
|
invited: bool,
|
||||||
|
) -> napi::Result<Vec<String>> {
|
||||||
Ok(self.list_workspaces(owned, invited).await?)
|
Ok(self.list_workspaces(owned, invited).await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi(js_name = "invite_to_workspace")]
|
#[napi(js_name = "invite_to_workspace")]
|
||||||
/// invite user to given workspace, if able to
|
/// invite user to given workspace, if able to
|
||||||
pub async fn js_invite_to_workspace(&self, workspace: String, user: String) -> napi::Result<()> {
|
pub async fn js_invite_to_workspace(
|
||||||
|
&self,
|
||||||
|
workspace: String,
|
||||||
|
user: String,
|
||||||
|
) -> napi::Result<()> {
|
||||||
Ok(self.invite_to_workspace(workspace, user).await?)
|
Ok(self.invite_to_workspace(workspace, user).await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,4 +101,4 @@ impl Client {
|
||||||
pub async fn js_refresh(&self) -> napi::Result<()> {
|
pub async fn js_refresh(&self) -> napi::Result<()> {
|
||||||
Ok(self.refresh().await?)
|
Ok(self.refresh().await?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
use napi::threadsafe_function::ErrorStrategy::Fatal;
|
|
||||||
use napi_derive::napi;
|
|
||||||
use napi::threadsafe_function::{ThreadsafeFunction, ThreadSafeCallContext, ThreadsafeFunctionCallMode};
|
|
||||||
use crate::api::Controller;
|
use crate::api::Controller;
|
||||||
use crate::cursor::controller::CursorController;
|
use crate::cursor::controller::CursorController;
|
||||||
|
use napi::threadsafe_function::ErrorStrategy::Fatal;
|
||||||
|
use napi::threadsafe_function::{
|
||||||
|
ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode,
|
||||||
|
};
|
||||||
|
use napi_derive::napi;
|
||||||
|
|
||||||
#[napi(object, js_name = "Cursor")]
|
#[napi(object, js_name = "Cursor")]
|
||||||
pub struct JsCursor {
|
pub struct JsCursor {
|
||||||
|
@ -18,8 +20,8 @@ pub struct JsCursor {
|
||||||
impl From<JsCursor> for crate::api::Cursor {
|
impl From<JsCursor> for crate::api::Cursor {
|
||||||
fn from(value: JsCursor) -> Self {
|
fn from(value: JsCursor) -> Self {
|
||||||
crate::api::Cursor {
|
crate::api::Cursor {
|
||||||
start : (value.start_row, value.start_col),
|
start: (value.start_row, value.start_col),
|
||||||
end: (value.end_row, value.end_col),
|
end: (value.end_row, value.end_col),
|
||||||
buffer: value.buffer,
|
buffer: value.buffer,
|
||||||
user: value.user,
|
user: value.user,
|
||||||
}
|
}
|
||||||
|
@ -29,32 +31,33 @@ impl From<JsCursor> for crate::api::Cursor {
|
||||||
impl From<crate::api::Cursor> for JsCursor {
|
impl From<crate::api::Cursor> for JsCursor {
|
||||||
fn from(value: crate::api::Cursor) -> Self {
|
fn from(value: crate::api::Cursor) -> Self {
|
||||||
JsCursor {
|
JsCursor {
|
||||||
start_row : value.start.0,
|
start_row: value.start.0,
|
||||||
start_col : value.start.1,
|
start_col: value.start.1,
|
||||||
end_row : value.end.0,
|
end_row: value.end.0,
|
||||||
end_col: value.end.1,
|
end_col: value.end.1,
|
||||||
buffer: value.buffer,
|
buffer: value.buffer,
|
||||||
user: value.user.map(|x| x.to_string())
|
user: value.user.map(|x| x.to_string()),
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[napi]
|
#[napi]
|
||||||
impl CursorController {
|
impl CursorController {
|
||||||
|
|
||||||
/// Register a callback to be called on receive.
|
/// Register a callback to be called on receive.
|
||||||
/// There can only be one callback registered at any given time.
|
/// There can only be one callback registered at any given time.
|
||||||
#[napi(js_name = "callback", ts_args_type = "fun: (event: CursorController) => void")]
|
#[napi(
|
||||||
pub fn js_callback(&self, fun: napi::JsFunction) -> napi::Result<()>{
|
js_name = "callback",
|
||||||
let tsfn : ThreadsafeFunction<crate::cursor::controller::CursorController, Fatal> =
|
ts_args_type = "fun: (event: CursorController) => void"
|
||||||
fun.create_threadsafe_function(0,
|
)]
|
||||||
|ctx : ThreadSafeCallContext<crate::cursor::controller::CursorController>| {
|
pub fn js_callback(&self, fun: napi::JsFunction) -> napi::Result<()> {
|
||||||
Ok(vec![ctx.value])
|
let tsfn: ThreadsafeFunction<crate::cursor::controller::CursorController, Fatal> = fun
|
||||||
}
|
.create_threadsafe_function(
|
||||||
)?;
|
0,
|
||||||
self.callback(move |controller : CursorController| {
|
|ctx: ThreadSafeCallContext<crate::cursor::controller::CursorController>| {
|
||||||
|
Ok(vec![ctx.value])
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
self.callback(move |controller: CursorController| {
|
||||||
tsfn.call(controller.clone(), ThreadsafeFunctionCallMode::Blocking);
|
tsfn.call(controller.clone(), ThreadsafeFunctionCallMode::Blocking);
|
||||||
//check this with tracing also we could use Ok(event) to get the error
|
//check this with tracing also we could use Ok(event) to get the error
|
||||||
// If it blocks the main thread too many time we have to change this
|
// If it blocks the main thread too many time we have to change this
|
||||||
|
@ -75,17 +78,15 @@ impl CursorController {
|
||||||
Ok(self.send(crate::api::Cursor::from(pos)).await?)
|
Ok(self.send(crate::api::Cursor::from(pos)).await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Get next cursor event if available without blocking
|
/// Get next cursor event if available without blocking
|
||||||
#[napi(js_name= "try_recv")]
|
#[napi(js_name = "try_recv")]
|
||||||
pub async fn js_try_recv(&self) -> napi::Result<Option<JsCursor>> {
|
pub async fn js_try_recv(&self) -> napi::Result<Option<JsCursor>> {
|
||||||
Ok(self.try_recv().await?
|
Ok(self.try_recv().await?.map(JsCursor::from))
|
||||||
.map(JsCursor::from))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Block until next
|
/// Block until next
|
||||||
#[napi(js_name= "recv")]
|
#[napi(js_name = "recv")]
|
||||||
pub async fn js_recv(&self) -> napi::Result<JsCursor> {
|
pub async fn js_recv(&self) -> napi::Result<JsCursor> {
|
||||||
Ok(self.recv().await?.into())
|
Ok(self.recv().await?.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,13 @@
|
||||||
use napi_derive::napi;
|
use napi_derive::napi;
|
||||||
|
|
||||||
|
|
||||||
/// Hash function
|
/// Hash function
|
||||||
#[napi(js_name = "hash")]
|
#[napi(js_name = "hash")]
|
||||||
pub fn js_hash(data: String) -> i64 {
|
pub fn js_hash(data: String) -> i64 {
|
||||||
crate::ext::hash(data)
|
crate::ext::hash(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Get the current version of the client
|
/// Get the current version of the client
|
||||||
#[napi(js_name = "version")]
|
#[napi(js_name = "version")]
|
||||||
pub fn js_version() -> String {
|
pub fn js_version() -> String {
|
||||||
crate::version()
|
crate::version()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
pub mod client;
|
|
||||||
pub mod workspace;
|
|
||||||
pub mod cursor;
|
|
||||||
pub mod buffer;
|
pub mod buffer;
|
||||||
|
pub mod client;
|
||||||
|
pub mod cursor;
|
||||||
pub mod ext;
|
pub mod ext;
|
||||||
|
pub mod workspace;
|
||||||
|
|
||||||
impl From<crate::errors::ConnectionError> for napi::Error {
|
impl From<crate::errors::ConnectionError> for napi::Error {
|
||||||
fn from(value: crate::errors::ConnectionError) -> Self {
|
fn from(value: crate::errors::ConnectionError) -> Self {
|
||||||
|
@ -33,7 +32,11 @@ impl JsLogger {
|
||||||
#[napi(constructor)]
|
#[napi(constructor)]
|
||||||
pub fn new(debug: Option<bool>) -> JsLogger {
|
pub fn new(debug: Option<bool>) -> JsLogger {
|
||||||
let (tx, rx) = tokio::sync::mpsc::channel(256);
|
let (tx, rx) = tokio::sync::mpsc::channel(256);
|
||||||
let level = if debug.unwrap_or(false) { tracing::Level::DEBUG } else {tracing::Level::INFO }; //TODO: study this tracing subscriber and customize it
|
let level = if debug.unwrap_or(false) {
|
||||||
|
tracing::Level::DEBUG
|
||||||
|
} else {
|
||||||
|
tracing::Level::INFO
|
||||||
|
}; //TODO: study this tracing subscriber and customize it
|
||||||
let format = tracing_subscriber::fmt::format()
|
let format = tracing_subscriber::fmt::format()
|
||||||
.with_level(true)
|
.with_level(true)
|
||||||
.with_target(true)
|
.with_target(true)
|
||||||
|
@ -55,11 +58,7 @@ impl JsLogger {
|
||||||
|
|
||||||
#[napi]
|
#[napi]
|
||||||
pub async fn message(&self) -> Option<String> {
|
pub async fn message(&self) -> Option<String> {
|
||||||
self.0
|
self.0.lock().await.recv().await
|
||||||
.lock()
|
|
||||||
.await
|
|
||||||
.recv()
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,5 +72,7 @@ impl std::io::Write for JsLoggerProducer {
|
||||||
Ok(buf.len())
|
Ok(buf.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flush(&mut self) -> std::io::Result<()> { Ok(()) }
|
fn flush(&mut self) -> std::io::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use napi_derive::napi;
|
|
||||||
use crate::Workspace;
|
|
||||||
use crate::buffer::controller::BufferController;
|
use crate::buffer::controller::BufferController;
|
||||||
use crate::cursor::controller::CursorController;
|
use crate::cursor::controller::CursorController;
|
||||||
use crate::ffi::js::client::JsUser;
|
use crate::ffi::js::client::JsUser;
|
||||||
|
use crate::Workspace;
|
||||||
|
use napi_derive::napi;
|
||||||
|
|
||||||
#[napi(object, js_name = "Event")]
|
#[napi(object, js_name = "Event")]
|
||||||
pub struct JsEvent {
|
pub struct JsEvent {
|
||||||
|
@ -13,9 +13,18 @@ pub struct JsEvent {
|
||||||
impl From<crate::api::Event> for JsEvent {
|
impl From<crate::api::Event> for JsEvent {
|
||||||
fn from(value: crate::api::Event) -> Self {
|
fn from(value: crate::api::Event) -> Self {
|
||||||
match value {
|
match value {
|
||||||
crate::api::Event::FileTreeUpdated(value) => Self { r#type: "filetree".into(), value },
|
crate::api::Event::FileTreeUpdated(value) => Self {
|
||||||
crate::api::Event::UserJoin(value) => Self { r#type: "join".into(), value },
|
r#type: "filetree".into(),
|
||||||
crate::api::Event::UserLeave(value) => Self { r#type: "leave".into(), value },
|
value,
|
||||||
|
},
|
||||||
|
crate::api::Event::UserJoin(value) => Self {
|
||||||
|
r#type: "join".into(),
|
||||||
|
value,
|
||||||
|
},
|
||||||
|
crate::api::Event::UserLeave(value) => Self {
|
||||||
|
r#type: "leave".into(),
|
||||||
|
value,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,7 +36,7 @@ impl Workspace {
|
||||||
pub fn js_id(&self) -> String {
|
pub fn js_id(&self) -> String {
|
||||||
self.id()
|
self.id()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// List all available buffers in this workspace
|
/// List all available buffers in this workspace
|
||||||
#[napi(js_name = "filetree")]
|
#[napi(js_name = "filetree")]
|
||||||
pub fn js_filetree(&self, filter: Option<&str>, strict: bool) -> Vec<String> {
|
pub fn js_filetree(&self, filter: Option<&str>, strict: bool) -> Vec<String> {
|
||||||
|
@ -64,13 +73,12 @@ impl Workspace {
|
||||||
Ok(self.create(&path).await?)
|
Ok(self.create(&path).await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Attach to a workspace buffer, starting a BufferController
|
/// Attach to a workspace buffer, starting a BufferController
|
||||||
#[napi(js_name = "attach")]
|
#[napi(js_name = "attach")]
|
||||||
pub async fn js_attach(&self, path: String) -> napi::Result<BufferController> {
|
pub async fn js_attach(&self, path: String) -> napi::Result<BufferController> {
|
||||||
Ok(self.attach(&path).await?)
|
Ok(self.attach(&path).await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Delete a buffer from workspace
|
/// Delete a buffer from workspace
|
||||||
#[napi(js_name = "delete")]
|
#[napi(js_name = "delete")]
|
||||||
pub async fn js_delete(&self, path: String) -> napi::Result<()> {
|
pub async fn js_delete(&self, path: String) -> napi::Result<()> {
|
||||||
|
@ -105,12 +113,11 @@ impl Workspace {
|
||||||
/// List users attached to a specific buffer
|
/// List users attached to a specific buffer
|
||||||
#[napi(js_name = "list_buffer_users")]
|
#[napi(js_name = "list_buffer_users")]
|
||||||
pub async fn js_list_buffer_users(&self, path: String) -> napi::Result<Vec<JsUser>> {
|
pub async fn js_list_buffer_users(&self, path: String) -> napi::Result<Vec<JsUser>> {
|
||||||
Ok(
|
Ok(self
|
||||||
self
|
.list_buffer_users(&path)
|
||||||
.list_buffer_users(&path)
|
.await?
|
||||||
.await?
|
.into_iter()
|
||||||
.into_iter()
|
.map(JsUser::from)
|
||||||
.map(JsUser::from)
|
.collect())
|
||||||
.collect())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +1,41 @@
|
||||||
use mlua_codemp_patch as mlua;
|
|
||||||
use mlua::prelude::*;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use mlua::prelude::*;
|
||||||
|
use mlua_codemp_patch as mlua;
|
||||||
|
|
||||||
use super::ext::a_sync::a_sync;
|
use super::ext::a_sync::a_sync;
|
||||||
use super::ext::from_lua_serde;
|
use super::ext::from_lua_serde;
|
||||||
|
|
||||||
|
|
||||||
impl LuaUserData for CodempBufferController {
|
impl LuaUserData for CodempBufferController {
|
||||||
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||||
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| Ok(format!("{:?}", this)));
|
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| {
|
||||||
|
Ok(format!("{:?}", this))
|
||||||
|
});
|
||||||
|
|
||||||
methods.add_method("send", |_, this, (change,): (CodempTextChange,)|
|
methods.add_method(
|
||||||
a_sync! { this => this.send(change).await? }
|
"send",
|
||||||
|
|_, this, (change,): (CodempTextChange,)| a_sync! { this => this.send(change).await? },
|
||||||
);
|
);
|
||||||
|
|
||||||
methods.add_method("try_recv", |_, this, ()| a_sync! { this => this.try_recv().await? });
|
methods.add_method(
|
||||||
|
"try_recv",
|
||||||
|
|_, this, ()| a_sync! { this => this.try_recv().await? },
|
||||||
|
);
|
||||||
methods.add_method("recv", |_, this, ()| a_sync! { this => this.recv().await? });
|
methods.add_method("recv", |_, this, ()| a_sync! { this => this.recv().await? });
|
||||||
methods.add_method("poll", |_, this, ()| a_sync! { this => this.poll().await? });
|
methods.add_method("poll", |_, this, ()| a_sync! { this => this.poll().await? });
|
||||||
|
|
||||||
methods.add_method("content", |_, this, ()| a_sync! { this => this.content().await? });
|
methods.add_method(
|
||||||
|
"content",
|
||||||
|
|_, this, ()| a_sync! { this => this.content().await? },
|
||||||
|
);
|
||||||
|
|
||||||
methods.add_method("clear_callback", |_, this, ()| { this.clear_callback(); Ok(()) });
|
methods.add_method("clear_callback", |_, this, ()| {
|
||||||
methods.add_method("callback", |_, this, (cb,):(LuaFunction,)| {
|
this.clear_callback();
|
||||||
this.callback(move |controller: CodempBufferController| super::ext::callback().invoke(cb.clone(), controller));
|
Ok(())
|
||||||
|
});
|
||||||
|
methods.add_method("callback", |_, this, (cb,): (LuaFunction,)| {
|
||||||
|
this.callback(move |controller: CodempBufferController| {
|
||||||
|
super::ext::callback().invoke(cb.clone(), controller)
|
||||||
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -32,15 +45,17 @@ from_lua_serde! { CodempTextChange }
|
||||||
impl LuaUserData for CodempTextChange {
|
impl LuaUserData for CodempTextChange {
|
||||||
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
||||||
fields.add_field_method_get("content", |_, this| Ok(this.content.clone()));
|
fields.add_field_method_get("content", |_, this| Ok(this.content.clone()));
|
||||||
fields.add_field_method_get("start", |_, this| Ok(this.start));
|
fields.add_field_method_get("start", |_, this| Ok(this.start));
|
||||||
fields.add_field_method_get("end", |_, this| Ok(this.end));
|
fields.add_field_method_get("end", |_, this| Ok(this.end));
|
||||||
fields.add_field_method_get("hash", |_, this| Ok(this.hash));
|
fields.add_field_method_get("hash", |_, this| Ok(this.hash));
|
||||||
// add a 'finish' accessor too because in Lua 'end' is reserved
|
// add a 'finish' accessor too because in Lua 'end' is reserved
|
||||||
fields.add_field_method_get("finish", |_, this| Ok(this.end));
|
fields.add_field_method_get("finish", |_, this| Ok(this.end));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||||
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| Ok(format!("{:?}", this)));
|
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| {
|
||||||
methods.add_method("apply", |_, this, (txt,):(String,)| Ok(this.apply(&txt)));
|
Ok(format!("{:?}", this))
|
||||||
|
});
|
||||||
|
methods.add_method("apply", |_, this, (txt,): (String,)| Ok(this.apply(&txt)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use mlua_codemp_patch as mlua;
|
|
||||||
use mlua::prelude::*;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use mlua::prelude::*;
|
||||||
|
use mlua_codemp_patch as mlua;
|
||||||
|
|
||||||
use super::ext::a_sync::a_sync;
|
use super::ext::a_sync::a_sync;
|
||||||
use super::ext::from_lua_serde;
|
use super::ext::from_lua_serde;
|
||||||
|
@ -13,22 +13,28 @@ impl LuaUserData for CodempClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||||
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| Ok(format!("{:?}", this)));
|
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| {
|
||||||
|
Ok(format!("{:?}", this))
|
||||||
|
});
|
||||||
|
|
||||||
methods.add_method("refresh", |_, this, ()|
|
methods.add_method(
|
||||||
a_sync! { this => this.refresh().await? }
|
"refresh",
|
||||||
|
|_, this, ()| a_sync! { this => this.refresh().await? },
|
||||||
);
|
);
|
||||||
|
|
||||||
methods.add_method("join_workspace", |_, this, (ws,):(String,)|
|
methods.add_method(
|
||||||
a_sync! { this => this.join_workspace(ws).await? }
|
"join_workspace",
|
||||||
|
|_, this, (ws,): (String,)| a_sync! { this => this.join_workspace(ws).await? },
|
||||||
);
|
);
|
||||||
|
|
||||||
methods.add_method("create_workspace", |_, this, (ws,):(String,)|
|
methods.add_method(
|
||||||
a_sync! { this => this.create_workspace(ws).await? }
|
"create_workspace",
|
||||||
|
|_, this, (ws,): (String,)| a_sync! { this => this.create_workspace(ws).await? },
|
||||||
);
|
);
|
||||||
|
|
||||||
methods.add_method("delete_workspace", |_, this, (ws,):(String,)|
|
methods.add_method(
|
||||||
a_sync! { this => this.delete_workspace(ws).await? }
|
"delete_workspace",
|
||||||
|
|_, this, (ws,): (String,)| a_sync! { this => this.delete_workspace(ws).await? },
|
||||||
);
|
);
|
||||||
|
|
||||||
methods.add_method("invite_to_workspace", |_, this, (ws,user):(String,String)|
|
methods.add_method("invite_to_workspace", |_, this, (ws,user):(String,String)|
|
||||||
|
@ -39,11 +45,13 @@ impl LuaUserData for CodempClient {
|
||||||
a_sync! { this => this.list_workspaces(owned.unwrap_or(true), invited.unwrap_or(true)).await? }
|
a_sync! { this => this.list_workspaces(owned.unwrap_or(true), invited.unwrap_or(true)).await? }
|
||||||
);
|
);
|
||||||
|
|
||||||
methods.add_method("leave_workspace", |_, this, (ws,):(String,)|
|
methods.add_method("leave_workspace", |_, this, (ws,): (String,)| {
|
||||||
Ok(this.leave_workspace(&ws))
|
Ok(this.leave_workspace(&ws))
|
||||||
);
|
});
|
||||||
|
|
||||||
methods.add_method("get_workspace", |_, this, (ws,):(String,)| Ok(this.get_workspace(&ws)));
|
methods.add_method("get_workspace", |_, this, (ws,): (String,)| {
|
||||||
|
Ok(this.get_workspace(&ws))
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use mlua_codemp_patch as mlua;
|
|
||||||
use mlua::prelude::*;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use mlua::prelude::*;
|
||||||
|
use mlua_codemp_patch as mlua;
|
||||||
|
|
||||||
use super::ext::a_sync::a_sync;
|
use super::ext::a_sync::a_sync;
|
||||||
use super::ext::from_lua_serde;
|
use super::ext::from_lua_serde;
|
||||||
|
@ -8,20 +8,29 @@ use super::ext::lua_tuple;
|
||||||
|
|
||||||
impl LuaUserData for CodempCursorController {
|
impl LuaUserData for CodempCursorController {
|
||||||
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||||
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| Ok(format!("{:?}", this)));
|
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| {
|
||||||
|
Ok(format!("{:?}", this))
|
||||||
|
});
|
||||||
|
|
||||||
methods.add_method("send", |_, this, (cursor,):(CodempCursor,)|
|
methods.add_method(
|
||||||
a_sync! { this => this.send(cursor).await? }
|
"send",
|
||||||
|
|_, this, (cursor,): (CodempCursor,)| a_sync! { this => this.send(cursor).await? },
|
||||||
);
|
);
|
||||||
methods.add_method("try_recv", |_, this, ()|
|
methods.add_method(
|
||||||
a_sync! { this => this.try_recv().await? }
|
"try_recv",
|
||||||
|
|_, this, ()| a_sync! { this => this.try_recv().await? },
|
||||||
);
|
);
|
||||||
methods.add_method("recv", |_, this, ()| a_sync! { this => this.recv().await? });
|
methods.add_method("recv", |_, this, ()| a_sync! { this => this.recv().await? });
|
||||||
methods.add_method("poll", |_, this, ()| a_sync! { this => this.poll().await? });
|
methods.add_method("poll", |_, this, ()| a_sync! { this => this.poll().await? });
|
||||||
|
|
||||||
methods.add_method("clear_callback", |_, this, ()| { this.clear_callback(); Ok(()) });
|
methods.add_method("clear_callback", |_, this, ()| {
|
||||||
methods.add_method("callback", |_, this, (cb,):(LuaFunction,)| {
|
this.clear_callback();
|
||||||
this.callback(move |controller: CodempCursorController| super::ext::callback().invoke(cb.clone(), controller));
|
Ok(())
|
||||||
|
});
|
||||||
|
methods.add_method("callback", |_, this, (cb,): (LuaFunction,)| {
|
||||||
|
this.callback(move |controller: CodempCursorController| {
|
||||||
|
super::ext::callback().invoke(cb.clone(), controller)
|
||||||
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -30,13 +39,15 @@ impl LuaUserData for CodempCursorController {
|
||||||
from_lua_serde! { CodempCursor }
|
from_lua_serde! { CodempCursor }
|
||||||
impl LuaUserData for CodempCursor {
|
impl LuaUserData for CodempCursor {
|
||||||
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||||
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| Ok(format!("{:?}", this)));
|
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| {
|
||||||
|
Ok(format!("{:?}", this))
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
||||||
fields.add_field_method_get("user", |_, this| Ok(this.user.clone()));
|
fields.add_field_method_get("user", |_, this| Ok(this.user.clone()));
|
||||||
fields.add_field_method_get("buffer", |_, this| Ok(this.buffer.clone()));
|
fields.add_field_method_get("buffer", |_, this| Ok(this.buffer.clone()));
|
||||||
fields.add_field_method_get("start", |lua, this| lua_tuple(lua, this.start));
|
fields.add_field_method_get("start", |lua, this| lua_tuple(lua, this.start));
|
||||||
fields.add_field_method_get("end", |lua, this| lua_tuple(lua, this.end));
|
fields.add_field_method_get("end", |lua, this| lua_tuple(lua, this.end));
|
||||||
// add a 'finish' accessor too because in Lua 'end' is reserved
|
// add a 'finish' accessor too because in Lua 'end' is reserved
|
||||||
fields.add_field_method_get("finish", |lua, this| lua_tuple(lua, this.end));
|
fields.add_field_method_get("finish", |lua, this| lua_tuple(lua, this.end));
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
use mlua_codemp_patch as mlua;
|
|
||||||
use mlua::prelude::*;
|
use mlua::prelude::*;
|
||||||
|
use mlua_codemp_patch as mlua;
|
||||||
|
|
||||||
pub(crate) fn tokio() -> &'static tokio::runtime::Runtime {
|
pub(crate) fn tokio() -> &'static tokio::runtime::Runtime {
|
||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
static RT: OnceLock<tokio::runtime::Runtime> = OnceLock::new();
|
static RT: OnceLock<tokio::runtime::Runtime> = OnceLock::new();
|
||||||
RT.get_or_init(||
|
RT.get_or_init(|| {
|
||||||
tokio::runtime::Builder::new_current_thread()
|
tokio::runtime::Builder::new_current_thread()
|
||||||
.enable_all()
|
.enable_all()
|
||||||
.build()
|
.build()
|
||||||
.expect("could not create tokio runtime")
|
.expect("could not create tokio runtime")
|
||||||
)
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! a_sync {
|
macro_rules! a_sync {
|
||||||
|
@ -32,45 +32,46 @@ macro_rules! a_sync {
|
||||||
|
|
||||||
pub(crate) use a_sync;
|
pub(crate) use a_sync;
|
||||||
|
|
||||||
pub(crate) struct Promise(pub(crate) Option<tokio::task::JoinHandle<LuaResult<super::callback::CallbackArg>>>);
|
pub(crate) struct Promise(
|
||||||
|
pub(crate) Option<tokio::task::JoinHandle<LuaResult<super::callback::CallbackArg>>>,
|
||||||
|
);
|
||||||
|
|
||||||
impl LuaUserData for Promise {
|
impl LuaUserData for Promise {
|
||||||
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
||||||
fields.add_field_method_get("ready", |_, this|
|
fields.add_field_method_get("ready", |_, this| {
|
||||||
Ok(this.0.as_ref().map_or(true, |x| x.is_finished()))
|
Ok(this.0.as_ref().map_or(true, |x| x.is_finished()))
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||||
// TODO: await MUST NOT be used in callbacks!!
|
// TODO: await MUST NOT be used in callbacks!!
|
||||||
methods.add_method_mut("await", |_, this, ()| match this.0.take() {
|
methods.add_method_mut("await", |_, this, ()| match this.0.take() {
|
||||||
None => Err(LuaError::runtime("Promise already awaited")),
|
None => Err(LuaError::runtime("Promise already awaited")),
|
||||||
Some(x) => {
|
Some(x) => tokio().block_on(x).map_err(LuaError::runtime)?,
|
||||||
tokio()
|
|
||||||
.block_on(x)
|
|
||||||
.map_err(LuaError::runtime)?
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
methods.add_method_mut("and_then", |_, this, (cb,):(LuaFunction,)| match this.0.take() {
|
methods.add_method_mut("and_then", |_, this, (cb,): (LuaFunction,)| {
|
||||||
None => Err(LuaError::runtime("Promise already awaited")),
|
match this.0.take() {
|
||||||
Some(x) => {
|
None => Err(LuaError::runtime("Promise already awaited")),
|
||||||
tokio()
|
Some(x) => {
|
||||||
.spawn(async move {
|
tokio().spawn(async move {
|
||||||
match x.await {
|
match x.await {
|
||||||
Err(e) => tracing::error!("could not join promise to run callback: {e}"),
|
Err(e) => {
|
||||||
|
tracing::error!("could not join promise to run callback: {e}")
|
||||||
|
}
|
||||||
Ok(res) => match res {
|
Ok(res) => match res {
|
||||||
Err(e) => super::callback().failure(e),
|
Err(e) => super::callback().failure(e),
|
||||||
Ok(val) => super::callback().invoke(cb, val),
|
Ok(val) => super::callback().invoke(cb, val),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn setup_driver(_: &Lua, (block,):(Option<bool>,)) -> LuaResult<Option<Driver>> {
|
pub(crate) fn setup_driver(_: &Lua, (block,): (Option<bool>,)) -> LuaResult<Option<Driver>> {
|
||||||
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
|
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
|
||||||
let future = async move {
|
let future = async move {
|
||||||
tracing::info!(" :: driving runtime...");
|
tracing::info!(" :: driving runtime...");
|
||||||
|
@ -89,25 +90,26 @@ pub(crate) fn setup_driver(_: &Lua, (block,):(Option<bool>,)) -> LuaResult<Optio
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct Driver(pub(crate) tokio::sync::mpsc::UnboundedSender<()>, Option<std::thread::JoinHandle<()>>);
|
pub(crate) struct Driver(
|
||||||
|
pub(crate) tokio::sync::mpsc::UnboundedSender<()>,
|
||||||
|
Option<std::thread::JoinHandle<()>>,
|
||||||
|
);
|
||||||
impl LuaUserData for Driver {
|
impl LuaUserData for Driver {
|
||||||
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||||
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| Ok(format!("{:?}", this)));
|
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| {
|
||||||
methods.add_method_mut("stop", |_, this, ()| {
|
Ok(format!("{:?}", this))
|
||||||
match this.1.take() {
|
});
|
||||||
None => Ok(false),
|
methods.add_method_mut("stop", |_, this, ()| match this.1.take() {
|
||||||
Some(handle) => {
|
None => Ok(false),
|
||||||
if this.0.send(()).is_err() {
|
Some(handle) => {
|
||||||
tracing::warn!("found runtime already stopped while attempting to stop it");
|
if this.0.send(()).is_err() {
|
||||||
}
|
tracing::warn!("found runtime already stopped while attempting to stop it");
|
||||||
match handle.join() {
|
}
|
||||||
Err(e) => Err(LuaError::runtime(format!("runtime thread panicked: {e:?}"))),
|
match handle.join() {
|
||||||
Ok(()) => Ok(true),
|
Err(e) => Err(LuaError::runtime(format!("runtime thread panicked: {e:?}"))),
|
||||||
}
|
Ok(()) => Ok(true),
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use mlua_codemp_patch as mlua;
|
|
||||||
use mlua::prelude::*;
|
|
||||||
use crate::prelude::*;
|
|
||||||
use crate::ext::IgnorableError;
|
use crate::ext::IgnorableError;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use mlua::prelude::*;
|
||||||
|
use mlua_codemp_patch as mlua;
|
||||||
|
|
||||||
pub(crate) fn callback() -> &'static CallbackChannel<LuaCallback> {
|
pub(crate) fn callback() -> &'static CallbackChannel<LuaCallback> {
|
||||||
static CHANNEL: std::sync::OnceLock<CallbackChannel<LuaCallback>> = std::sync::OnceLock::new();
|
static CHANNEL: std::sync::OnceLock<CallbackChannel<LuaCallback>> = std::sync::OnceLock::new();
|
||||||
|
@ -10,7 +10,7 @@ pub(crate) fn callback() -> &'static CallbackChannel<LuaCallback> {
|
||||||
|
|
||||||
pub(crate) struct CallbackChannel<T> {
|
pub(crate) struct CallbackChannel<T> {
|
||||||
tx: std::sync::Arc<tokio::sync::mpsc::UnboundedSender<T>>,
|
tx: std::sync::Arc<tokio::sync::mpsc::UnboundedSender<T>>,
|
||||||
rx: std::sync::Mutex<tokio::sync::mpsc::UnboundedReceiver<T>>
|
rx: std::sync::Mutex<tokio::sync::mpsc::UnboundedReceiver<T>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for CallbackChannel<LuaCallback> {
|
impl Default for CallbackChannel<LuaCallback> {
|
||||||
|
@ -26,12 +26,16 @@ impl Default for CallbackChannel<LuaCallback> {
|
||||||
|
|
||||||
impl CallbackChannel<LuaCallback> {
|
impl CallbackChannel<LuaCallback> {
|
||||||
pub(crate) fn invoke(&self, cb: LuaFunction, arg: impl Into<CallbackArg>) {
|
pub(crate) fn invoke(&self, cb: LuaFunction, arg: impl Into<CallbackArg>) {
|
||||||
self.tx.send(LuaCallback::Invoke(cb, arg.into()))
|
self.tx
|
||||||
|
.send(LuaCallback::Invoke(cb, arg.into()))
|
||||||
.unwrap_or_warn("error scheduling callback")
|
.unwrap_or_warn("error scheduling callback")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn failure(&self, err: impl std::error::Error) {
|
pub(crate) fn failure(&self, err: impl std::error::Error) {
|
||||||
self.tx.send(LuaCallback::Fail(format!("promise failed with error: {err:?}")))
|
self.tx
|
||||||
|
.send(LuaCallback::Fail(format!(
|
||||||
|
"promise failed with error: {err:?}"
|
||||||
|
)))
|
||||||
.unwrap_or_warn("error scheduling callback failure")
|
.unwrap_or_warn("error scheduling callback failure")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,12 +44,12 @@ impl CallbackChannel<LuaCallback> {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::debug!("backing off from callback mutex: {e}");
|
tracing::debug!("backing off from callback mutex: {e}");
|
||||||
None
|
None
|
||||||
},
|
}
|
||||||
Ok(mut lock) => match lock.try_recv() {
|
Ok(mut lock) => match lock.try_recv() {
|
||||||
Err(tokio::sync::mpsc::error::TryRecvError::Disconnected) => {
|
Err(tokio::sync::mpsc::error::TryRecvError::Disconnected) => {
|
||||||
tracing::error!("callback channel closed");
|
tracing::error!("callback channel closed");
|
||||||
None
|
None
|
||||||
},
|
}
|
||||||
Err(tokio::sync::mpsc::error::TryRecvError::Empty) => None,
|
Err(tokio::sync::mpsc::error::TryRecvError::Empty) => None,
|
||||||
Ok(cb) => Some(cb),
|
Ok(cb) => Some(cb),
|
||||||
},
|
},
|
||||||
|
@ -95,15 +99,63 @@ impl IntoLua for CallbackArg {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<()> for CallbackArg { fn from(_: ()) -> Self { CallbackArg::Nil } }
|
impl From<()> for CallbackArg {
|
||||||
impl From<String> for CallbackArg { fn from(value: String) -> Self { CallbackArg::Str(value) } }
|
fn from(_: ()) -> Self {
|
||||||
impl From<CodempClient> for CallbackArg { fn from(value: CodempClient) -> Self { CallbackArg::Client(value) } }
|
CallbackArg::Nil
|
||||||
impl From<CodempCursorController> for CallbackArg { fn from(value: CodempCursorController) -> Self { CallbackArg::CursorController(value) } }
|
}
|
||||||
impl From<CodempBufferController> for CallbackArg { fn from(value: CodempBufferController) -> Self { CallbackArg::BufferController(value) } }
|
}
|
||||||
impl From<CodempWorkspace> for CallbackArg { fn from(value: CodempWorkspace) -> Self { CallbackArg::Workspace(value) } }
|
impl From<String> for CallbackArg {
|
||||||
impl From<Vec<String>> for CallbackArg { fn from(value: Vec<String>) -> Self { CallbackArg::VecStr(value) } }
|
fn from(value: String) -> Self {
|
||||||
impl From<CodempEvent> for CallbackArg { fn from(value: CodempEvent) -> Self { CallbackArg::Event(value) } }
|
CallbackArg::Str(value)
|
||||||
impl From<CodempCursor> for CallbackArg { fn from(value: CodempCursor) -> Self { CallbackArg::Cursor(value) } }
|
}
|
||||||
impl From<Option<CodempCursor>> for CallbackArg { fn from(value: Option<CodempCursor>) -> Self { CallbackArg::MaybeCursor(value) } }
|
}
|
||||||
impl From<CodempTextChange> for CallbackArg { fn from(value: CodempTextChange) -> Self { CallbackArg::TextChange(value) } }
|
impl From<CodempClient> for CallbackArg {
|
||||||
impl From<Option<CodempTextChange>> for CallbackArg { fn from(value: Option<CodempTextChange>) -> Self { CallbackArg::MaybeTextChange(value) } }
|
fn from(value: CodempClient) -> Self {
|
||||||
|
CallbackArg::Client(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<CodempCursorController> for CallbackArg {
|
||||||
|
fn from(value: CodempCursorController) -> Self {
|
||||||
|
CallbackArg::CursorController(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<CodempBufferController> for CallbackArg {
|
||||||
|
fn from(value: CodempBufferController) -> Self {
|
||||||
|
CallbackArg::BufferController(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<CodempWorkspace> for CallbackArg {
|
||||||
|
fn from(value: CodempWorkspace) -> Self {
|
||||||
|
CallbackArg::Workspace(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<Vec<String>> for CallbackArg {
|
||||||
|
fn from(value: Vec<String>) -> Self {
|
||||||
|
CallbackArg::VecStr(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<CodempEvent> for CallbackArg {
|
||||||
|
fn from(value: CodempEvent) -> Self {
|
||||||
|
CallbackArg::Event(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<CodempCursor> for CallbackArg {
|
||||||
|
fn from(value: CodempCursor) -> Self {
|
||||||
|
CallbackArg::Cursor(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<Option<CodempCursor>> for CallbackArg {
|
||||||
|
fn from(value: Option<CodempCursor>) -> Self {
|
||||||
|
CallbackArg::MaybeCursor(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<CodempTextChange> for CallbackArg {
|
||||||
|
fn from(value: CodempTextChange) -> Self {
|
||||||
|
CallbackArg::TextChange(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<Option<CodempTextChange>> for CallbackArg {
|
||||||
|
fn from(value: Option<CodempTextChange>) -> Self {
|
||||||
|
CallbackArg::MaybeTextChange(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::{io::Write, sync::Mutex};
|
use std::{io::Write, sync::Mutex};
|
||||||
|
|
||||||
use mlua_codemp_patch as mlua;
|
|
||||||
use mlua::prelude::*;
|
use mlua::prelude::*;
|
||||||
|
use mlua_codemp_patch as mlua;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -12,12 +12,21 @@ impl Write for LuaLoggerProducer {
|
||||||
Ok(buf.len())
|
Ok(buf.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flush(&mut self) -> std::io::Result<()> { Ok(()) }
|
fn flush(&mut self) -> std::io::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO can we make this less verbose?
|
// TODO can we make this less verbose?
|
||||||
pub(crate) fn setup_tracing(_: &Lua, (printer, debug): (LuaValue, Option<bool>)) -> LuaResult<bool> {
|
pub(crate) fn setup_tracing(
|
||||||
let level = if debug.unwrap_or_default() { tracing::Level::DEBUG } else {tracing::Level::INFO };
|
_: &Lua,
|
||||||
|
(printer, debug): (LuaValue, Option<bool>),
|
||||||
|
) -> LuaResult<bool> {
|
||||||
|
let level = if debug.unwrap_or_default() {
|
||||||
|
tracing::Level::DEBUG
|
||||||
|
} else {
|
||||||
|
tracing::Level::INFO
|
||||||
|
};
|
||||||
let format = tracing_subscriber::fmt::format()
|
let format = tracing_subscriber::fmt::format()
|
||||||
.with_level(true)
|
.with_level(true)
|
||||||
.with_target(true)
|
.with_target(true)
|
||||||
|
@ -36,16 +45,15 @@ pub(crate) fn setup_tracing(_: &Lua, (printer, debug): (LuaValue, Option<bool>))
|
||||||
| LuaValue::Thread(_)
|
| LuaValue::Thread(_)
|
||||||
| LuaValue::UserData(_)
|
| LuaValue::UserData(_)
|
||||||
| LuaValue::Error(_) => return Err(LuaError::BindError), // TODO full BadArgument type??
|
| LuaValue::Error(_) => return Err(LuaError::BindError), // TODO full BadArgument type??
|
||||||
LuaValue::Nil => {
|
LuaValue::Nil => tracing_subscriber::fmt()
|
||||||
tracing_subscriber::fmt()
|
.event_format(format)
|
||||||
.event_format(format)
|
.with_max_level(level)
|
||||||
.with_max_level(level)
|
.with_writer(std::sync::Mutex::new(std::io::stderr()))
|
||||||
.with_writer(std::sync::Mutex::new(std::io::stderr()))
|
.try_init()
|
||||||
.try_init()
|
.is_ok(),
|
||||||
.is_ok()
|
|
||||||
},
|
|
||||||
LuaValue::String(path) => {
|
LuaValue::String(path) => {
|
||||||
let logfile = std::fs::File::create(path.to_string_lossy()).map_err(|e| LuaError::RuntimeError(e.to_string()))?;
|
let logfile = std::fs::File::create(path.to_string_lossy())
|
||||||
|
.map_err(|e| LuaError::RuntimeError(e.to_string()))?;
|
||||||
tracing_subscriber::fmt()
|
tracing_subscriber::fmt()
|
||||||
.event_format(format)
|
.event_format(format)
|
||||||
.with_max_level(level)
|
.with_max_level(level)
|
||||||
|
@ -53,7 +61,7 @@ pub(crate) fn setup_tracing(_: &Lua, (printer, debug): (LuaValue, Option<bool>))
|
||||||
.with_ansi(false)
|
.with_ansi(false)
|
||||||
.try_init()
|
.try_init()
|
||||||
.is_ok()
|
.is_ok()
|
||||||
},
|
}
|
||||||
LuaValue::Function(cb) => {
|
LuaValue::Function(cb) => {
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||||
let res = tracing_subscriber::fmt()
|
let res = tracing_subscriber::fmt()
|
||||||
|
@ -71,7 +79,7 @@ pub(crate) fn setup_tracing(_: &Lua, (printer, debug): (LuaValue, Option<bool>))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
res
|
res
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(success)
|
Ok(success)
|
||||||
|
|
|
@ -2,8 +2,8 @@ pub mod a_sync;
|
||||||
pub mod callback;
|
pub mod callback;
|
||||||
pub mod log;
|
pub mod log;
|
||||||
|
|
||||||
use mlua_codemp_patch as mlua;
|
|
||||||
use mlua::prelude::*;
|
use mlua::prelude::*;
|
||||||
|
use mlua_codemp_patch as mlua;
|
||||||
|
|
||||||
pub(crate) use a_sync::tokio;
|
pub(crate) use a_sync::tokio;
|
||||||
pub(crate) use callback::callback;
|
pub(crate) use callback::callback;
|
||||||
|
|
|
@ -1,73 +1,99 @@
|
||||||
mod client;
|
|
||||||
mod workspace;
|
|
||||||
mod cursor;
|
|
||||||
mod buffer;
|
mod buffer;
|
||||||
|
mod client;
|
||||||
|
mod cursor;
|
||||||
mod ext;
|
mod ext;
|
||||||
|
mod workspace;
|
||||||
|
|
||||||
use mlua_codemp_patch as mlua;
|
|
||||||
use mlua::prelude::*;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use mlua::prelude::*;
|
||||||
|
use mlua_codemp_patch as mlua;
|
||||||
|
|
||||||
// define multiple entrypoints, so this library can have multiple names and still work
|
// define multiple entrypoints, so this library can have multiple names and still work
|
||||||
#[mlua::lua_module(name = "codemp")] fn entry_1(lua: &Lua) -> LuaResult<LuaTable> { entrypoint(lua) }
|
#[mlua::lua_module(name = "codemp")]
|
||||||
#[mlua::lua_module(name = "libcodemp")] fn entry_2(lua: &Lua) -> LuaResult<LuaTable> { entrypoint(lua) }
|
fn entry_1(lua: &Lua) -> LuaResult<LuaTable> {
|
||||||
#[mlua::lua_module(name = "codemp_native")] fn entry_3(lua: &Lua) -> LuaResult<LuaTable> { entrypoint(lua) }
|
entrypoint(lua)
|
||||||
#[mlua::lua_module(name = "codemp_lua")] fn entry_4(lua: &Lua) -> LuaResult<LuaTable> { entrypoint(lua) }
|
}
|
||||||
|
#[mlua::lua_module(name = "libcodemp")]
|
||||||
|
fn entry_2(lua: &Lua) -> LuaResult<LuaTable> {
|
||||||
|
entrypoint(lua)
|
||||||
|
}
|
||||||
|
#[mlua::lua_module(name = "codemp_native")]
|
||||||
|
fn entry_3(lua: &Lua) -> LuaResult<LuaTable> {
|
||||||
|
entrypoint(lua)
|
||||||
|
}
|
||||||
|
#[mlua::lua_module(name = "codemp_lua")]
|
||||||
|
fn entry_4(lua: &Lua) -> LuaResult<LuaTable> {
|
||||||
|
entrypoint(lua)
|
||||||
|
}
|
||||||
|
|
||||||
fn entrypoint(lua: &Lua) -> LuaResult<LuaTable> {
|
fn entrypoint(lua: &Lua) -> LuaResult<LuaTable> {
|
||||||
let exports = lua.create_table()?;
|
let exports = lua.create_table()?;
|
||||||
|
|
||||||
// entrypoint
|
// entrypoint
|
||||||
exports.set("connect", lua.create_function(|_, (config,):(CodempConfig,)|
|
exports.set(
|
||||||
ext::a_sync::a_sync! { => CodempClient::connect(config).await? }
|
"connect",
|
||||||
)?)?;
|
lua.create_function(
|
||||||
|
|_, (config,): (CodempConfig,)| ext::a_sync::a_sync! { => CodempClient::connect(config).await? },
|
||||||
|
)?,
|
||||||
|
)?;
|
||||||
|
|
||||||
// utils
|
// utils
|
||||||
exports.set("hash", lua.create_function(|_, (txt,):(String,)|
|
exports.set(
|
||||||
Ok(crate::ext::hash(txt))
|
"hash",
|
||||||
)?)?;
|
lua.create_function(|_, (txt,): (String,)| Ok(crate::ext::hash(txt)))?,
|
||||||
|
)?;
|
||||||
|
|
||||||
exports.set("version", lua.create_function(|_, ()|
|
exports.set(
|
||||||
Ok(crate::version())
|
"version",
|
||||||
)?)?;
|
lua.create_function(|_, ()| Ok(crate::version()))?,
|
||||||
|
)?;
|
||||||
|
|
||||||
// runtime
|
// runtime
|
||||||
exports.set("setup_driver", lua.create_function(ext::a_sync::setup_driver)?)?;
|
exports.set(
|
||||||
exports.set("poll_callback", lua.create_function(|lua, ()| {
|
"setup_driver",
|
||||||
let mut val = LuaMultiValue::new();
|
lua.create_function(ext::a_sync::setup_driver)?,
|
||||||
match ext::callback().recv() {
|
)?;
|
||||||
None => {},
|
exports.set(
|
||||||
Some(ext::callback::LuaCallback::Invoke(cb, arg)) => {
|
"poll_callback",
|
||||||
val.push_back(LuaValue::Function(cb));
|
lua.create_function(|lua, ()| {
|
||||||
val.push_back(arg.into_lua(lua)?);
|
let mut val = LuaMultiValue::new();
|
||||||
|
match ext::callback().recv() {
|
||||||
|
None => {}
|
||||||
|
Some(ext::callback::LuaCallback::Invoke(cb, arg)) => {
|
||||||
|
val.push_back(LuaValue::Function(cb));
|
||||||
|
val.push_back(arg.into_lua(lua)?);
|
||||||
|
}
|
||||||
|
Some(ext::callback::LuaCallback::Fail(msg)) => {
|
||||||
|
val.push_back(false.into_lua(lua)?);
|
||||||
|
val.push_back(msg.into_lua(lua)?);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Some(ext::callback::LuaCallback::Fail(msg)) => {
|
Ok(val)
|
||||||
val.push_back(false.into_lua(lua)?);
|
})?,
|
||||||
val.push_back(msg.into_lua(lua)?);
|
)?;
|
||||||
},
|
|
||||||
}
|
|
||||||
Ok(val)
|
|
||||||
})?)?;
|
|
||||||
|
|
||||||
// logging
|
// logging
|
||||||
exports.set("setup_tracing", lua.create_function(ext::log::setup_tracing)?)?;
|
exports.set(
|
||||||
|
"setup_tracing",
|
||||||
|
lua.create_function(ext::log::setup_tracing)?,
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(exports)
|
Ok(exports)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From::<crate::errors::ConnectionError> for LuaError {
|
impl From<crate::errors::ConnectionError> for LuaError {
|
||||||
fn from(value: crate::errors::ConnectionError) -> Self {
|
fn from(value: crate::errors::ConnectionError) -> Self {
|
||||||
LuaError::runtime(value.to_string())
|
LuaError::runtime(value.to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From::<crate::errors::RemoteError> for LuaError {
|
impl From<crate::errors::RemoteError> for LuaError {
|
||||||
fn from(value: crate::errors::RemoteError) -> Self {
|
fn from(value: crate::errors::RemoteError) -> Self {
|
||||||
LuaError::runtime(value.to_string())
|
LuaError::runtime(value.to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From::<crate::errors::ControllerError> for LuaError {
|
impl From<crate::errors::ControllerError> for LuaError {
|
||||||
fn from(value: crate::errors::ControllerError) -> Self {
|
fn from(value: crate::errors::ControllerError) -> Self {
|
||||||
LuaError::runtime(value.to_string())
|
LuaError::runtime(value.to_string())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,49 +1,60 @@
|
||||||
use mlua_codemp_patch as mlua;
|
|
||||||
use mlua::prelude::*;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use mlua::prelude::*;
|
||||||
|
use mlua_codemp_patch as mlua;
|
||||||
|
|
||||||
use super::ext::a_sync::a_sync;
|
use super::ext::a_sync::a_sync;
|
||||||
use super::ext::from_lua_serde;
|
use super::ext::from_lua_serde;
|
||||||
|
|
||||||
impl LuaUserData for CodempWorkspace {
|
impl LuaUserData for CodempWorkspace {
|
||||||
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||||
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| Ok(format!("{:?}", this)));
|
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| {
|
||||||
methods.add_method("create", |_, this, (name,):(String,)|
|
Ok(format!("{:?}", this))
|
||||||
a_sync! { this => this.create(&name).await? }
|
});
|
||||||
|
methods.add_method(
|
||||||
|
"create",
|
||||||
|
|_, this, (name,): (String,)| a_sync! { this => this.create(&name).await? },
|
||||||
);
|
);
|
||||||
|
|
||||||
methods.add_method("attach", |_, this, (name,):(String,)|
|
methods.add_method(
|
||||||
a_sync! { this => this.attach(&name).await? }
|
"attach",
|
||||||
|
|_, this, (name,): (String,)| a_sync! { this => this.attach(&name).await? },
|
||||||
);
|
);
|
||||||
|
|
||||||
methods.add_method("detach", |_, this, (name,):(String,)|
|
methods.add_method("detach", |_, this, (name,): (String,)| {
|
||||||
Ok(this.detach(&name))
|
Ok(this.detach(&name))
|
||||||
|
});
|
||||||
|
|
||||||
|
methods.add_method(
|
||||||
|
"delete",
|
||||||
|
|_, this, (name,): (String,)| a_sync! { this => this.delete(&name).await? },
|
||||||
);
|
);
|
||||||
|
|
||||||
methods.add_method("delete", |_, this, (name,):(String,)|
|
methods.add_method("get_buffer", |_, this, (name,): (String,)| {
|
||||||
a_sync! { this => this.delete(&name).await? }
|
Ok(this.buffer_by_name(&name))
|
||||||
|
});
|
||||||
|
|
||||||
|
methods.add_method(
|
||||||
|
"event",
|
||||||
|
|_, this, ()| a_sync! { this => this.event().await? },
|
||||||
);
|
);
|
||||||
|
|
||||||
methods.add_method("get_buffer", |_, this, (name,):(String,)| Ok(this.buffer_by_name(&name)));
|
methods.add_method(
|
||||||
|
"fetch_buffers",
|
||||||
methods.add_method("event", |_, this, ()|
|
|_, this, ()| a_sync! { this => this.fetch_buffers().await? },
|
||||||
a_sync! { this => this.event().await? }
|
);
|
||||||
|
methods.add_method(
|
||||||
|
"fetch_users",
|
||||||
|
|_, this, ()| a_sync! { this => this.fetch_users().await? },
|
||||||
);
|
);
|
||||||
|
|
||||||
methods.add_method("fetch_buffers", |_, this, ()|
|
methods.add_method(
|
||||||
a_sync! { this => this.fetch_buffers().await? }
|
"filetree",
|
||||||
);
|
|_, this, (filter, strict): (Option<String>, Option<bool>)| {
|
||||||
methods.add_method("fetch_users", |_, this, ()|
|
Ok(this.filetree(filter.as_deref(), strict.unwrap_or(false)))
|
||||||
a_sync! { this => this.fetch_users().await? }
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
methods.add_method("filetree", |_, this, (filter, strict,):(Option<String>, Option<bool>,)|
|
methods.add_method("user_list", |_, this, ()| Ok(this.user_list()));
|
||||||
Ok(this.filetree(filter.as_deref(), strict.unwrap_or(false)))
|
|
||||||
);
|
|
||||||
|
|
||||||
methods.add_method("user_list", |_, this, ()|
|
|
||||||
Ok(this.user_list())
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
||||||
|
@ -57,7 +68,9 @@ impl LuaUserData for CodempWorkspace {
|
||||||
from_lua_serde! { CodempEvent }
|
from_lua_serde! { CodempEvent }
|
||||||
impl LuaUserData for CodempEvent {
|
impl LuaUserData for CodempEvent {
|
||||||
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||||
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| Ok(format!("{:?}", this)));
|
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| {
|
||||||
|
Ok(format!("{:?}", this))
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
||||||
|
@ -68,9 +81,8 @@ impl LuaUserData for CodempEvent {
|
||||||
});
|
});
|
||||||
fields.add_field_method_get("value", |_, this| match this {
|
fields.add_field_method_get("value", |_, this| match this {
|
||||||
CodempEvent::FileTreeUpdated(x)
|
CodempEvent::FileTreeUpdated(x)
|
||||||
| CodempEvent::UserJoin(x)
|
| CodempEvent::UserJoin(x)
|
||||||
| CodempEvent::UserLeave(x)
|
| CodempEvent::UserLeave(x) => Ok(x.clone()),
|
||||||
=> Ok(x.clone()),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,7 +50,7 @@
|
||||||
//! # let client = codemp::Client::connect(codemp::api::Config::new("", "")).await.unwrap();
|
//! # let client = codemp::Client::connect(codemp::api::Config::new("", "")).await.unwrap();
|
||||||
//! # client.create_workspace("").await.unwrap();
|
//! # client.create_workspace("").await.unwrap();
|
||||||
//! # let workspace = client.join_workspace("").await.unwrap();
|
//! # let workspace = client.join_workspace("").await.unwrap();
|
||||||
//! use codemp::api::Controller; // needed to access trait methods
|
//! use codemp::api::Controller; // needed to access trait methods
|
||||||
//! let cursor = workspace.cursor();
|
//! let cursor = workspace.cursor();
|
||||||
//! let event = cursor.recv().await.expect("disconnected while waiting for event!");
|
//! let event = cursor.recv().await.expect("disconnected while waiting for event!");
|
||||||
//! println!("user {} moved on buffer {}", event.user.unwrap_or_default(), event.buffer);
|
//! println!("user {} moved on buffer {}", event.user.unwrap_or_default(), event.buffer);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use codemp_proto::{
|
use codemp_proto::{
|
||||||
common::Token, buffer::buffer_client::BufferClient, cursor::cursor_client::CursorClient,
|
buffer::buffer_client::BufferClient, common::Token, cursor::cursor_client::CursorClient,
|
||||||
workspace::workspace_client::WorkspaceClient,
|
workspace::workspace_client::WorkspaceClient,
|
||||||
};
|
};
|
||||||
use tonic::{
|
use tonic::{
|
||||||
|
@ -14,10 +14,7 @@ type AuthedService = InterceptedService<Channel, WorkspaceInterceptor>;
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct SessionInterceptor(pub tokio::sync::watch::Receiver<codemp_proto::common::Token>);
|
pub struct SessionInterceptor(pub tokio::sync::watch::Receiver<codemp_proto::common::Token>);
|
||||||
impl tonic::service::Interceptor for SessionInterceptor {
|
impl tonic::service::Interceptor for SessionInterceptor {
|
||||||
fn call(
|
fn call(&mut self, mut request: tonic::Request<()>) -> tonic::Result<tonic::Request<()>> {
|
||||||
&mut self,
|
|
||||||
mut request: tonic::Request<()>,
|
|
||||||
) -> tonic::Result<tonic::Request<()>> {
|
|
||||||
if let Ok(token) = self.0.borrow().token.parse() {
|
if let Ok(token) = self.0.borrow().token.parse() {
|
||||||
request.metadata_mut().insert("session", token);
|
request.metadata_mut().insert("session", token);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,17 +2,11 @@
|
||||||
//! All-in-one renamed imports with `use codemp::prelude::*`.
|
//! All-in-one renamed imports with `use codemp::prelude::*`.
|
||||||
|
|
||||||
pub use crate::api::{
|
pub use crate::api::{
|
||||||
Controller as CodempController,
|
Config as CodempConfig, Controller as CodempController, Cursor as CodempCursor,
|
||||||
TextChange as CodempTextChange,
|
Event as CodempEvent, TextChange as CodempTextChange, User as CodempUser,
|
||||||
Cursor as CodempCursor,
|
|
||||||
User as CodempUser,
|
|
||||||
Event as CodempEvent,
|
|
||||||
Config as CodempConfig,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
client::Client as CodempClient,
|
buffer::Controller as CodempBufferController, client::Client as CodempClient,
|
||||||
workspace::Workspace as CodempWorkspace,
|
cursor::Controller as CodempCursorController, workspace::Workspace as CodempWorkspace,
|
||||||
cursor::Controller as CodempCursorController,
|
|
||||||
buffer::Controller as CodempBufferController,
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
api::{Event, User},
|
api::{Event, User},
|
||||||
buffer, cursor,
|
buffer, cursor,
|
||||||
errors::{ConnectionResult, ControllerResult, RemoteResult},
|
errors::{ConnectionResult, ControllerResult, RemoteResult},
|
||||||
ext::InternallyMutable,
|
ext::InternallyMutable,
|
||||||
network::Services,
|
network::Services,
|
||||||
|
@ -155,9 +155,9 @@ impl Workspace {
|
||||||
match self.0.buffers.remove(path) {
|
match self.0.buffers.remove(path) {
|
||||||
None => true, // noop: we werent attached in the first place
|
None => true, // noop: we werent attached in the first place
|
||||||
Some((_name, controller)) => match Arc::into_inner(controller.0) {
|
Some((_name, controller)) => match Arc::into_inner(controller.0) {
|
||||||
None => false, // dangling ref! we can't drop this
|
None => false, // dangling ref! we can't drop this
|
||||||
Some(_) => true, // dropping it now
|
Some(_) => true, // dropping it now
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -239,7 +239,6 @@ impl Workspace {
|
||||||
}))
|
}))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
|
||||||
self.0.filetree.remove(path);
|
self.0.filetree.remove(path);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -315,11 +314,15 @@ impl Workspace {
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
loop {
|
loop {
|
||||||
// TODO can we stop responsively rather than poll for Arc being dropped?
|
// TODO can we stop responsively rather than poll for Arc being dropped?
|
||||||
if weak.upgrade().is_none() { break };
|
if weak.upgrade().is_none() {
|
||||||
|
break;
|
||||||
|
};
|
||||||
let Some(res) = tokio::select!(
|
let Some(res) = tokio::select!(
|
||||||
x = stream.message() => Some(x),
|
x = stream.message() => Some(x),
|
||||||
_ = tokio::time::sleep(std::time::Duration::from_secs(5)) => None,
|
_ = tokio::time::sleep(std::time::Duration::from_secs(5)) => None,
|
||||||
) else { continue };
|
) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
match res {
|
match res {
|
||||||
Err(e) => break tracing::error!("workspace '{}' stream closed: {}", name, e),
|
Err(e) => break tracing::error!("workspace '{}' stream closed: {}", name, e),
|
||||||
Ok(None) => break tracing::info!("leaving workspace {}", name),
|
Ok(None) => break tracing::info!("leaving workspace {}", name),
|
||||||
|
|
Loading…
Reference in a new issue