mirror of
https://github.com/hexedtech/codemp.git
synced 2024-12-22 21:04:53 +01:00
chore(lua): split down the monofile
This commit is contained in:
parent
864348bef4
commit
54db029ecc
11 changed files with 610 additions and 550 deletions
1
.github/workflows/lua.yml
vendored
1
.github/workflows/lua.yml
vendored
|
@ -4,7 +4,6 @@ on:
|
|||
push:
|
||||
branches:
|
||||
- stable
|
||||
- dev
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
|
549
src/ffi/lua.rs
549
src/ffi/lua.rs
|
@ -1,549 +0,0 @@
|
|||
use std::io::Write;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use mlua_codemp_patch as mlua;
|
||||
|
||||
use crate::api::Cursor;
|
||||
use crate::ext::IgnorableError;
|
||||
use crate::prelude::*;
|
||||
use crate::workspace::DetachResult;
|
||||
use mlua::prelude::*;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::sync::mpsc::error::TryRecvError;
|
||||
|
||||
impl From::<crate::errors::ConnectionError> for LuaError {
|
||||
fn from(value: crate::errors::ConnectionError) -> Self {
|
||||
LuaError::runtime(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From::<crate::errors::RemoteError> for LuaError {
|
||||
fn from(value: crate::errors::RemoteError) -> Self {
|
||||
LuaError::runtime(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From::<crate::errors::ControllerError> for LuaError {
|
||||
fn from(value: crate::errors::ControllerError) -> Self {
|
||||
LuaError::runtime(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
fn tokio() -> &'static tokio::runtime::Runtime {
|
||||
use std::sync::OnceLock;
|
||||
static RT: OnceLock<tokio::runtime::Runtime> = OnceLock::new();
|
||||
RT.get_or_init(||
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("could not create tokio runtime")
|
||||
)
|
||||
}
|
||||
|
||||
fn lua_tuple<T: IntoLua>(lua: &Lua, (a, b): (T, T)) -> LuaResult<LuaTable> {
|
||||
let table = lua.create_table()?;
|
||||
table.set(1, a)?;
|
||||
table.set(2, b)?;
|
||||
Ok(table)
|
||||
}
|
||||
|
||||
enum CallbackArg {
|
||||
Nil,
|
||||
Str(String),
|
||||
VecStr(Vec<String>),
|
||||
Client(CodempClient),
|
||||
CursorController(CodempCursorController),
|
||||
BufferController(CodempBufferController),
|
||||
Workspace(CodempWorkspace),
|
||||
Event(CodempEvent),
|
||||
Cursor(CodempCursor),
|
||||
MaybeCursor(Option<CodempCursor>),
|
||||
TextChange(CodempTextChange),
|
||||
MaybeTextChange(Option<CodempTextChange>),
|
||||
}
|
||||
|
||||
impl IntoLua for CallbackArg {
|
||||
// TODO this basically calls .into_lua() on all enum variants
|
||||
// i wish i could do this with a Box<dyn IntoLua> or an impl IntoLua
|
||||
// but IntoLua requires Sized so it can't be made into an object
|
||||
fn into_lua(self, lua: &Lua) -> LuaResult<LuaValue> {
|
||||
match self {
|
||||
CallbackArg::Nil => Ok(LuaValue::Nil),
|
||||
CallbackArg::Str(x) => x.into_lua(lua),
|
||||
CallbackArg::Client(x) => x.into_lua(lua),
|
||||
CallbackArg::CursorController(x) => x.into_lua(lua),
|
||||
CallbackArg::BufferController(x) => x.into_lua(lua),
|
||||
CallbackArg::Workspace(x) => x.into_lua(lua),
|
||||
CallbackArg::VecStr(x) => x.into_lua(lua),
|
||||
CallbackArg::Event(x) => x.into_lua(lua),
|
||||
CallbackArg::Cursor(x) => x.into_lua(lua),
|
||||
CallbackArg::MaybeCursor(x) => x.into_lua(lua),
|
||||
CallbackArg::TextChange(x) => x.into_lua(lua),
|
||||
CallbackArg::MaybeTextChange(x) => x.into_lua(lua),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<()> for CallbackArg { fn from(_: ()) -> Self { CallbackArg::Nil } }
|
||||
impl From<String> for CallbackArg { fn from(value: String) -> Self { CallbackArg::Str(value) } }
|
||||
impl From<CodempClient> for CallbackArg { 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) } }
|
||||
|
||||
struct CallbackChannel {
|
||||
tx: std::sync::Arc<tokio::sync::mpsc::UnboundedSender<(LuaFunction, CallbackArg)>>,
|
||||
rx: std::sync::Mutex<tokio::sync::mpsc::UnboundedReceiver<(LuaFunction, CallbackArg)>>
|
||||
}
|
||||
|
||||
impl Default for CallbackChannel {
|
||||
fn default() -> Self {
|
||||
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
let rx = std::sync::Mutex::new(rx);
|
||||
Self {
|
||||
tx: std::sync::Arc::new(tx),
|
||||
rx,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CallbackChannel {
|
||||
fn send(&self, cb: LuaFunction, arg: impl Into<CallbackArg>) {
|
||||
self.tx.send((cb, arg.into()))
|
||||
.unwrap_or_warn("error scheduling callback")
|
||||
}
|
||||
|
||||
fn recv(&self) -> Option<(LuaFunction, CallbackArg)> {
|
||||
match self.rx.try_lock() {
|
||||
Err(e) => {
|
||||
tracing::warn!("could not acquire callback channel mutex: {e}");
|
||||
None
|
||||
},
|
||||
Ok(mut lock) => match lock.try_recv() {
|
||||
Err(TryRecvError::Empty) => None,
|
||||
Err(TryRecvError::Disconnected) => {
|
||||
tracing::error!("callback channel closed");
|
||||
None
|
||||
},
|
||||
Ok((cb, arg)) => Some((cb, arg)),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref CHANNEL: CallbackChannel = CallbackChannel::default();
|
||||
}
|
||||
|
||||
|
||||
|
||||
struct Promise(Option<tokio::task::JoinHandle<LuaResult<CallbackArg>>>);
|
||||
|
||||
impl LuaUserData for Promise {
|
||||
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("ready", |_, this|
|
||||
Ok(this.0.as_ref().map_or(true, |x| x.is_finished()))
|
||||
);
|
||||
}
|
||||
|
||||
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||
// TODO: await MUST NOT be used in callbacks!!
|
||||
methods.add_method_mut("await", |_, this, ()| match this.0.take() {
|
||||
None => Err(LuaError::runtime("Promise already awaited")),
|
||||
Some(x) => {
|
||||
tokio()
|
||||
.block_on(x)
|
||||
.map_err(LuaError::runtime)?
|
||||
},
|
||||
});
|
||||
methods.add_method_mut("and_then", |_, this, (cb,):(LuaFunction,)| match this.0.take() {
|
||||
None => Err(LuaError::runtime("Promise already awaited")),
|
||||
Some(x) => {
|
||||
tokio()
|
||||
.spawn(async move {
|
||||
match x.await {
|
||||
Err(e) => tracing::error!("could not join promise to run callback: {e}"),
|
||||
Ok(Err(e)) => tracing::error!("promise returned error: {e}"),
|
||||
Ok(Ok(res)) => CHANNEL.send(cb, res),
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! a_sync {
|
||||
($($clone:ident)* => $x:expr) => {
|
||||
{
|
||||
$(let $clone = $clone.clone();)*
|
||||
Ok(Promise(Some(tokio().spawn(async move { Ok(CallbackArg::from($x)) }))))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! from_lua_serde {
|
||||
($($t:ty)*) => {
|
||||
$(
|
||||
impl FromLua for $t {
|
||||
fn from_lua(value: LuaValue, lua: &Lua) -> LuaResult<$t> {
|
||||
lua.from_value(value)
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
fn spawn_runtime_driver(_: &Lua, ():()) -> LuaResult<Driver> {
|
||||
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
let handle = std::thread::spawn(move || tokio().block_on(async move {
|
||||
tracing::info!(" :: driving runtime...");
|
||||
tokio::select! {
|
||||
() = std::future::pending::<()>() => {},
|
||||
_ = rx.recv() => {},
|
||||
}
|
||||
}));
|
||||
Ok(Driver(tx, Some(handle)))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Driver(tokio::sync::mpsc::UnboundedSender<()>, Option<std::thread::JoinHandle<()>>);
|
||||
impl LuaUserData for Driver {
|
||||
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| Ok(format!("{:?}", this)));
|
||||
methods.add_method_mut("stop", |_, this, ()| {
|
||||
match this.1.take() {
|
||||
None => Ok(false),
|
||||
Some(handle) => {
|
||||
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:?}"))),
|
||||
Ok(()) => Ok(true),
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl LuaUserData for CodempClient {
|
||||
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("id", |_, this| Ok(this.user().id.to_string()));
|
||||
fields.add_field_method_get("username", |_, this| Ok(this.user().name.clone()));
|
||||
fields.add_field_method_get("active_workspaces", |_, this| Ok(this.active_workspaces()));
|
||||
}
|
||||
|
||||
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| Ok(format!("{:?}", this)));
|
||||
|
||||
methods.add_method("refresh", |_, this, ()|
|
||||
a_sync! { this => this.refresh().await? }
|
||||
);
|
||||
|
||||
methods.add_method("join_workspace", |_, this, (ws,):(String,)|
|
||||
a_sync! { this => this.join_workspace(ws).await? }
|
||||
);
|
||||
|
||||
methods.add_method("create_workspace", |_, this, (ws,):(String,)|
|
||||
a_sync! { this => this.create_workspace(ws).await? }
|
||||
);
|
||||
|
||||
methods.add_method("delete_workspace", |_, this, (ws,):(String,)|
|
||||
a_sync! { this => this.delete_workspace(ws).await? }
|
||||
);
|
||||
|
||||
methods.add_method("invite_to_workspace", |_, this, (ws,user):(String,String)|
|
||||
a_sync! { this => this.invite_to_workspace(ws, user).await? }
|
||||
);
|
||||
|
||||
methods.add_method("list_workspaces", |_, this, (owned,invited):(Option<bool>,Option<bool>)|
|
||||
a_sync! { this => this.list_workspaces(owned.unwrap_or(true), invited.unwrap_or(true)).await? }
|
||||
);
|
||||
|
||||
methods.add_method("leave_workspace", |_, this, (ws,):(String,)|
|
||||
Ok(this.leave_workspace(&ws))
|
||||
);
|
||||
|
||||
methods.add_method("get_workspace", |_, this, (ws,):(String,)| Ok(this.get_workspace(&ws)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl LuaUserData for CodempWorkspace {
|
||||
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| Ok(format!("{:?}", this)));
|
||||
methods.add_method("create", |_, this, (name,):(String,)|
|
||||
a_sync! { this => this.create(&name).await? }
|
||||
);
|
||||
|
||||
methods.add_method("attach", |_, this, (name,):(String,)|
|
||||
a_sync! { this => this.attach(&name).await? }
|
||||
);
|
||||
|
||||
methods.add_method("detach", |_, this, (name,):(String,)|
|
||||
Ok(matches!(this.detach(&name), DetachResult::Detaching | DetachResult::AlreadyDetached))
|
||||
);
|
||||
|
||||
methods.add_method("delete", |_, this, (name,):(String,)|
|
||||
a_sync! { this => this.delete(&name).await? }
|
||||
);
|
||||
|
||||
methods.add_method("get_buffer", |_, this, (name,):(String,)| Ok(this.buffer_by_name(&name)));
|
||||
|
||||
methods.add_method("event", |_, this, ()|
|
||||
a_sync! { this => this.event().await? }
|
||||
);
|
||||
|
||||
methods.add_method("fetch_buffers", |_, this, ()|
|
||||
a_sync! { this => this.fetch_buffers().await? }
|
||||
);
|
||||
methods.add_method("fetch_users", |_, this, ()|
|
||||
a_sync! { this => this.fetch_users().await? }
|
||||
);
|
||||
|
||||
methods.add_method("filetree", |_, this, (filter, strict,):(Option<String>, Option<bool>,)|
|
||||
Ok(this.filetree(filter.as_deref(), strict.unwrap_or(false)))
|
||||
);
|
||||
}
|
||||
|
||||
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("name", |_, this| Ok(this.id()));
|
||||
fields.add_field_method_get("cursor", |_, this| Ok(this.cursor()));
|
||||
fields.add_field_method_get("active_buffers", |_, this| Ok(this.buffer_list()));
|
||||
// fields.add_field_method_get("users", |_, this| Ok(this.0.users())); // TODO
|
||||
}
|
||||
}
|
||||
|
||||
from_lua_serde! { CodempEvent }
|
||||
impl LuaUserData for CodempEvent {
|
||||
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| Ok(format!("{:?}", this)));
|
||||
}
|
||||
|
||||
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("type", |_, this| match this {
|
||||
CodempEvent::FileTreeUpdated(_) => Ok("filetree"),
|
||||
CodempEvent::UserJoin(_) => Ok("join"),
|
||||
CodempEvent::UserLeave(_) => Ok("leave"),
|
||||
});
|
||||
fields.add_field_method_get("value", |_, this| match this {
|
||||
CodempEvent::FileTreeUpdated(x)
|
||||
| CodempEvent::UserJoin(x)
|
||||
| CodempEvent::UserLeave(x)
|
||||
=> Ok(x.clone()),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl LuaUserData for CodempCursorController {
|
||||
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| Ok(format!("{:?}", this)));
|
||||
|
||||
methods.add_method("send", |_, this, (cursor,):(CodempCursor,)|
|
||||
a_sync! { this => this.send(cursor).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("poll", |_, this, ()| a_sync! { this => this.poll().await? });
|
||||
|
||||
methods.add_method("stop", |_, this, ()| Ok(this.stop()));
|
||||
|
||||
methods.add_method("clear_callback", |_, this, ()| { this.clear_callback(); Ok(()) });
|
||||
methods.add_method("callback", |_, this, (cb,):(LuaFunction,)| {
|
||||
this.callback(move |controller: CodempCursorController| CHANNEL.send(cb.clone(), controller));
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
from_lua_serde! { CodempCursor }
|
||||
impl LuaUserData for Cursor {
|
||||
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| Ok(format!("{:?}", this)));
|
||||
}
|
||||
|
||||
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("buffer", |_, this| Ok(this.buffer.clone()));
|
||||
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));
|
||||
// add a 'finish' accessor too because in Lua 'end' is reserved
|
||||
fields.add_field_method_get("finish", |lua, this| lua_tuple(lua, this.end));
|
||||
}
|
||||
}
|
||||
|
||||
impl LuaUserData for CodempBufferController {
|
||||
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| Ok(format!("{:?}", this)));
|
||||
|
||||
methods.add_method("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("recv", |_, this, ()| a_sync! { this => this.recv().await? });
|
||||
methods.add_method("poll", |_, this, ()| a_sync! { this => this.poll().await? });
|
||||
|
||||
methods.add_method("stop", |_, this, ()| Ok(this.stop()));
|
||||
|
||||
methods.add_method("content", |_, this, ()| a_sync! { this => this.content().await? });
|
||||
|
||||
methods.add_method("clear_callback", |_, this, ()| { this.clear_callback(); Ok(()) });
|
||||
methods.add_method("callback", |_, this, (cb,):(LuaFunction,)| {
|
||||
this.callback(move |controller: CodempBufferController| CHANNEL.send(cb.clone(), controller));
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
from_lua_serde! { CodempTextChange }
|
||||
impl LuaUserData for CodempTextChange {
|
||||
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("start", |_, this| Ok(this.start));
|
||||
fields.add_field_method_get("end", |_, this| Ok(this.end));
|
||||
fields.add_field_method_get("hash", |_, this| Ok(this.hash));
|
||||
// add a 'finish' accessor too because in Lua 'end' is reserved
|
||||
fields.add_field_method_get("finish", |_, this| Ok(this.end));
|
||||
}
|
||||
|
||||
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| Ok(format!("{:?}", this)));
|
||||
methods.add_method("apply", |_, this, (txt,):(String,)| Ok(this.apply(&txt)));
|
||||
}
|
||||
}
|
||||
|
||||
from_lua_serde! { CodempConfig }
|
||||
impl LuaUserData for CodempConfig {
|
||||
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("username", |_, this| Ok(this.username.clone()));
|
||||
fields.add_field_method_get("password", |_, this| Ok(this.password.clone()));
|
||||
fields.add_field_method_get("host", |_, this| Ok(this.host.clone()));
|
||||
fields.add_field_method_get("port", |_, this| Ok(this.port));
|
||||
fields.add_field_method_get("tls", |_, this| Ok(this.tls));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct LuaLoggerProducer(mpsc::UnboundedSender<String>);
|
||||
impl Write for LuaLoggerProducer {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
let _ = self.0.send(String::from_utf8_lossy(buf).to_string());
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> { Ok(()) }
|
||||
}
|
||||
|
||||
// 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 format = tracing_subscriber::fmt::format()
|
||||
.with_level(true)
|
||||
.with_target(true)
|
||||
.with_thread_ids(false)
|
||||
.with_thread_names(false)
|
||||
.with_file(false)
|
||||
.with_line_number(false)
|
||||
.with_source_location(false);
|
||||
|
||||
let success = match printer {
|
||||
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::Nil => {
|
||||
tracing_subscriber::fmt()
|
||||
.event_format(format)
|
||||
.with_max_level(level)
|
||||
.with_writer(std::sync::Mutex::new(std::io::stderr()))
|
||||
.try_init()
|
||||
.is_ok()
|
||||
},
|
||||
LuaValue::String(path) => {
|
||||
let logfile = std::fs::File::create(path.to_string_lossy()).map_err(|e| LuaError::RuntimeError(e.to_string()))?;
|
||||
tracing_subscriber::fmt()
|
||||
.event_format(format)
|
||||
.with_max_level(level)
|
||||
.with_writer(Mutex::new(logfile))
|
||||
.with_ansi(false)
|
||||
.try_init()
|
||||
.is_ok()
|
||||
},
|
||||
LuaValue::Function(cb) => {
|
||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||
let res = tracing_subscriber::fmt()
|
||||
.event_format(format)
|
||||
.with_max_level(level)
|
||||
.with_writer(Mutex::new(LuaLoggerProducer(tx)))
|
||||
.with_ansi(false)
|
||||
.try_init()
|
||||
.is_ok();
|
||||
if res {
|
||||
tokio().spawn(async move {
|
||||
while let Some(msg) = rx.recv().await {
|
||||
CHANNEL.send(cb.clone(), msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
res
|
||||
},
|
||||
};
|
||||
|
||||
Ok(success)
|
||||
}
|
||||
|
||||
|
||||
// define module and exports
|
||||
#[mlua::lua_module(name = "codemp")] fn entry_1(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> {
|
||||
let exports = lua.create_table()?;
|
||||
|
||||
// entrypoint
|
||||
exports.set("connect", lua.create_function(|_, (config,):(CodempConfig,)|
|
||||
a_sync! { => CodempClient::connect(config).await? }
|
||||
)?)?;
|
||||
|
||||
// utils
|
||||
exports.set("hash", lua.create_function(|_, (txt,):(String,)|
|
||||
Ok(crate::ext::hash(txt))
|
||||
)?)?;
|
||||
|
||||
// runtime
|
||||
exports.set("spawn_runtime_driver", lua.create_function(spawn_runtime_driver)?)?;
|
||||
exports.set("poll_callback", lua.create_function(|lua, ()| {
|
||||
// TODO pass args too
|
||||
let mut val = LuaMultiValue::new();
|
||||
if let Some((cb, arg)) = CHANNEL.recv() {
|
||||
val.push_back(LuaValue::Function(cb));
|
||||
val.push_back(arg.into_lua(lua)?);
|
||||
}
|
||||
Ok(val)
|
||||
})?)?;
|
||||
|
||||
// logging
|
||||
exports.set("logger", lua.create_function(logger)?)?;
|
||||
|
||||
Ok(exports)
|
||||
}
|
49
src/ffi/lua/buffer.rs
Normal file
49
src/ffi/lua/buffer.rs
Normal file
|
@ -0,0 +1,49 @@
|
|||
use mlua_codemp_patch as mlua;
|
||||
use mlua::prelude::*;
|
||||
use crate::prelude::*;
|
||||
|
||||
use super::ext::a_sync::a_sync;
|
||||
use super::ext::from_lua_serde;
|
||||
use super::ext::callback::CHANNEL;
|
||||
|
||||
|
||||
impl LuaUserData for CodempBufferController {
|
||||
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| Ok(format!("{:?}", this)));
|
||||
|
||||
methods.add_method("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("recv", |_, this, ()| a_sync! { this => this.recv().await? });
|
||||
methods.add_method("poll", |_, this, ()| a_sync! { this => this.poll().await? });
|
||||
|
||||
methods.add_method("stop", |_, this, ()| Ok(this.stop()));
|
||||
|
||||
methods.add_method("content", |_, this, ()| a_sync! { this => this.content().await? });
|
||||
|
||||
methods.add_method("clear_callback", |_, this, ()| { this.clear_callback(); Ok(()) });
|
||||
methods.add_method("callback", |_, this, (cb,):(LuaFunction,)| {
|
||||
this.callback(move |controller: CodempBufferController| CHANNEL.send(cb.clone(), controller));
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
from_lua_serde! { CodempTextChange }
|
||||
impl LuaUserData for CodempTextChange {
|
||||
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("start", |_, this| Ok(this.start));
|
||||
fields.add_field_method_get("end", |_, this| Ok(this.end));
|
||||
fields.add_field_method_get("hash", |_, this| Ok(this.hash));
|
||||
// add a 'finish' accessor too because in Lua 'end' is reserved
|
||||
fields.add_field_method_get("finish", |_, this| Ok(this.end));
|
||||
}
|
||||
|
||||
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| Ok(format!("{:?}", this)));
|
||||
methods.add_method("apply", |_, this, (txt,):(String,)| Ok(this.apply(&txt)));
|
||||
}
|
||||
}
|
59
src/ffi/lua/client.rs
Normal file
59
src/ffi/lua/client.rs
Normal file
|
@ -0,0 +1,59 @@
|
|||
use mlua_codemp_patch as mlua;
|
||||
use mlua::prelude::*;
|
||||
use crate::prelude::*;
|
||||
|
||||
use super::ext::a_sync::a_sync;
|
||||
use super::ext::from_lua_serde;
|
||||
|
||||
impl LuaUserData for CodempClient {
|
||||
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("id", |_, this| Ok(this.user().id.to_string()));
|
||||
fields.add_field_method_get("username", |_, this| Ok(this.user().name.clone()));
|
||||
fields.add_field_method_get("active_workspaces", |_, this| Ok(this.active_workspaces()));
|
||||
}
|
||||
|
||||
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| Ok(format!("{:?}", this)));
|
||||
|
||||
methods.add_method("refresh", |_, this, ()|
|
||||
a_sync! { this => this.refresh().await? }
|
||||
);
|
||||
|
||||
methods.add_method("join_workspace", |_, this, (ws,):(String,)|
|
||||
a_sync! { this => this.join_workspace(ws).await? }
|
||||
);
|
||||
|
||||
methods.add_method("create_workspace", |_, this, (ws,):(String,)|
|
||||
a_sync! { this => this.create_workspace(ws).await? }
|
||||
);
|
||||
|
||||
methods.add_method("delete_workspace", |_, this, (ws,):(String,)|
|
||||
a_sync! { this => this.delete_workspace(ws).await? }
|
||||
);
|
||||
|
||||
methods.add_method("invite_to_workspace", |_, this, (ws,user):(String,String)|
|
||||
a_sync! { this => this.invite_to_workspace(ws, user).await? }
|
||||
);
|
||||
|
||||
methods.add_method("list_workspaces", |_, this, (owned,invited):(Option<bool>,Option<bool>)|
|
||||
a_sync! { this => this.list_workspaces(owned.unwrap_or(true), invited.unwrap_or(true)).await? }
|
||||
);
|
||||
|
||||
methods.add_method("leave_workspace", |_, this, (ws,):(String,)|
|
||||
Ok(this.leave_workspace(&ws))
|
||||
);
|
||||
|
||||
methods.add_method("get_workspace", |_, this, (ws,):(String,)| Ok(this.get_workspace(&ws)));
|
||||
}
|
||||
}
|
||||
|
||||
from_lua_serde! { CodempConfig }
|
||||
impl LuaUserData for CodempConfig {
|
||||
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("username", |_, this| Ok(this.username.clone()));
|
||||
fields.add_field_method_get("password", |_, this| Ok(this.password.clone()));
|
||||
fields.add_field_method_get("host", |_, this| Ok(this.host.clone()));
|
||||
fields.add_field_method_get("port", |_, this| Ok(this.port));
|
||||
fields.add_field_method_get("tls", |_, this| Ok(this.tls));
|
||||
}
|
||||
}
|
47
src/ffi/lua/cursor.rs
Normal file
47
src/ffi/lua/cursor.rs
Normal file
|
@ -0,0 +1,47 @@
|
|||
use mlua_codemp_patch as mlua;
|
||||
use mlua::prelude::*;
|
||||
use crate::prelude::*;
|
||||
|
||||
use super::ext::a_sync::a_sync;
|
||||
use super::ext::from_lua_serde;
|
||||
use super::ext::callback::CHANNEL;
|
||||
use super::ext::lua_tuple;
|
||||
|
||||
impl LuaUserData for CodempCursorController {
|
||||
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| Ok(format!("{:?}", this)));
|
||||
|
||||
methods.add_method("send", |_, this, (cursor,):(CodempCursor,)|
|
||||
a_sync! { this => this.send(cursor).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("poll", |_, this, ()| a_sync! { this => this.poll().await? });
|
||||
|
||||
methods.add_method("stop", |_, this, ()| Ok(this.stop()));
|
||||
|
||||
methods.add_method("clear_callback", |_, this, ()| { this.clear_callback(); Ok(()) });
|
||||
methods.add_method("callback", |_, this, (cb,):(LuaFunction,)| {
|
||||
this.callback(move |controller: CodempCursorController| CHANNEL.send(cb.clone(), controller));
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
from_lua_serde! { CodempCursor }
|
||||
impl LuaUserData for CodempCursor {
|
||||
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| Ok(format!("{:?}", this)));
|
||||
}
|
||||
|
||||
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("buffer", |_, this| Ok(this.buffer.clone()));
|
||||
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));
|
||||
// add a 'finish' accessor too because in Lua 'end' is reserved
|
||||
fields.add_field_method_get("finish", |lua, this| lua_tuple(lua, this.end));
|
||||
}
|
||||
}
|
113
src/ffi/lua/ext/a_sync.rs
Normal file
113
src/ffi/lua/ext/a_sync.rs
Normal file
|
@ -0,0 +1,113 @@
|
|||
use mlua_codemp_patch as mlua;
|
||||
use mlua::prelude::*;
|
||||
|
||||
use super::callback::CHANNEL;
|
||||
|
||||
pub(crate) fn tokio() -> &'static tokio::runtime::Runtime {
|
||||
use std::sync::OnceLock;
|
||||
static RT: OnceLock<tokio::runtime::Runtime> = OnceLock::new();
|
||||
RT.get_or_init(||
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("could not create tokio runtime")
|
||||
)
|
||||
}
|
||||
|
||||
macro_rules! a_sync {
|
||||
($($clone:ident)* => $x:expr) => {
|
||||
{
|
||||
$(let $clone = $clone.clone();)*
|
||||
Ok(
|
||||
crate::ffi::lua::ext::a_sync::Promise(
|
||||
Some(
|
||||
crate::ffi::lua::ext::a_sync::tokio()
|
||||
.spawn(async move {
|
||||
Ok(crate::ffi::lua::ext::callback::CallbackArg::from($x))
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) use a_sync;
|
||||
|
||||
pub(crate) struct Promise(pub(crate) Option<tokio::task::JoinHandle<LuaResult<super::callback::CallbackArg>>>);
|
||||
|
||||
impl LuaUserData for Promise {
|
||||
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("ready", |_, this|
|
||||
Ok(this.0.as_ref().map_or(true, |x| x.is_finished()))
|
||||
);
|
||||
}
|
||||
|
||||
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||
// TODO: await MUST NOT be used in callbacks!!
|
||||
methods.add_method_mut("await", |_, this, ()| match this.0.take() {
|
||||
None => Err(LuaError::runtime("Promise already awaited")),
|
||||
Some(x) => {
|
||||
tokio()
|
||||
.block_on(x)
|
||||
.map_err(LuaError::runtime)?
|
||||
},
|
||||
});
|
||||
methods.add_method_mut("and_then", |_, this, (cb,):(LuaFunction,)| match this.0.take() {
|
||||
None => Err(LuaError::runtime("Promise already awaited")),
|
||||
Some(x) => {
|
||||
tokio()
|
||||
.spawn(async move {
|
||||
match x.await {
|
||||
Err(e) => tracing::error!("could not join promise to run callback: {e}"),
|
||||
Ok(Err(e)) => tracing::error!("promise returned error: {e}"),
|
||||
Ok(Ok(res)) => CHANNEL.send(cb, res),
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn spawn_runtime_driver(_: &Lua, (block,):(Option<bool>,)) -> LuaResult<Option<Driver>> {
|
||||
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
let future = async move {
|
||||
tracing::info!(" :: driving runtime...");
|
||||
tokio::select! {
|
||||
() = std::future::pending::<()>() => {},
|
||||
_ = rx.recv() => {},
|
||||
}
|
||||
};
|
||||
if block.unwrap_or(false) {
|
||||
super::tokio().block_on(future);
|
||||
Ok(None)
|
||||
} else {
|
||||
let handle = std::thread::spawn(move || super::tokio().block_on(future));
|
||||
Ok(Some(Driver(tx, Some(handle))))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Driver(pub(crate) tokio::sync::mpsc::UnboundedSender<()>, Option<std::thread::JoinHandle<()>>);
|
||||
impl LuaUserData for Driver {
|
||||
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| Ok(format!("{:?}", this)));
|
||||
methods.add_method_mut("stop", |_, this, ()| {
|
||||
match this.1.take() {
|
||||
None => Ok(false),
|
||||
Some(handle) => {
|
||||
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:?}"))),
|
||||
Ok(()) => Ok(true),
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
98
src/ffi/lua/ext/callback.rs
Normal file
98
src/ffi/lua/ext/callback.rs
Normal file
|
@ -0,0 +1,98 @@
|
|||
use mlua_codemp_patch as mlua;
|
||||
use mlua::prelude::*;
|
||||
use crate::prelude::*;
|
||||
use crate::ext::IgnorableError;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub(crate) static ref CHANNEL: CallbackChannel = CallbackChannel::default();
|
||||
}
|
||||
|
||||
pub(crate) struct CallbackChannel {
|
||||
tx: std::sync::Arc<tokio::sync::mpsc::UnboundedSender<(LuaFunction, CallbackArg)>>,
|
||||
rx: std::sync::Mutex<tokio::sync::mpsc::UnboundedReceiver<(LuaFunction, CallbackArg)>>
|
||||
}
|
||||
|
||||
impl Default for CallbackChannel {
|
||||
fn default() -> Self {
|
||||
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
let rx = std::sync::Mutex::new(rx);
|
||||
Self {
|
||||
tx: std::sync::Arc::new(tx),
|
||||
rx,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CallbackChannel {
|
||||
pub(crate) fn send(&self, cb: LuaFunction, arg: impl Into<CallbackArg>) {
|
||||
self.tx.send((cb, arg.into()))
|
||||
.unwrap_or_warn("error scheduling callback")
|
||||
}
|
||||
|
||||
pub(crate) fn recv(&self) -> Option<(LuaFunction, CallbackArg)> {
|
||||
match self.rx.try_lock() {
|
||||
Err(e) => {
|
||||
tracing::warn!("could not acquire callback channel mutex: {e}");
|
||||
None
|
||||
},
|
||||
Ok(mut lock) => match lock.try_recv() {
|
||||
Err(tokio::sync::mpsc::error::TryRecvError::Empty) => None,
|
||||
Err(tokio::sync::mpsc::error::TryRecvError::Disconnected) => {
|
||||
tracing::error!("callback channel closed");
|
||||
None
|
||||
},
|
||||
Ok((cb, arg)) => Some((cb, arg)),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum CallbackArg {
|
||||
Nil,
|
||||
Str(String),
|
||||
VecStr(Vec<String>),
|
||||
Client(CodempClient),
|
||||
CursorController(CodempCursorController),
|
||||
BufferController(CodempBufferController),
|
||||
Workspace(CodempWorkspace),
|
||||
Event(CodempEvent),
|
||||
Cursor(CodempCursor),
|
||||
MaybeCursor(Option<CodempCursor>),
|
||||
TextChange(CodempTextChange),
|
||||
MaybeTextChange(Option<CodempTextChange>),
|
||||
}
|
||||
|
||||
impl IntoLua for CallbackArg {
|
||||
// TODO this basically calls .into_lua() on all enum variants
|
||||
// i wish i could do this with a Box<dyn IntoLua> or an impl IntoLua
|
||||
// but IntoLua requires Sized so it can't be made into an object
|
||||
fn into_lua(self, lua: &Lua) -> LuaResult<LuaValue> {
|
||||
match self {
|
||||
CallbackArg::Nil => Ok(LuaValue::Nil),
|
||||
CallbackArg::Str(x) => x.into_lua(lua),
|
||||
CallbackArg::Client(x) => x.into_lua(lua),
|
||||
CallbackArg::CursorController(x) => x.into_lua(lua),
|
||||
CallbackArg::BufferController(x) => x.into_lua(lua),
|
||||
CallbackArg::Workspace(x) => x.into_lua(lua),
|
||||
CallbackArg::VecStr(x) => x.into_lua(lua),
|
||||
CallbackArg::Event(x) => x.into_lua(lua),
|
||||
CallbackArg::Cursor(x) => x.into_lua(lua),
|
||||
CallbackArg::MaybeCursor(x) => x.into_lua(lua),
|
||||
CallbackArg::TextChange(x) => x.into_lua(lua),
|
||||
CallbackArg::MaybeTextChange(x) => x.into_lua(lua),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<()> for CallbackArg { fn from(_: ()) -> Self { CallbackArg::Nil } }
|
||||
impl From<String> for CallbackArg { fn from(value: String) -> Self { CallbackArg::Str(value) } }
|
||||
impl From<CodempClient> for CallbackArg { 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) } }
|
78
src/ffi/lua/ext/log.rs
Normal file
78
src/ffi/lua/ext/log.rs
Normal file
|
@ -0,0 +1,78 @@
|
|||
use std::{io::Write, sync::Mutex};
|
||||
|
||||
use mlua_codemp_patch as mlua;
|
||||
use mlua::prelude::*;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct LuaLoggerProducer(mpsc::UnboundedSender<String>);
|
||||
impl Write for LuaLoggerProducer {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
let _ = self.0.send(String::from_utf8_lossy(buf).to_string());
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> { Ok(()) }
|
||||
}
|
||||
|
||||
// TODO can we make this less verbose?
|
||||
pub(crate) 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 format = tracing_subscriber::fmt::format()
|
||||
.with_level(true)
|
||||
.with_target(true)
|
||||
.with_thread_ids(false)
|
||||
.with_thread_names(false)
|
||||
.with_file(false)
|
||||
.with_line_number(false)
|
||||
.with_source_location(false);
|
||||
|
||||
let success = match printer {
|
||||
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::Nil => {
|
||||
tracing_subscriber::fmt()
|
||||
.event_format(format)
|
||||
.with_max_level(level)
|
||||
.with_writer(std::sync::Mutex::new(std::io::stderr()))
|
||||
.try_init()
|
||||
.is_ok()
|
||||
},
|
||||
LuaValue::String(path) => {
|
||||
let logfile = std::fs::File::create(path.to_string_lossy()).map_err(|e| LuaError::RuntimeError(e.to_string()))?;
|
||||
tracing_subscriber::fmt()
|
||||
.event_format(format)
|
||||
.with_max_level(level)
|
||||
.with_writer(Mutex::new(logfile))
|
||||
.with_ansi(false)
|
||||
.try_init()
|
||||
.is_ok()
|
||||
},
|
||||
LuaValue::Function(cb) => {
|
||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||
let res = tracing_subscriber::fmt()
|
||||
.event_format(format)
|
||||
.with_max_level(level)
|
||||
.with_writer(Mutex::new(LuaLoggerProducer(tx)))
|
||||
.with_ansi(false)
|
||||
.try_init()
|
||||
.is_ok();
|
||||
if res {
|
||||
super::a_sync::tokio().spawn(async move {
|
||||
while let Some(msg) = rx.recv().await {
|
||||
super::callback::CHANNEL.send(cb.clone(), msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
res
|
||||
},
|
||||
};
|
||||
|
||||
Ok(success)
|
||||
}
|
29
src/ffi/lua/ext/mod.rs
Normal file
29
src/ffi/lua/ext/mod.rs
Normal file
|
@ -0,0 +1,29 @@
|
|||
pub mod a_sync;
|
||||
pub mod callback;
|
||||
pub mod log;
|
||||
|
||||
use mlua_codemp_patch as mlua;
|
||||
use mlua::prelude::*;
|
||||
|
||||
pub(crate) use a_sync::tokio;
|
||||
|
||||
pub(crate) fn lua_tuple<T: IntoLua>(lua: &Lua, (a, b): (T, T)) -> LuaResult<LuaTable> {
|
||||
let table = lua.create_table()?;
|
||||
table.set(1, a)?;
|
||||
table.set(2, b)?;
|
||||
Ok(table)
|
||||
}
|
||||
|
||||
macro_rules! from_lua_serde {
|
||||
($($t:ty)*) => {
|
||||
$(
|
||||
impl FromLua for $t {
|
||||
fn from_lua(value: LuaValue, lua: &Lua) -> LuaResult<$t> {
|
||||
lua.from_value(value)
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) use from_lua_serde;
|
64
src/ffi/lua/mod.rs
Normal file
64
src/ffi/lua/mod.rs
Normal file
|
@ -0,0 +1,64 @@
|
|||
mod client;
|
||||
mod workspace;
|
||||
mod cursor;
|
||||
mod buffer;
|
||||
mod ext;
|
||||
|
||||
use mlua_codemp_patch as mlua;
|
||||
use mlua::prelude::*;
|
||||
use crate::prelude::*;
|
||||
|
||||
// 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 = "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> {
|
||||
let exports = lua.create_table()?;
|
||||
|
||||
// entrypoint
|
||||
exports.set("connect", lua.create_function(|_, (config,):(CodempConfig,)|
|
||||
ext::a_sync::a_sync! { => CodempClient::connect(config).await? }
|
||||
)?)?;
|
||||
|
||||
// utils
|
||||
exports.set("hash", lua.create_function(|_, (txt,):(String,)|
|
||||
Ok(crate::ext::hash(txt))
|
||||
)?)?;
|
||||
|
||||
// runtime
|
||||
exports.set("spawn_runtime_driver", lua.create_function(ext::a_sync::spawn_runtime_driver)?)?;
|
||||
exports.set("poll_callback", lua.create_function(|lua, ()| {
|
||||
// TODO pass args too
|
||||
let mut val = LuaMultiValue::new();
|
||||
if let Some((cb, arg)) = ext::callback::CHANNEL.recv() {
|
||||
val.push_back(LuaValue::Function(cb));
|
||||
val.push_back(arg.into_lua(lua)?);
|
||||
}
|
||||
Ok(val)
|
||||
})?)?;
|
||||
|
||||
// logging
|
||||
exports.set("logger", lua.create_function(ext::log::logger)?)?;
|
||||
|
||||
Ok(exports)
|
||||
}
|
||||
|
||||
impl From::<crate::errors::ConnectionError> for LuaError {
|
||||
fn from(value: crate::errors::ConnectionError) -> Self {
|
||||
LuaError::runtime(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From::<crate::errors::RemoteError> for LuaError {
|
||||
fn from(value: crate::errors::RemoteError) -> Self {
|
||||
LuaError::runtime(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From::<crate::errors::ControllerError> for LuaError {
|
||||
fn from(value: crate::errors::ControllerError) -> Self {
|
||||
LuaError::runtime(value.to_string())
|
||||
}
|
||||
}
|
73
src/ffi/lua/workspace.rs
Normal file
73
src/ffi/lua/workspace.rs
Normal file
|
@ -0,0 +1,73 @@
|
|||
use mlua_codemp_patch as mlua;
|
||||
use mlua::prelude::*;
|
||||
use crate::prelude::*;
|
||||
use crate::workspace::DetachResult;
|
||||
|
||||
use super::ext::a_sync::a_sync;
|
||||
use super::ext::from_lua_serde;
|
||||
|
||||
impl LuaUserData for CodempWorkspace {
|
||||
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| Ok(format!("{:?}", this)));
|
||||
methods.add_method("create", |_, this, (name,):(String,)|
|
||||
a_sync! { this => this.create(&name).await? }
|
||||
);
|
||||
|
||||
methods.add_method("attach", |_, this, (name,):(String,)|
|
||||
a_sync! { this => this.attach(&name).await? }
|
||||
);
|
||||
|
||||
methods.add_method("detach", |_, this, (name,):(String,)|
|
||||
Ok(matches!(this.detach(&name), DetachResult::Detaching | DetachResult::AlreadyDetached))
|
||||
);
|
||||
|
||||
methods.add_method("delete", |_, this, (name,):(String,)|
|
||||
a_sync! { this => this.delete(&name).await? }
|
||||
);
|
||||
|
||||
methods.add_method("get_buffer", |_, this, (name,):(String,)| Ok(this.buffer_by_name(&name)));
|
||||
|
||||
methods.add_method("event", |_, this, ()|
|
||||
a_sync! { this => this.event().await? }
|
||||
);
|
||||
|
||||
methods.add_method("fetch_buffers", |_, this, ()|
|
||||
a_sync! { this => this.fetch_buffers().await? }
|
||||
);
|
||||
methods.add_method("fetch_users", |_, this, ()|
|
||||
a_sync! { this => this.fetch_users().await? }
|
||||
);
|
||||
|
||||
methods.add_method("filetree", |_, this, (filter, strict,):(Option<String>, Option<bool>,)|
|
||||
Ok(this.filetree(filter.as_deref(), strict.unwrap_or(false)))
|
||||
);
|
||||
}
|
||||
|
||||
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("name", |_, this| Ok(this.id()));
|
||||
fields.add_field_method_get("cursor", |_, this| Ok(this.cursor()));
|
||||
fields.add_field_method_get("active_buffers", |_, this| Ok(this.buffer_list()));
|
||||
// fields.add_field_method_get("users", |_, this| Ok(this.0.users())); // TODO
|
||||
}
|
||||
}
|
||||
|
||||
from_lua_serde! { CodempEvent }
|
||||
impl LuaUserData for CodempEvent {
|
||||
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| Ok(format!("{:?}", this)));
|
||||
}
|
||||
|
||||
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("type", |_, this| match this {
|
||||
CodempEvent::FileTreeUpdated(_) => Ok("filetree"),
|
||||
CodempEvent::UserJoin(_) => Ok("join"),
|
||||
CodempEvent::UserLeave(_) => Ok("leave"),
|
||||
});
|
||||
fields.add_field_method_get("value", |_, this| match this {
|
||||
CodempEvent::FileTreeUpdated(x)
|
||||
| CodempEvent::UserJoin(x)
|
||||
| CodempEvent::UserLeave(x)
|
||||
=> Ok(x.clone()),
|
||||
});
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue