feat(lua): impl all methods, add callback

this uses mlua beta but whatev api is so much better
This commit is contained in:
əlemi 2024-08-15 22:19:08 +02:00
parent a8ce56bcc0
commit 79f132236f
Signed by: alemi
GPG key ID: A4895B84D311642C
2 changed files with 147 additions and 131 deletions

View file

@ -33,7 +33,7 @@ tracing-subscriber = { version = "0.3", optional = true }
jni = { version = "0.21", features = ["invocation"], optional = true } jni = { version = "0.21", features = ["invocation"], optional = true }
# glue (lua) # glue (lua)
mlua = { version = "0.9", features = ["module", "luajit", "send"], optional = true } mlua = { version = "0.10.0-beta.1", features = ["module", "luajit", "send"], optional = true }
# glue (js) # glue (js)
napi = { version = "2.16", features = ["full"], optional = true } napi = { version = "2.16", features = ["full"], optional = true }

View file

@ -1,11 +1,12 @@
use std::io::Write; use std::io::Write;
use std::sync::Mutex; use std::sync::Mutex;
use crate::api::controller::ControllerCallback;
use crate::api::Cursor; use crate::api::Cursor;
use crate::prelude::*; use crate::prelude::*;
use crate::workspace::worker::DetachResult; use crate::workspace::worker::DetachResult;
use mlua::prelude::*; use mlua::prelude::*;
use tokio::sync::broadcast; use tokio::sync::mpsc;
impl From::<CodempError> for LuaError { impl From::<CodempError> for LuaError {
fn from(value: CodempError) -> Self { fn from(value: CodempError) -> Self {
@ -18,16 +19,6 @@ impl From::<CodempError> for LuaError {
lazy_static::lazy_static!{ lazy_static::lazy_static!{
static ref RT : tokio::runtime::Runtime = tokio::runtime::Builder::new_current_thread().enable_all().build().expect("could not create tokio runtime"); static ref RT : tokio::runtime::Runtime = tokio::runtime::Builder::new_current_thread().enable_all().build().expect("could not create tokio runtime");
static ref LOG : broadcast::Sender<String> = broadcast::channel(32).0;
static ref STORE : dashmap::DashMap<String, CodempClient> = dashmap::DashMap::default();
}
#[derive(Debug, Clone)]
struct Driver(tokio::sync::mpsc::UnboundedSender<()>);
impl LuaUserData for Driver {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_method("stop", |_, this, ()| Ok(this.0.send(()).is_ok()));
}
} }
fn runtime_drive_forever(_: &Lua, ():()) -> LuaResult<Driver> { fn runtime_drive_forever(_: &Lua, ():()) -> LuaResult<Driver> {
@ -41,28 +32,15 @@ fn runtime_drive_forever(_: &Lua, ():()) -> LuaResult<Driver> {
Ok(Driver(tx)) Ok(Driver(tx))
} }
fn connect(_: &Lua, (host, username, password): (String, String, String)) -> LuaResult<CodempClient> { #[derive(Debug, Clone)]
let client = RT.block_on(CodempClient::new(host, username, password))?; struct Driver(tokio::sync::mpsc::UnboundedSender<()>);
STORE.insert(client.user_id().to_string(), client.clone()); impl LuaUserData for Driver {
Ok(client) fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| Ok(format!("{:?}", this)));
methods.add_method("stop", |_, this, ()| Ok(this.0.send(()).is_ok()));
}
} }
fn get_client(_: &Lua, (id,): (String,)) -> LuaResult<Option<CodempClient>> {
Ok(STORE.get(&id).map(|x| x.value().clone()))
}
fn close_client(_: &Lua, (id,): (String,)) -> LuaResult<bool> {
if let Some((_id, client)) = STORE.remove(&id) {
for ws in client.active_workspaces() {
if !client.leave_workspace(&ws) {
tracing::warn!("could not leave workspace {ws}");
}
}
Ok(true)
} else {
Ok(false)
}
}
impl LuaUserData for CodempClient { impl LuaUserData for CodempClient {
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) { fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
@ -70,19 +48,19 @@ impl LuaUserData for CodempClient {
} }
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| Ok(format!("{:?}", this)));
// join a remote workspace and start processing cursor events // join a remote workspace and start processing cursor events
methods.add_method("join_workspace", |_, this, (session,):(String,)| { methods.add_method("join_workspace", |_, this, (session,):(String,)|
tracing::info!("joining workspace {}", session); Ok(RT.block_on(async { this.join_workspace(&session).await })?)
let ws = RT.block_on(async { this.join_workspace(&session).await })?; );
let cursor = ws.cursor();
Ok(cursor)
});
methods.add_method("leave_workspace", |_, this, (session,):(String,)| { methods.add_method("leave_workspace", |_, this, (session,):(String,)| {
Ok(this.leave_workspace(&session)) Ok(this.leave_workspace(&session))
}); });
methods.add_method("get_workspace", |_, this, (session,):(String,)| Ok(this.get_workspace(&session))); methods.add_method("get_workspace", |_, this, (session,):(String,)| Ok(this.get_workspace(&session)));
methods.add_method("active_workspaces", |_, this, ()| Ok(this.active_workspaces()));
} }
} }
@ -90,6 +68,7 @@ impl LuaUserData for CodempClient {
impl LuaUserData for CodempWorkspace { impl LuaUserData for CodempWorkspace {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| Ok(format!("{:?}", this)));
methods.add_method("create_buffer", |_, this, (name,):(String,)| { methods.add_method("create_buffer", |_, this, (name,):(String,)| {
Ok(RT.block_on(async { this.create(&name).await })?) Ok(RT.block_on(async { this.create(&name).await })?)
}); });
@ -109,16 +88,25 @@ impl LuaUserData for CodempWorkspace {
methods.add_method("get_buffer", |_, this, (name,):(String,)| Ok(this.buffer_by_name(&name))); methods.add_method("get_buffer", |_, this, (name,):(String,)| Ok(this.buffer_by_name(&name)));
methods.add_method("event", |_, this, ()| Ok(RT.block_on(this.event())?)); methods.add_method("event", |_, this, ()| Ok(RT.block_on(this.event())?));
methods.add_method("fetch_buffers", |_, this, ()| Ok(RT.block_on(this.fetch_buffers())?));
methods.add_method("fetch_users", |_, this, ()| Ok(RT.block_on(this.fetch_users())?));
} }
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) { fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_field_method_get("id", |_, this| Ok(this.id()));
fields.add_field_method_get("cursor", |_, this| Ok(this.cursor())); fields.add_field_method_get("cursor", |_, this| Ok(this.cursor()));
fields.add_field_method_get("filetree", |_, this| Ok(this.filetree())); fields.add_field_method_get("filetree", |_, this| Ok(this.filetree()));
fields.add_field_method_get("active_buffers", |_, this| Ok(this.buffer_list()));
// fields.add_field_method_get("users", |_, this| Ok(this.0.users())); // TODO // fields.add_field_method_get("users", |_, this| Ok(this.0.users())); // TODO
} }
} }
impl LuaUserData for CodempEvent { impl LuaUserData for CodempEvent {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| Ok(format!("{:?}", this)));
}
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) { fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_field_method_get("type", |_, this| match this { fields.add_field_method_get("type", |_, this| match this {
CodempEvent::FileTreeUpdated => Ok("filetree"), CodempEvent::FileTreeUpdated => Ok("filetree"),
@ -134,23 +122,33 @@ impl LuaUserData for CodempEvent {
impl LuaUserData for CodempCursorController { impl LuaUserData for CodempCursorController {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { fn add_methods<'lua, M: LuaUserDataMethods<'lua, 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, (buffer, start_row, start_col, end_row, end_col):(String, i32, i32, i32, i32)| { methods.add_method("send", |_, this, (buffer, start_row, start_col, end_row, end_col):(String, i32, i32, i32, i32)| {
Ok(RT.block_on(this.send(CodempCursor { buffer, start: (start_row, start_col), end: (end_row, end_col), user: None }))?) Ok(RT.block_on(this.send(CodempCursor { buffer, start: (start_row, start_col), end: (end_row, end_col), user: None }))?)
}); });
methods.add_method("try_recv", |_, this, ()| { methods.add_method("try_recv", |_, this, ()| Ok(RT.block_on(this.try_recv())?));
match RT.block_on(this.try_recv())? { methods.add_method("recv", |_, this, ()| Ok(RT.block_on(this.recv())?));
Some(x) => Ok(Some(x)), methods.add_method("poll", |_, this, ()| Ok(RT.block_on(this.poll())?));
None => Ok(None),
methods.add_method("stop", |_, this, ()| Ok(this.stop()));
methods.add_method("clear_callback", |_, this, ()| Ok(this.clear_callback()));
methods.add_method("callback", |_, this, (cb,):(LuaFunction,)| {
this.callback(ControllerCallback::from(move |controller| {
if let Err(e) = cb.call::<(CodempCursorController,), ()>((controller,)) {
tracing::error!("error running cursor callback: {e}");
} }
}); }));
methods.add_method("poll", |_, this, ()| {
RT.block_on(this.poll())?;
Ok(()) Ok(())
}); });
} }
} }
impl LuaUserData for Cursor { impl LuaUserData for Cursor {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| Ok(format!("{:?}", this)));
}
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) { fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_field_method_get("user", |_, this| Ok(this.user.map(|x| x.to_string()))); fields.add_field_method_get("user", |_, this| Ok(this.user.map(|x| x.to_string())));
fields.add_field_method_get("buffer", |_, this| Ok(this.buffer.clone())); fields.add_field_method_get("buffer", |_, this| Ok(this.buffer.clone()));
@ -172,6 +170,10 @@ impl From<(i32, i32)> for RowCol {
} }
impl LuaUserData for RowCol { impl LuaUserData for RowCol {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| Ok(format!("{:?}", this)));
}
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) { fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_field_method_get("row", |_, this| Ok(this.row)); fields.add_field_method_get("row", |_, this| Ok(this.row));
fields.add_field_method_get("col", |_, this| Ok(this.col)); fields.add_field_method_get("col", |_, this| Ok(this.col));
@ -181,6 +183,7 @@ impl LuaUserData for RowCol {
impl LuaUserData for CodempBufferController { impl LuaUserData for CodempBufferController {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { fn add_methods<'lua, M: LuaUserDataMethods<'lua, 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, (start, end, text): (usize, usize, String)| { methods.add_method("send", |_, this, (start, end, text): (usize, usize, String)| {
Ok( Ok(
RT.block_on(this.send( RT.block_on(this.send(
@ -193,19 +196,25 @@ impl LuaUserData for CodempBufferController {
))? ))?
) )
}); });
methods.add_method("try_recv", |_, this, ()| {
match RT.block_on(this.try_recv())? { methods.add_method("try_recv", |_, this, ()| Ok(RT.block_on(this.try_recv())?));
Some(x) => Ok(Some(x)), methods.add_method("recv", |_, this, ()| Ok(RT.block_on(this.recv())?));
None => Ok(None), methods.add_method("poll", |_, this, ()| Ok(RT.block_on(this.poll())?));
methods.add_method("stop", |_, this, ()| Ok(this.stop()));
methods.add_method("content", |_, this, ()| Ok(RT.block_on(this.content())?));
methods.add_method("clear_callback", |_, this, ()| Ok(this.clear_callback()));
methods.add_method("callback", |_, this, (cb,):(LuaFunction,)| {
this.callback(ControllerCallback::from(move |controller: CodempBufferController| {
let _c = controller.clone();
if let Err(e) = cb.call::<(CodempBufferController,), ()>((controller,)) {
tracing::error!("error running buffer#{} callback: {e}", _c.name());
} }
}); }));
methods.add_method("poll", |_, this, ()| {
RT.block_on(this.poll())?;
Ok(()) Ok(())
}); });
methods.add_method("content", |_, this, ()|
Ok(RT.block_on(this.content())?)
);
} }
} }
@ -218,44 +227,81 @@ impl LuaUserData for CodempTextChange {
} }
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_function(LuaMetaMethod::Call, |_, (start, end, txt, hash): (usize, usize, String, Option<i64>)| {
Ok(CodempTextChange {
start: start as u32,
end: end as u32,
content: txt,
hash,
})
});
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| Ok(format!("{:?}", this))); methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| Ok(format!("{:?}", this)));
methods.add_method("apply", |_, this, (txt,):(String,)| Ok(this.apply(&txt))); methods.add_method("apply", |_, this, (txt,):(String,)| Ok(this.apply(&txt)));
} }
} }
// define module and exports
#[mlua::lua_module]
fn codemp_lua(lua: &Lua) -> LuaResult<LuaTable> {
let exports = lua.create_table()?;
// setup library logging to file // entrypoint
#[derive(Debug)] exports.set("connect", lua.create_function(|_, (host, username, password):(String,String,String)|
struct LuaLogger(broadcast::Receiver<String>); Ok(RT.block_on(CodempClient::new(host, username, password))?)
impl LuaUserData for LuaLogger { )?)?;
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_method_mut("recv", |_, this, ()| { // utils
Ok(this.0.blocking_recv().expect("logger channel closed")) exports.set("hash", lua.create_function(|_, (txt,):(String,)|
}); Ok(crate::hash(txt))
} )?)?;
// runtime
exports.set("runtime_drive_forever", lua.create_function(runtime_drive_forever)?)?;
// logging
exports.set("logger", lua.create_function(logger)?)?;
Ok(exports)
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct LuaLoggerProducer; struct LuaLoggerProducer(mpsc::UnboundedSender<String>);
impl Write for LuaLoggerProducer { impl Write for LuaLoggerProducer {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> { fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let _ = LOG.send(String::from_utf8_lossy(buf).to_string()); let _ = self.0.send(String::from_utf8_lossy(buf).to_string());
Ok(buf.len()) Ok(buf.len())
} }
fn flush(&mut self) -> std::io::Result<()> { Ok(()) } fn flush(&mut self) -> std::io::Result<()> { Ok(()) }
} }
fn setup_logger(_: &Lua, (debug, path): (Option<bool>, Option<String>)) -> LuaResult<bool> { // TODO can we make this less verbose?
fn logger(_: &Lua, (printer, debug): (LuaValue, Option<bool>)) -> LuaResult<bool> {
let level = if debug.unwrap_or_default() { tracing::Level::DEBUG } else {tracing::Level::INFO };
let success = match printer {
LuaNil
| LuaValue::Boolean(_)
| LuaValue::LightUserData(_)
| LuaValue::Integer(_)
| LuaValue::Number(_)
| LuaValue::Table(_)
| LuaValue::Thread(_)
| LuaValue::UserData(_)
| LuaValue::Error(_) => return Err(LuaError::BindError), // TODO full BadArgument type??
LuaValue::String(path) => {
let logfile = std::fs::File::create(path.to_string_lossy()).map_err(|e| LuaError::RuntimeError(e.to_string()))?;
let format = tracing_subscriber::fmt::format()
.with_level(true)
.with_target(true)
.with_thread_ids(true)
.with_thread_names(true)
.with_ansi(false)
.with_file(false)
.with_line_number(false)
.with_source_location(false);
tracing_subscriber::fmt()
.event_format(format)
.with_max_level(level)
.with_writer(Mutex::new(logfile))
.try_init()
.is_ok()
},
LuaValue::Function(cb) => {
let (tx, mut rx) = mpsc::unbounded_channel();
let format = tracing_subscriber::fmt::format() let format = tracing_subscriber::fmt::format()
.with_level(true) .with_level(true)
.with_target(true) .with_target(true)
@ -264,54 +310,24 @@ fn setup_logger(_: &Lua, (debug, path): (Option<bool>, Option<String>)) -> LuaRe
.with_ansi(false) .with_ansi(false)
.with_file(false) .with_file(false)
.with_line_number(false) .with_line_number(false)
.with_source_location(false) .with_source_location(false);
.compact(); let res = tracing_subscriber::fmt()
let level = if debug.unwrap_or_default() { tracing::Level::DEBUG } else {tracing::Level::INFO };
let builder = tracing_subscriber::fmt()
.event_format(format) .event_format(format)
.with_max_level(level); .with_max_level(level)
.with_writer(Mutex::new(LuaLoggerProducer(tx)))
let result = if let Some(path) = path { .try_init()
let logfile = std::fs::File::create(path).expect("failed creating logfile"); .is_ok();
builder.with_writer(Mutex::new(logfile)).try_init().is_ok() if res {
} else { RT.spawn(async move {
builder.with_writer(Mutex::new(LuaLoggerProducer)).try_init().is_ok() while let Some(msg) = rx.recv().await {
let _ = cb.call::<(String,),()>((msg,));
// if the logger fails logging who logs it?
}
});
}
res
},
}; };
Ok(result) Ok(success)
} }
fn get_logger(_: &Lua, (): ()) -> LuaResult<LuaLogger> {
let sub = LOG.subscribe();
Ok(LuaLogger(sub))
}
fn hash(_: &Lua, (txt,): (String,)) -> LuaResult<i64> {
Ok(crate::hash(txt))
}
// define module and exports
#[mlua::lua_module]
fn codemp_lua(lua: &Lua) -> LuaResult<LuaTable> {
let exports = lua.create_table()?;
// entrypoint
exports.set("connect", lua.create_function(connect)?)?;
exports.set("get_client", lua.create_function(get_client)?)?;
exports.set("close_client", lua.create_function(close_client)?)?;
// utils
exports.set("hash", lua.create_function(hash)?)?;
// runtime
exports.set("runtime_drive_forever", lua.create_function(runtime_drive_forever)?)?;
// logging
exports.set("setup_logger", lua.create_function(setup_logger)?)?;
exports.set("get_logger", lua.create_function(get_logger)?)?;
Ok(exports)
}