mirror of
https://github.com/hexedtech/codemp-vscode.git
synced 2024-12-23 05:54:51 +01:00
feat: removed rust from extension and added to glue
This commit is contained in:
parent
a45f5fbaa0
commit
26f2c3b836
8 changed files with 0 additions and 507 deletions
29
Cargo.toml
29
Cargo.toml
|
@ -1,29 +0,0 @@
|
|||
[package]
|
||||
name = "codemp-vscode"
|
||||
version = "0.0.1"
|
||||
description = "VSCode extension for CodeMP"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
path = "src/rust/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
codemp = { git = "ssh://git@github.com/codewithotherpeopleandchangenamelater/codemp.git", tag="v0.6.0", features = ["client", "woot"] }
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = "0.3.17"
|
||||
uuid = { version = "1.3.1", features = ["v4"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
rmpv = "1"
|
||||
async-trait = "0.1.68"
|
||||
napi = { version = "2", features = ["full"] }
|
||||
napi-derive = "2"
|
||||
futures = "0.3.28"
|
||||
tokio = {version = "1.32.0", features = ["full"] }
|
||||
|
||||
[build-dependencies]
|
||||
napi-build = "2"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
5
build.rs
5
build.rs
|
@ -1,5 +0,0 @@
|
|||
extern crate napi_build;
|
||||
|
||||
fn main() {
|
||||
napi_build::setup();
|
||||
}
|
|
@ -1,110 +0,0 @@
|
|||
use std::sync::Arc;
|
||||
use napi::threadsafe_function::{ErrorStrategy::Fatal, ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode};
|
||||
use napi_derive::napi;
|
||||
use codemp::api::Controller;
|
||||
use crate::JsCodempError;
|
||||
|
||||
/// 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::<codemp::api::TextChange> for JsTextChange {
|
||||
fn from(value: codemp::api::TextChange) -> Self {
|
||||
JsTextChange {
|
||||
// TODO how is x.. represented ? span.end can never be None
|
||||
span: JsRange { start: value.span.start as i32, end: value.span.end as i32 },
|
||||
content: value.content,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl From::<Arc<codemp::buffer::Controller>> for JsBufferController {
|
||||
fn from(value: Arc<codemp::buffer::Controller>) -> Self {
|
||||
JsBufferController(value)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[napi]
|
||||
pub struct JsBufferController(Arc<codemp::buffer::Controller>);
|
||||
|
||||
|
||||
/*#[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")]
|
||||
pub fn callback(&self, fun: napi::JsFunction) -> napi::Result<()>{
|
||||
let tsfn : ThreadsafeFunction<codemp::api::TextChange, Fatal> =
|
||||
fun.create_threadsafe_function(0,
|
||||
|ctx : ThreadSafeCallContext<codemp::api::TextChange>| {
|
||||
Ok(vec![JsTextChange::from(ctx.value)])
|
||||
}
|
||||
)?;
|
||||
let _controller = self.0.clone();
|
||||
tokio::spawn(async move {
|
||||
//tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
loop {
|
||||
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
|
||||
match _controller.recv().await {
|
||||
Ok(event) => {
|
||||
tsfn.call(event, ThreadsafeFunctionCallMode::NonBlocking); //check this shit with tracing also we could use Ok(event) to get the error
|
||||
},
|
||||
Err(codemp::Error::Deadlocked) => continue,
|
||||
Err(e) => break tracing::warn!("error receiving: {}", e),
|
||||
}
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
#[napi]
|
||||
pub fn content(&self) -> String {
|
||||
self.0.content()
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn get_name(&self) -> String {
|
||||
self.0.name().to_string()
|
||||
}
|
||||
|
||||
#[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 = codemp::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,45 +0,0 @@
|
|||
use napi_derive::napi;
|
||||
use crate::JsCodempError;
|
||||
use crate::workspace::JsWorkspace;
|
||||
|
||||
#[napi]
|
||||
/// main codemp client session
|
||||
pub struct JsCodempClient(tokio::sync::RwLock<codemp::Client>);
|
||||
|
||||
#[napi]
|
||||
/// connect to codemp servers and return a client session
|
||||
pub async fn connect(addr: Option<String>) -> napi::Result<JsCodempClient>{
|
||||
let client = codemp::Client::new(addr.as_deref().unwrap_or("http://codemp.alemi.dev:50053"))
|
||||
.await
|
||||
.map_err(JsCodempError)?;
|
||||
|
||||
Ok(JsCodempClient(tokio::sync::RwLock::new(client)))
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl JsCodempClient {
|
||||
#[napi]
|
||||
/// 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<()> {
|
||||
self.0.read().await.login(username, password, workspace_id).await.map_err(JsCodempError)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[napi]
|
||||
/// join workspace with given id (will start its cursor controller)
|
||||
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)?))
|
||||
}
|
||||
|
||||
#[napi]
|
||||
/// get workspace with given id, if it exists
|
||||
pub async fn get_workspace(&self, workspace: String) -> Option<JsWorkspace> {
|
||||
self.0.read().await.get_workspace(&workspace).map(|w| JsWorkspace::from(w))
|
||||
}
|
||||
|
||||
#[napi]
|
||||
/// return current sessions's user id
|
||||
pub async fn user_id(&self) -> String {
|
||||
self.0.read().await.user_id().to_string()
|
||||
}
|
||||
}
|
|
@ -1,90 +0,0 @@
|
|||
use std::sync::Arc;
|
||||
use napi_derive::napi;
|
||||
use uuid::Uuid;
|
||||
use napi::threadsafe_function::{ThreadsafeFunction, ThreadSafeCallContext, ThreadsafeFunctionCallMode, ErrorStrategy};
|
||||
use codemp::api::Controller;
|
||||
use crate::JsCodempError;
|
||||
|
||||
#[napi]
|
||||
pub struct JsCursorController(Arc<codemp::cursor::Controller>);
|
||||
|
||||
impl From::<Arc<codemp::cursor::Controller>> for JsCursorController {
|
||||
fn from(value: Arc<codemp::cursor::Controller>) -> Self {
|
||||
JsCursorController(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl JsCursorController {
|
||||
|
||||
#[napi(ts_args_type = "fun: (event: JsCursorEvent) => void")]
|
||||
pub fn callback(&self, fun: napi::JsFunction) -> napi::Result<()>{
|
||||
let tsfn : ThreadsafeFunction<codemp::proto::cursor::CursorEvent, ErrorStrategy::Fatal> =
|
||||
fun.create_threadsafe_function(0,
|
||||
|ctx : ThreadSafeCallContext<codemp::proto::cursor::CursorEvent>| {
|
||||
Ok(vec![JsCursorEvent::from(ctx.value)])
|
||||
}
|
||||
)?;
|
||||
let _controller = self.0.clone();
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
match _controller.recv().await {
|
||||
Ok(event) => {
|
||||
tsfn.call(event.clone(), ThreadsafeFunctionCallMode::NonBlocking); //check this shit with tracing also we could use Ok(event) to get the error
|
||||
},
|
||||
Err(codemp::Error::Deadlocked) => continue,
|
||||
Err(e) => break tracing::warn!("error receiving: {}", e),
|
||||
}
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn send(&self, buffer: String, start: (i32, i32), end: (i32, i32)) -> napi::Result<()> {
|
||||
let pos = codemp::proto::cursor::CursorPosition {
|
||||
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)?)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
#[napi(object)]
|
||||
pub struct JsCursorEvent {
|
||||
pub user: String,
|
||||
pub buffer: String,
|
||||
pub start: JsRowCol,
|
||||
pub end: JsRowCol,
|
||||
}
|
||||
|
||||
impl From::<codemp::proto::cursor::CursorEvent> for JsCursorEvent {
|
||||
fn from(value: codemp::proto::cursor::CursorEvent) -> Self {
|
||||
let pos = value.position;
|
||||
let start = pos.start;
|
||||
let end = pos.end;
|
||||
JsCursorEvent {
|
||||
user: Uuid::from(value.user).to_string(),
|
||||
buffer: pos.buffer.into(),
|
||||
start: JsRowCol { row: start.row, col: start.col },
|
||||
end: JsRowCol { row: end.row, col: end.col },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[napi(object)]
|
||||
pub struct JsRowCol {
|
||||
pub row: i32,
|
||||
pub col: i32
|
||||
}
|
||||
|
||||
impl From::<codemp::proto::cursor::RowCol> for JsRowCol {
|
||||
fn from(value: codemp::proto::cursor::RowCol) -> Self {
|
||||
JsRowCol { row: value.row, col: value.col }
|
||||
}
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
#![deny(clippy::all)]
|
||||
|
||||
pub mod client;
|
||||
pub mod workspace;
|
||||
pub mod cursor;
|
||||
pub mod buffer;
|
||||
pub mod op_cache;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct JsCodempError(codemp::Error);
|
||||
|
||||
impl From::<JsCodempError> for napi::Error {
|
||||
fn from(value: JsCodempError) -> Self {
|
||||
napi::Error::new(napi::Status::GenericFailure, &format!("CodempError: {:?}", value))
|
||||
}
|
||||
}
|
||||
|
||||
use napi_derive::napi;
|
||||
|
||||
#[napi]
|
||||
pub struct JsLogger(std::sync::Arc<tokio::sync::Mutex<tokio::sync::mpsc::Receiver<String>>>);
|
||||
|
||||
#[napi]
|
||||
impl JsLogger {
|
||||
#[napi(constructor)]
|
||||
pub fn new(debug: Option<bool>) -> JsLogger {
|
||||
let (tx, rx) = tokio::sync::mpsc::channel(256);
|
||||
let level = if debug.unwrap_or(false) { tracing::Level::DEBUG } else {tracing::Level::INFO }; //TODO: study this tracing subscriber and customize it
|
||||
let format = tracing_subscriber::fmt::format()
|
||||
.with_level(true)
|
||||
.with_target(true)
|
||||
.with_thread_ids(false)
|
||||
.with_thread_names(false)
|
||||
.with_ansi(false)
|
||||
.with_file(false)
|
||||
.with_line_number(false)
|
||||
.with_source_location(false)
|
||||
.compact();
|
||||
tracing_subscriber::fmt()
|
||||
.event_format(format)
|
||||
.with_max_level(level)
|
||||
.with_writer(std::sync::Mutex::new(JsLoggerProducer(tx)))
|
||||
.init();
|
||||
JsLogger(std::sync::Arc::new(tokio::sync::Mutex::new(rx)))
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub async fn message(&self) -> Option<String> {
|
||||
self.0
|
||||
.lock()
|
||||
.await
|
||||
.recv()
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct JsLoggerProducer(tokio::sync::mpsc::Sender<String>);
|
||||
|
||||
impl std::io::Write for JsLoggerProducer {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
// TODO this is a LOSSY logger!!
|
||||
let _ = self.0.try_send(String::from_utf8_lossy(buf).to_string()); // ignore: logger disconnected or with full buffer
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> { Ok(()) }
|
||||
}
|
|
@ -1,104 +0,0 @@
|
|||
use std::collections::HashMap;
|
||||
use napi_derive::napi;
|
||||
|
||||
pub type OpTuple = (String, u32, String, u32); // buf_path, start, text, end
|
||||
|
||||
#[napi]
|
||||
pub struct OpCache {
|
||||
store: HashMap<OpTuple, i32>
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl OpCache {
|
||||
#[napi(constructor)]
|
||||
pub fn new() -> Self {
|
||||
OpCache {
|
||||
store: HashMap::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn to_string(&self) -> String {
|
||||
self.store.iter()
|
||||
.map(|(k, v)| format!("{}x Op(@{} {}:{} '{}')", k.0, v, k.1, k.3, k.2))
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn put(&mut self, buf: String, start: u32, text: String, end: u32) -> i32 {
|
||||
let op = (buf, start, text, end);
|
||||
match self.store.get_mut(&op) {
|
||||
Some(val) => {
|
||||
if *val < 0 { *val = 0 }
|
||||
*val += 1;
|
||||
*val
|
||||
},
|
||||
None => {
|
||||
self.store.insert(op, 1);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn get(&mut self, buf: String, start: u32, text: String, end: u32) -> bool {
|
||||
let op = (buf, start, text, end);
|
||||
match self.store.get_mut(&op) {
|
||||
Some(val) => {
|
||||
*val -= 1;
|
||||
*val >= 0
|
||||
}
|
||||
None => {
|
||||
tracing::warn!("never seen this op: {:?}", op);
|
||||
self.store.insert(op, -1);
|
||||
false
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
//a
|
||||
//consume a
|
||||
//a
|
||||
|
||||
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[test]
|
||||
fn opcache_put_increments_internal_counter() {
|
||||
let mut op = super::OpCache::new();
|
||||
assert_eq!(op.put("default".into(), 0, "hello world".into(), 0), 1); // 1: did not already contain it
|
||||
assert_eq!(op.put("default".into(), 0, "hello world".into(), 0), 2); // 2: already contained it
|
||||
}
|
||||
#[test]
|
||||
fn op_cache_get_checks_count() {
|
||||
let mut op = super::OpCache::new();
|
||||
assert_eq!(op.get("default".into(), 0, "hello world".into(), 0), false);
|
||||
assert_eq!(op.put("default".into(), 0, "hello world".into(), 0), 1);
|
||||
assert_eq!(op.get("default".into(), 0, "hello world".into(), 0), true);
|
||||
assert_eq!(op.get("default".into(), 0, "hello world".into(), 0), false);
|
||||
}
|
||||
#[test]
|
||||
fn op_cache_get_works_for_multiple_puts() {
|
||||
let mut op = super::OpCache::new();
|
||||
assert_eq!(op.get("default".into(), 0, "hello world".into(), 0), false);
|
||||
assert_eq!(op.put("default".into(), 0, "hello world".into(), 0), 1);
|
||||
assert_eq!(op.put("default".into(), 0, "hello world".into(), 0), 2);
|
||||
assert_eq!(op.get("default".into(), 0, "hello world".into(), 0), true);
|
||||
assert_eq!(op.get("default".into(), 0, "hello world".into(), 0), true);
|
||||
assert_eq!(op.get("default".into(), 0, "hello world".into(), 0), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn op_cache_different_keys(){
|
||||
let mut op = super::OpCache::new();
|
||||
assert_eq!(op.get("default".into(), 0, "hello world".into(), 0), false);
|
||||
assert_eq!(op.put("default".into(), 0, "hello world".into(), 0), 1);
|
||||
assert_eq!(op.get("workspace".into(), 0, "hi".into(), 0), false);
|
||||
assert_eq!(op.put("workspace".into(), 0, "hi".into(), 0), 1);
|
||||
assert_eq!(op.get("workspace".into(), 0, "hi".into(), 0), true);
|
||||
assert_eq!(op.get("default".into(), 0, "hello world".into(), 0), true);
|
||||
}
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use napi_derive::napi;
|
||||
|
||||
use crate::{JsCodempError, buffer::JsBufferController, cursor::JsCursorController};
|
||||
|
||||
|
||||
#[napi]
|
||||
/// a reference to a codemp workspace
|
||||
pub struct JsWorkspace(Arc<codemp::Workspace>);
|
||||
|
||||
impl From<Arc<codemp::Workspace>> for JsWorkspace {
|
||||
fn from(value: Arc<codemp::Workspace>) -> Self {
|
||||
JsWorkspace(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl JsWorkspace {
|
||||
|
||||
#[napi]
|
||||
pub fn id(&self) -> String {
|
||||
self.0.id()
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn filetree(&self) -> Vec<String> {
|
||||
self.0.filetree()
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn cursor(&self) -> JsCursorController {
|
||||
JsCursorController::from(self.0.cursor())
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn buffer_by_name(&self, path: String) -> Option<JsBufferController> {
|
||||
self.0.buffer_by_name(&path).map(|b| JsBufferController::from(b))
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub async fn create(&self, path: String) -> napi::Result<()> {
|
||||
Ok(self.0.create(&path).await.map_err(JsCodempError)?)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub async fn attach(&self, path: String) -> napi::Result<JsBufferController> {
|
||||
Ok(JsBufferController::from(self.0.attach(&path).await.map_err(JsCodempError)?))
|
||||
}
|
||||
|
||||
/*#[napi]
|
||||
pub async fn delete(&self, path: String) -> napi::Result<>{
|
||||
self.0.delete(&path)
|
||||
}*/
|
||||
|
||||
}
|
Loading…
Reference in a new issue