mirror of
https://github.com/hexedtech/codemp.git
synced 2024-11-22 07:14:50 +01:00
Updated Javascript Glue with Napi
Co-authored-by: alemi.dev <me@alemi.dev>
This commit is contained in:
parent
e4b6e82485
commit
ffa8d8ea82
9 changed files with 125 additions and 144 deletions
|
@ -42,8 +42,8 @@ derive_more = { version = "0.99.17", optional = true }
|
||||||
|
|
||||||
# glue (js)
|
# glue (js)
|
||||||
rmpv = { version = "1", optional = true }
|
rmpv = { version = "1", optional = true }
|
||||||
napi = { version = "2", features = ["full"], optional = true }
|
napi = { version = "2.16", features = ["full"], optional = true }
|
||||||
napi-derive = { version="2", optional = true}
|
napi-derive = { version="2.16", optional = true}
|
||||||
futures = { version = "0.3.28", optional = true }
|
futures = { version = "0.3.28", optional = true }
|
||||||
|
|
||||||
# glue (python)
|
# glue (python)
|
||||||
|
@ -60,7 +60,7 @@ napi-build = { version = "2", optional = true }
|
||||||
pyo3-build-config = { version = "0.19.2", optional = true }
|
pyo3-build-config = { version = "0.19.2", optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = ["js"]
|
||||||
lua = ["mlua", "derive_more", "lazy_static", "tracing-subscriber"]
|
lua = ["mlua", "derive_more", "lazy_static", "tracing-subscriber"]
|
||||||
java = ["lazy_static", "jni", "tracing-subscriber"]
|
java = ["lazy_static", "jni", "tracing-subscriber"]
|
||||||
java-artifact = ["java"] # also builds the jar
|
java-artifact = ["java"] # also builds the jar
|
||||||
|
|
|
@ -30,11 +30,15 @@ pub struct Op(pub(crate) woot::crdt::Op);
|
||||||
/// to delete a the fourth character we should send a
|
/// to delete a the fourth character we should send a
|
||||||
/// `TextChange { span: 3..4, content: "".into() }`
|
/// `TextChange { span: 3..4, content: "".into() }`
|
||||||
///
|
///
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
#[cfg_attr(feature = "python", pyo3::pyclass)]
|
#[cfg_attr(feature = "python", pyo3::pyclass)]
|
||||||
|
#[cfg_attr(feature = "js", napi_derive::napi(object))]
|
||||||
pub struct TextChange {
|
pub struct TextChange {
|
||||||
/// range of text change, as char indexes in buffer previous state
|
/// range start of text change, as char indexes in buffer previous state
|
||||||
pub span: std::ops::Range<usize>,
|
pub start: u32,
|
||||||
|
/// range end of text change, as char indexes in buffer previous state
|
||||||
|
pub end: u32,
|
||||||
/// new content of text inside span
|
/// new content of text inside span
|
||||||
pub content: String,
|
pub content: String,
|
||||||
}
|
}
|
||||||
|
@ -65,11 +69,16 @@ impl TextChange {
|
||||||
let end_after = after.len() - end;
|
let end_after = after.len() - end;
|
||||||
|
|
||||||
TextChange {
|
TextChange {
|
||||||
span: start..end_before,
|
start: start as u32,
|
||||||
|
end: end_before as u32,
|
||||||
content: after[start..end_after].to_string(),
|
content: after[start..end_after].to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn span(&self) -> std::ops::Range<usize> {
|
||||||
|
self.start as usize .. self.end as usize
|
||||||
|
}
|
||||||
|
|
||||||
/// consume the [TextChange], transforming it into a Vec of [Op]
|
/// consume the [TextChange], transforming it into a Vec of [Op]
|
||||||
pub fn transform(self, woot: &Woot) -> WootResult<Vec<Op>> {
|
pub fn transform(self, woot: &Woot) -> WootResult<Vec<Op>> {
|
||||||
let mut out = Vec::new();
|
let mut out = Vec::new();
|
||||||
|
@ -77,19 +86,19 @@ impl TextChange {
|
||||||
return Ok(out);
|
return Ok(out);
|
||||||
} // no-op
|
} // no-op
|
||||||
let view = woot.view();
|
let view = woot.view();
|
||||||
let Some(span) = view.get(self.span.clone()) else {
|
let Some(span) = view.get(self.span()) else {
|
||||||
return Err(crate::woot::WootError::OutOfBounds);
|
return Err(crate::woot::WootError::OutOfBounds);
|
||||||
};
|
};
|
||||||
let diff = similar::TextDiff::from_chars(span, &self.content);
|
let diff = similar::TextDiff::from_chars(span, &self.content);
|
||||||
for (i, diff) in diff.iter_all_changes().enumerate() {
|
for (i, diff) in diff.iter_all_changes().enumerate() {
|
||||||
match diff.tag() {
|
match diff.tag() {
|
||||||
similar::ChangeTag::Equal => {}
|
similar::ChangeTag::Equal => {}
|
||||||
similar::ChangeTag::Delete => match woot.delete_one(self.span.start + i) {
|
similar::ChangeTag::Delete => match woot.delete_one(self.span().start + i) {
|
||||||
Err(e) => tracing::error!("could not create deletion: {}", e),
|
Err(e) => tracing::error!("could not create deletion: {}", e),
|
||||||
Ok(op) => out.push(Op(op)),
|
Ok(op) => out.push(Op(op)),
|
||||||
},
|
},
|
||||||
similar::ChangeTag::Insert => {
|
similar::ChangeTag::Insert => {
|
||||||
match woot.insert(self.span.start + i, diff.value()) {
|
match woot.insert(self.span().start + i, diff.value()) {
|
||||||
Ok(ops) => {
|
Ok(ops) => {
|
||||||
for op in ops {
|
for op in ops {
|
||||||
out.push(Op(op))
|
out.push(Op(op))
|
||||||
|
@ -105,7 +114,7 @@ impl TextChange {
|
||||||
|
|
||||||
/// returns true if this TextChange deletes existing text
|
/// returns true if this TextChange deletes existing text
|
||||||
pub fn is_deletion(&self) -> bool {
|
pub fn is_deletion(&self) -> bool {
|
||||||
!self.span.is_empty()
|
!self.span().is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// returns true if this TextChange adds new text
|
/// returns true if this TextChange adds new text
|
||||||
|
@ -120,9 +129,9 @@ impl TextChange {
|
||||||
|
|
||||||
/// applies this text change to given text, returning a new string
|
/// applies this text change to given text, returning a new string
|
||||||
pub fn apply(&self, txt: &str) -> String {
|
pub fn apply(&self, txt: &str) -> String {
|
||||||
let pre_index = std::cmp::min(self.span.start, txt.len());
|
let pre_index = std::cmp::min(self.span().start, txt.len());
|
||||||
let pre = txt.get(..pre_index).unwrap_or("").to_string();
|
let pre = txt.get(..pre_index).unwrap_or("").to_string();
|
||||||
let post = txt.get(self.span.end..).unwrap_or("").to_string();
|
let post = txt.get(self.span().end..).unwrap_or("").to_string();
|
||||||
format!("{}{}{}", pre, self.content, post)
|
format!("{}{}{}", pre, self.content, post)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,7 +153,7 @@ mod tests {
|
||||||
"sphinx of black quartz, judge my vow",
|
"sphinx of black quartz, judge my vow",
|
||||||
"sphinx of quartz, judge my vow",
|
"sphinx of quartz, judge my vow",
|
||||||
);
|
);
|
||||||
assert_eq!(change.span, 10..16);
|
assert_eq!(change.span(), 10..16);
|
||||||
assert_eq!(change.content, "");
|
assert_eq!(change.content, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,7 +163,7 @@ mod tests {
|
||||||
"sphinx of quartz, judge my vow",
|
"sphinx of quartz, judge my vow",
|
||||||
"sphinx of black quartz, judge my vow",
|
"sphinx of black quartz, judge my vow",
|
||||||
);
|
);
|
||||||
assert_eq!(change.span, 10..10);
|
assert_eq!(change.span(), 10..10);
|
||||||
assert_eq!(change.content, "black ");
|
assert_eq!(change.content, "black ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,14 +173,14 @@ mod tests {
|
||||||
"sphinx of black quartz, judge my vow",
|
"sphinx of black quartz, judge my vow",
|
||||||
"sphinx who watches the desert, judge my vow",
|
"sphinx who watches the desert, judge my vow",
|
||||||
);
|
);
|
||||||
assert_eq!(change.span, 7..22);
|
assert_eq!(change.span(), 7..22);
|
||||||
assert_eq!(change.content, "who watches the desert");
|
assert_eq!(change.content, "who watches the desert");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn textchange_apply_works_for_insertions() {
|
fn textchange_apply_works_for_insertions() {
|
||||||
let change = super::TextChange {
|
let change = super::TextChange {
|
||||||
span: 5..5,
|
start: 5, end: 5,
|
||||||
content: " cruel".to_string(),
|
content: " cruel".to_string(),
|
||||||
};
|
};
|
||||||
let result = change.apply("hello world!");
|
let result = change.apply("hello world!");
|
||||||
|
@ -181,7 +190,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn textchange_apply_works_for_deletions() {
|
fn textchange_apply_works_for_deletions() {
|
||||||
let change = super::TextChange {
|
let change = super::TextChange {
|
||||||
span: 5..11,
|
start: 5, end: 11,
|
||||||
content: "".to_string(),
|
content: "".to_string(),
|
||||||
};
|
};
|
||||||
let result = change.apply("hello cruel world!");
|
let result = change.apply("hello cruel world!");
|
||||||
|
@ -191,7 +200,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn textchange_apply_works_for_replacements() {
|
fn textchange_apply_works_for_replacements() {
|
||||||
let change = super::TextChange {
|
let change = super::TextChange {
|
||||||
span: 5..11,
|
start: 5, end: 11,
|
||||||
content: " not very pleasant".to_string(),
|
content: " not very pleasant".to_string(),
|
||||||
};
|
};
|
||||||
let result = change.apply("hello cruel world!");
|
let result = change.apply("hello cruel world!");
|
||||||
|
@ -201,7 +210,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn textchange_apply_never_panics() {
|
fn textchange_apply_never_panics() {
|
||||||
let change = super::TextChange {
|
let change = super::TextChange {
|
||||||
span: 100..110,
|
start: 100, end: 110,
|
||||||
content: "a very long string \n which totally matters".to_string(),
|
content: "a very long string \n which totally matters".to_string(),
|
||||||
};
|
};
|
||||||
let result = change.apply("a short text");
|
let result = change.apply("a short text");
|
||||||
|
@ -220,7 +229,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn empty_textchange_doesnt_alter_buffer() {
|
fn empty_textchange_doesnt_alter_buffer() {
|
||||||
let change = super::TextChange {
|
let change = super::TextChange {
|
||||||
span: 42..42,
|
start: 42, end: 42,
|
||||||
content: "".to_string(),
|
content: "".to_string(),
|
||||||
};
|
};
|
||||||
let result = change.apply("some important text");
|
let result = change.apply("some important text");
|
||||||
|
|
|
@ -22,6 +22,7 @@ use crate::api::TextChange;
|
||||||
/// upon dropping this handle will stop the associated worker
|
/// upon dropping this handle will stop the associated worker
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
#[cfg_attr(feature = "python", pyo3::pyclass)]
|
#[cfg_attr(feature = "python", pyo3::pyclass)]
|
||||||
|
#[cfg_attr(feature = "js", napi_derive::napi)]
|
||||||
pub struct BufferController(Arc<BufferControllerInner>);
|
pub struct BufferController(Arc<BufferControllerInner>);
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
|
@ -30,6 +30,7 @@ use codemp_proto::cursor::{CursorEvent, CursorPosition};
|
||||||
/// upon dropping this handle will stop the associated worker
|
/// upon dropping this handle will stop the associated worker
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
#[cfg_attr(feature = "python", pyo3::pyclass)]
|
#[cfg_attr(feature = "python", pyo3::pyclass)]
|
||||||
|
#[cfg_attr(feature = "js", napi_derive::napi)]
|
||||||
pub struct CursorController(Arc<CursorControllerInner>);
|
pub struct CursorController(Arc<CursorControllerInner>);
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
|
@ -1,56 +1,25 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use napi::threadsafe_function::{ErrorStrategy::Fatal, ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode};
|
use napi::threadsafe_function::{ErrorStrategy::Fatal, ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode};
|
||||||
use napi_derive::napi;
|
use napi_derive::napi;
|
||||||
use crate::api::Controller;
|
use crate::api::TextChange;
|
||||||
use crate::ffi::js::JsCodempError;
|
use crate::ffi::js::JsCodempError;
|
||||||
|
use crate::api::Controller;
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// BUFFER
|
|
||||||
#[napi(object)]
|
|
||||||
pub struct JsTextChange {
|
|
||||||
pub span: JsRange,
|
|
||||||
pub content: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[napi(object)]
|
|
||||||
pub struct JsRange{
|
|
||||||
pub start: i32,
|
|
||||||
pub end: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From::<crate::api::TextChange> for JsTextChange {
|
impl From<crate::Error> for napi::Error {
|
||||||
fn from(value: crate::api::TextChange) -> Self {
|
fn from(value: crate::Error) -> Self {
|
||||||
JsTextChange {
|
let msg = format!("{value}");
|
||||||
// TODO how is x.. represented ? span.end can never be None
|
match value {
|
||||||
span: JsRange { start: value.span.start as i32, end: value.span.end as i32 },
|
crate::Error::Deadlocked => napi::Error::new(napi::Status::WouldDeadlock, msg),
|
||||||
content: value.content,
|
_ => napi::Error::new(napi::Status::GenericFailure, msg),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl From::<Arc<crate::buffer::Controller>> for JsBufferController {
|
|
||||||
fn from(value: Arc<crate::buffer::Controller>) -> Self {
|
|
||||||
JsBufferController(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[napi]
|
#[napi]
|
||||||
pub struct JsBufferController(Arc<crate::buffer::Controller>);
|
impl CodempBufferController {
|
||||||
|
|
||||||
|
|
||||||
/*#[napi]
|
|
||||||
pub fn delta(string : String, start: i64, txt: String, end: i64 ) -> Option<JsCodempOperationSeq> {
|
|
||||||
Some(JsCodempOperationSeq(string.diff(start as usize, &txt, end as usize)?))
|
|
||||||
}*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[napi]
|
|
||||||
impl JsBufferController {
|
|
||||||
|
|
||||||
|
|
||||||
#[napi(ts_args_type = "fun: (event: JsTextChange) => void")]
|
#[napi(ts_args_type = "fun: (event: JsTextChange) => void")]
|
||||||
|
@ -58,10 +27,10 @@ impl JsBufferController {
|
||||||
let tsfn : ThreadsafeFunction<crate::api::TextChange, Fatal> =
|
let tsfn : ThreadsafeFunction<crate::api::TextChange, Fatal> =
|
||||||
fun.create_threadsafe_function(0,
|
fun.create_threadsafe_function(0,
|
||||||
|ctx : ThreadSafeCallContext<crate::api::TextChange>| {
|
|ctx : ThreadSafeCallContext<crate::api::TextChange>| {
|
||||||
Ok(vec![JsTextChange::from(ctx.value)])
|
Ok(vec![ctx.value])
|
||||||
}
|
}
|
||||||
)?;
|
)?;
|
||||||
let _controller = self.0.clone();
|
let _controller = self.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
//tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
//tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||||
loop {
|
loop {
|
||||||
|
@ -79,32 +48,13 @@ impl JsBufferController {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[napi]
|
#[napi(js_name = "recv")]
|
||||||
pub fn content(&self) -> String {
|
pub async fn jsrecv(&self) -> napi::Result<TextChange> {
|
||||||
self.0.content()
|
Ok(self.recv().await?.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi]
|
#[napi]
|
||||||
pub fn get_name(&self) -> String {
|
pub fn send(&self, op: TextChange) -> napi::Result<()> {
|
||||||
self.0.name().to_string()
|
Ok(self.send(op)?)
|
||||||
}
|
|
||||||
|
|
||||||
#[napi]
|
|
||||||
pub async fn recv(&self) -> napi::Result<JsTextChange> {
|
|
||||||
Ok(
|
|
||||||
self.0.recv().await
|
|
||||||
.map_err(|e| napi::Error::from(JsCodempError(e)))?
|
|
||||||
.into()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[napi]
|
|
||||||
pub fn send(&self, op: JsTextChange) -> napi::Result<()> {
|
|
||||||
// TODO might be nice to take ownership of the opseq
|
|
||||||
let new_text_change = crate::api::TextChange {
|
|
||||||
span: op.span.start as usize .. op.span.end as usize,
|
|
||||||
content: op.content,
|
|
||||||
};
|
|
||||||
Ok(self.0.send(new_text_change).map_err(JsCodempError)?)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
use napi_derive::napi;
|
use napi_derive::napi;
|
||||||
use crate::ffi::js::JsCodempError;
|
use crate::ffi::js::JsCodempError;
|
||||||
use crate::ffi::js::workspace::JsWorkspace;
|
|
||||||
|
|
||||||
#[napi]
|
#[napi]
|
||||||
/// main codemp client session
|
/// main codemp client session
|
||||||
|
@ -10,8 +9,7 @@ pub struct JsCodempClient(tokio::sync::RwLock<crate::Client>);
|
||||||
/// connect to codemp servers and return a client session
|
/// connect to codemp servers and return a client session
|
||||||
pub async fn connect(addr: Option<String>) -> napi::Result<JsCodempClient>{
|
pub async fn connect(addr: Option<String>) -> napi::Result<JsCodempClient>{
|
||||||
let client = crate::Client::new(addr.as_deref().unwrap_or("http://codemp.alemi.dev:50053"))
|
let client = crate::Client::new(addr.as_deref().unwrap_or("http://codemp.alemi.dev:50053"))
|
||||||
.await
|
.await?;
|
||||||
.map_err(JsCodempError)?;
|
|
||||||
|
|
||||||
Ok(JsCodempClient(tokio::sync::RwLock::new(client)))
|
Ok(JsCodempClient(tokio::sync::RwLock::new(client)))
|
||||||
}
|
}
|
||||||
|
@ -21,14 +19,14 @@ impl JsCodempClient {
|
||||||
#[napi]
|
#[napi]
|
||||||
/// login against AuthService with provided credentials, optionally requesting access to a workspace
|
/// login against AuthService with provided credentials, optionally requesting access to a workspace
|
||||||
pub async fn login(&self, username: String, password: String, workspace_id: Option<String>) -> napi::Result<()> {
|
pub async fn login(&self, username: String, password: String, workspace_id: Option<String>) -> napi::Result<()> {
|
||||||
self.0.read().await.login(username, password, workspace_id).await.map_err(JsCodempError)?;
|
self.0.read().await.login(username, password, workspace_id).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi]
|
#[napi]
|
||||||
/// join workspace with given id (will start its cursor controller)
|
/// join workspace with given id (will start its cursor controller)
|
||||||
pub async fn join_workspace(&self, workspace: String) -> napi::Result<JsWorkspace> {
|
pub async fn join_workspace(&self, workspace: String) -> napi::Result<JsWorkspace> {
|
||||||
Ok(JsWorkspace::from(self.0.write().await.join_workspace(&workspace).await.map_err(JsCodempError)?))
|
Ok(JsWorkspace::from(self.0.write().await.join_workspace(&workspace).await?))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi]
|
#[napi]
|
||||||
|
|
|
@ -3,34 +3,64 @@ use napi_derive::napi;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use napi::threadsafe_function::{ThreadsafeFunction, ThreadSafeCallContext, ThreadsafeFunctionCallMode, ErrorStrategy};
|
use napi::threadsafe_function::{ThreadsafeFunction, ThreadSafeCallContext, ThreadsafeFunctionCallMode, ErrorStrategy};
|
||||||
use crate::api::Controller;
|
use crate::api::Controller;
|
||||||
use crate::ffi::js::JsCodempError;
|
use crate::prelude::*;
|
||||||
|
|
||||||
#[napi]
|
|
||||||
pub struct JsCursorController(Arc<crate::cursor::Controller>);
|
|
||||||
|
|
||||||
impl From::<Arc<crate::cursor::Controller>> for JsCursorController {
|
|
||||||
fn from(value: Arc<crate::cursor::Controller>) -> Self {
|
|
||||||
JsCursorController(value)
|
#[napi(js_name = "Cursor")]
|
||||||
|
pub struct JsCursor {
|
||||||
|
/// range of text change, as char indexes in buffer previous state
|
||||||
|
pub start_row: i32,
|
||||||
|
pub start_col: i32,
|
||||||
|
pub end_row: i32,
|
||||||
|
pub end_col: i32,
|
||||||
|
pub buffer: String,
|
||||||
|
pub user: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<JsCursor> for CodempCursor {
|
||||||
|
fn from(value: JsCursor) -> Self {
|
||||||
|
CodempCursor {
|
||||||
|
start : (value.start_row, value.start_col),
|
||||||
|
end: (value.end_row, value.end_col),
|
||||||
|
buffer: value.buffer,
|
||||||
|
user: value.user.map(|x| uuid::Uuid::parse_str(&x).expect("invalid uuid")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<CodempCursor> for JsCursor {
|
||||||
|
fn from(value: CodempCursor) -> Self {
|
||||||
|
JsCursor {
|
||||||
|
start_row : value.start.0,
|
||||||
|
start_col : value.start.1,
|
||||||
|
end_row : value.end.0,
|
||||||
|
end_col: value.end.1,
|
||||||
|
buffer: value.buffer,
|
||||||
|
user: value.user.map(|x| x.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[napi]
|
#[napi]
|
||||||
impl JsCursorController {
|
impl CodempCursorController {
|
||||||
|
|
||||||
#[napi(ts_args_type = "fun: (event: JsCursorEvent) => void")]
|
#[napi(ts_args_type = "fun: (event: JsCursorEvent) => void")]
|
||||||
pub fn callback(&self, fun: napi::JsFunction) -> napi::Result<()>{
|
pub fn callback(&self, fun: napi::JsFunction) -> napi::Result<()>{
|
||||||
let tsfn : ThreadsafeFunction<codemp_proto::cursor::CursorEvent, ErrorStrategy::Fatal> =
|
let tsfn : ThreadsafeFunction<JsCursor, ErrorStrategy::Fatal> =
|
||||||
fun.create_threadsafe_function(0,
|
fun.create_threadsafe_function(0,
|
||||||
|ctx : ThreadSafeCallContext<codemp_proto::cursor::CursorEvent>| {
|
|ctx : ThreadSafeCallContext<JsCursor>| {
|
||||||
Ok(vec![JsCursorEvent::from(ctx.value)])
|
Ok(vec![ctx.value])
|
||||||
}
|
}
|
||||||
)?;
|
)?;
|
||||||
let _controller = self.0.clone();
|
let _controller = self.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
loop {
|
loop {
|
||||||
match _controller.recv().await {
|
match _controller.recv().await {
|
||||||
Ok(event) => {
|
Ok(event) => {
|
||||||
tsfn.call(event.clone(), ThreadsafeFunctionCallMode::NonBlocking); //check this shit with tracing also we could use Ok(event) to get the error
|
tsfn.call(event.into(), ThreadsafeFunctionCallMode::NonBlocking); //check this shit with tracing also we could use Ok(event) to get the error
|
||||||
},
|
},
|
||||||
Err(crate::Error::Deadlocked) => continue,
|
Err(crate::Error::Deadlocked) => continue,
|
||||||
Err(e) => break tracing::warn!("error receiving: {}", e),
|
Err(e) => break tracing::warn!("error receiving: {}", e),
|
||||||
|
@ -41,13 +71,8 @@ impl JsCursorController {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi]
|
#[napi]
|
||||||
pub fn send(&self, buffer: String, start: (i32, i32), end: (i32, i32)) -> napi::Result<()> {
|
pub fn send(&self, pos: &CodempCursorController) -> napi::Result<()> {
|
||||||
let pos = codemp_proto::cursor::CursorPosition {
|
Ok(self.send(pos)?)
|
||||||
buffer: buffer.into(),
|
|
||||||
start: codemp_proto::cursor::RowCol::from(start),
|
|
||||||
end: codemp_proto::cursor::RowCol::from(end),
|
|
||||||
};
|
|
||||||
Ok(self.0.send(pos).map_err(JsCodempError)?)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,51 +1,39 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use napi_derive::napi;
|
use napi_derive::napi;
|
||||||
|
use crate::prelude::*;
|
||||||
use crate::ffi::js::{JsCodempError, buffer::JsBufferController, cursor::JsCursorController};
|
|
||||||
|
|
||||||
|
|
||||||
#[napi]
|
#[napi]
|
||||||
/// a reference to a codemp workspace
|
impl CodempWorkspace {
|
||||||
pub struct JsWorkspace(Arc<crate::Workspace>);
|
#[napi(js_name = "id")]
|
||||||
|
pub fn js_id(&self) -> String {
|
||||||
impl From<Arc<crate::Workspace>> for JsWorkspace {
|
self.id()
|
||||||
fn from(value: Arc<crate::Workspace>) -> Self {
|
|
||||||
JsWorkspace(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[napi]
|
|
||||||
impl JsWorkspace {
|
|
||||||
|
|
||||||
#[napi]
|
|
||||||
pub fn id(&self) -> String {
|
|
||||||
self.0.id()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi]
|
#[napi(js_name = "filetree")]
|
||||||
pub fn filetree(&self) -> Vec<String> {
|
pub fn js_filetree(&self) -> Vec<String> {
|
||||||
self.0.filetree()
|
self.filetree()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi]
|
#[napi(js_name = "cursor")]
|
||||||
pub fn cursor(&self) -> JsCursorController {
|
pub fn js_cursor(&self) -> CodempCursorController {
|
||||||
JsCursorController::from(self.0.cursor())
|
self.cursor()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi]
|
#[napi(js_name = "buffer_by_name")]
|
||||||
pub fn buffer_by_name(&self, path: String) -> Option<JsBufferController> {
|
pub fn js_buffer_by_name(&self, path: String) -> Option<CodempBufferController> {
|
||||||
self.0.buffer_by_name(&path).map(|b| JsBufferController::from(b))
|
self.buffer_by_name(&path)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi]
|
#[napi(js_name = "create")]
|
||||||
pub async fn create(&self, path: String) -> napi::Result<()> {
|
pub async fn js_create(&self, path: String) -> napi::Result<()> {
|
||||||
Ok(self.0.create(&path).await.map_err(JsCodempError)?)
|
Ok(self.create(&path).await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi]
|
#[napi(js_name = "attach")]
|
||||||
pub async fn attach(&self, path: String) -> napi::Result<JsBufferController> {
|
pub async fn js_attach(&self, path: String) -> napi::Result<CodempBufferController> {
|
||||||
Ok(JsBufferController::from(self.0.attach(&path).await.map_err(JsCodempError)?))
|
Ok(self.attach(&path).await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*#[napi]
|
/*#[napi]
|
||||||
|
|
|
@ -21,6 +21,9 @@ use tokio::sync::mpsc;
|
||||||
use tonic::Streaming;
|
use tonic::Streaming;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
#[cfg(feature = "js")]
|
||||||
|
use napi_derive::napi;
|
||||||
|
|
||||||
//TODO may contain more info in the future
|
//TODO may contain more info in the future
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct UserInfo {
|
pub struct UserInfo {
|
||||||
|
@ -29,6 +32,7 @@ pub struct UserInfo {
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
#[cfg_attr(feature = "python", pyo3::pyclass)]
|
#[cfg_attr(feature = "python", pyo3::pyclass)]
|
||||||
|
#[cfg_attr(feature = "js", napi)]
|
||||||
pub struct Workspace(Arc<WorkspaceInner>);
|
pub struct Workspace(Arc<WorkspaceInner>);
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -227,27 +231,32 @@ impl Workspace {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// get the id of the workspace
|
/// get the id of the workspace
|
||||||
|
// #[cfg_attr(feature = "js", napi)] // https://github.com/napi-rs/napi-rs/issues/1120
|
||||||
pub fn id(&self) -> String {
|
pub fn id(&self) -> String {
|
||||||
self.0.id.clone()
|
self.0.id.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// return a reference to current cursor controller, if currently in a workspace
|
/// return a reference to current cursor controller, if currently in a workspace
|
||||||
|
// #[cfg_attr(feature = "js", napi)] // https://github.com/napi-rs/napi-rs/issues/1120
|
||||||
pub fn cursor(&self) -> cursor::Controller {
|
pub fn cursor(&self) -> cursor::Controller {
|
||||||
self.0.cursor.clone()
|
self.0.cursor.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// get a new reference to a buffer controller, if any is active to given path
|
/// get a new reference to a buffer controller, if any is active to given path
|
||||||
|
// #[cfg_attr(feature = "js", napi)] // https://github.com/napi-rs/napi-rs/issues/1120
|
||||||
pub fn buffer_by_name(&self, path: &str) -> Option<buffer::Controller> {
|
pub fn buffer_by_name(&self, path: &str) -> Option<buffer::Controller> {
|
||||||
self.0.buffers.get(path).map(|x| x.clone())
|
self.0.buffers.get(path).map(|x| x.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// get a list of all the currently attached to buffers
|
/// get a list of all the currently attached to buffers
|
||||||
|
// #[cfg_attr(feature = "js", napi)] // https://github.com/napi-rs/napi-rs/issues/1120
|
||||||
pub fn buffer_list(&self) -> Vec<String> {
|
pub fn buffer_list(&self) -> Vec<String> {
|
||||||
self.0.buffers.iter().map(|elem| elem.key().clone()).collect()
|
self.0.buffers.iter().map(|elem| elem.key().clone()).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// get the currently cached "filetree"
|
/// get the currently cached "filetree"
|
||||||
|
// #[cfg_attr(feature = "js", napi)] // https://github.com/napi-rs/napi-rs/issues/1120
|
||||||
pub fn filetree(&self) -> Vec<String> {
|
pub fn filetree(&self) -> Vec<String> {
|
||||||
self.0.filetree.iter().map(|f| f.clone()).collect()
|
self.0.filetree.iter().map(|f| f.clone()).collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in a new issue