diff --git a/src/api/change.rs b/src/api/change.rs index b5ece0d..9575614 100644 --- a/src/api/change.rs +++ b/src/api/change.rs @@ -3,7 +3,13 @@ //! an editor-friendly representation of a text change in a buffer //! to easily interface with codemp from various editors -use crate::woot::{WootResult, woot::Woot, crdt::{TextEditor, CRDT, Op}}; +use crate::woot::{WootResult, woot::Woot, crdt::{TextEditor, CRDT}}; + +/// an atomic and orderable operation +/// +/// this under the hood thinly wraps our CRDT operation +#[derive(Debug, Clone)] +pub struct Op(pub(crate) woot::crdt::Op); /// an editor-friendly representation of a text change in a buffer /// @@ -59,7 +65,7 @@ impl TextChange { } } - /// consume the [TextChange], transforming it into a Vec of [woot::crdt::Op] + /// consume the [TextChange], transforming it into a Vec of [Op] pub fn transform(self, woot: &Woot) -> WootResult> { let mut out = Vec::new(); if self.is_empty() { return Ok(out); } // no-op @@ -73,11 +79,11 @@ impl TextChange { similar::ChangeTag::Equal => {}, similar::ChangeTag::Delete => match woot.delete_one(self.span.start + i) { Err(e) => tracing::error!("could not create deletion: {}", e), - Ok(op) => out.push(op), + Ok(op) => out.push(Op(op)), }, similar::ChangeTag::Insert => { match woot.insert(self.span.start + i, diff.value()) { - Ok(mut op) => out.append(&mut op), + Ok(ops) => for op in ops { out.push(Op(op)) }, Err(e) => tracing::error!("could not create insertion: {}", e), } }, diff --git a/src/api/cursor.rs b/src/api/cursor.rs new file mode 100644 index 0000000..bc7963b --- /dev/null +++ b/src/api/cursor.rs @@ -0,0 +1,63 @@ +//! # Cursor +//! +//! represents the position of an user's cursor, with +//! information about their identity + +use uuid::Uuid; +use codemp_proto as proto; + +/// user cursor position in a buffer +#[derive(Clone, Debug, Default)] +pub struct Cursor { + /// range of text change, as char indexes in buffer previous state + pub start: (i32, i32), + pub end: (i32, i32), + pub buffer: String, + pub user: Option, +} + + +impl From for Cursor { + fn from(value: proto::cursor::CursorPosition) -> Self { + Self { + start: (value.start.row, value.start.col), + end: (value.end.row, value.end.col), + buffer: value.buffer.path, + user: None, + } + } +} + +impl From for proto::cursor::CursorPosition { + fn from(value: Cursor) -> Self { + Self { + buffer: proto::files::BufferNode { path: value.buffer }, + start: proto::cursor::RowCol { row: value.start.0, col: value.start.1 }, + end: proto::cursor::RowCol { row: value.end.0, col: value.end.1 }, + } + } +} + +impl From for Cursor { + fn from(value: proto::cursor::CursorEvent) -> Self { + Self { + start: (value.position.start.row, value.position.start.col), + end: (value.position.end.row, value.position.end.col), + buffer: value.position.buffer.path, + user: Uuid::parse_str(&value.user.id).ok(), + } + } +} + +impl From for proto::cursor::CursorEvent { + fn from(value: Cursor) -> Self { + Self { + user: proto::common::Identity { id: value.user.unwrap_or_default().to_string() }, + position: proto::cursor::CursorPosition { + buffer: proto::files::BufferNode { path: value.buffer }, + start: proto::cursor::RowCol { row: value.start.0, col: value.start.1 }, + end: proto::cursor::RowCol { row: value.end.0, col: value.end.1 }, + } + } + } +} diff --git a/src/api/mod.rs b/src/api/mod.rs index fcb5f3e..79dba09 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -10,5 +10,10 @@ pub mod controller; /// a generic representation of a text change pub mod change; +/// representation for an user's cursor +pub mod cursor; + pub use controller::Controller; pub use change::TextChange; +pub use change::Op; +pub use cursor::Cursor; diff --git a/src/buffer/worker.rs b/src/buffer/worker.rs index 297c3e1..c38efd2 100644 --- a/src/buffer/worker.rs +++ b/src/buffer/worker.rs @@ -4,7 +4,7 @@ use std::hash::{Hash, Hasher}; use tokio::sync::{watch, mpsc, oneshot}; use tonic::{async_trait, Streaming}; use uuid::Uuid; -use woot::crdt::{Op, CRDT}; +use woot::crdt::CRDT; use woot::woot::Woot; use crate::errors::IgnorableError; @@ -99,12 +99,12 @@ impl ControllerWorker for BufferWorker { Err(e) => break tracing::error!("could not apply operation from client: {}", e), Ok(ops) => { for op in ops { - self.buffer.merge(op.clone()); + self.buffer.merge(op.0.clone()); let operation = Operation { - data: postcard::to_extend(&op, Vec::new()).unwrap(), + data: postcard::to_extend(&op.0, Vec::new()).unwrap(), }; if let Err(e) = tx.send(operation).await { - tracing::error!("server refused to broadcast {}: {}", op, e); + tracing::error!("server refused to broadcast {}: {}", op.0, e); } } self.content.send(self.buffer.view()) @@ -117,7 +117,7 @@ impl ControllerWorker for BufferWorker { res = rx.message() => match res { Err(_e) => break, Ok(None) => break, - Ok(Some(change)) => match postcard::from_bytes::(&change.op.data) { + Ok(Some(change)) => match postcard::from_bytes::(&change.op.data) { Ok(op) => { // TODO here in change we receive info about the author, maybe propagate? self.buffer.merge(op); self.content.send(self.buffer.view()).unwrap_or_warn("could not send buffer update"); diff --git a/src/cursor/worker.rs b/src/cursor/worker.rs index 268665d..8a893e9 100644 --- a/src/cursor/worker.rs +++ b/src/cursor/worker.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use tokio::sync::{mpsc, broadcast::{self}, Mutex, watch}; use tonic::{Streaming, async_trait}; -use crate::{api::controller::ControllerWorker, errors::IgnorableError}; +use crate::{api::{controller::ControllerWorker, Cursor}, errors::IgnorableError}; use codemp_proto::cursor::{CursorPosition, CursorEvent}; use super::controller::CursorController; @@ -37,7 +37,7 @@ impl Default for CursorWorker { } #[async_trait] -impl ControllerWorker for CursorWorker { +impl ControllerWorker for CursorWorker { type Controller = CursorController; type Tx = mpsc::Sender; type Rx = Streaming; diff --git a/src/prelude.rs b/src/prelude.rs index efd0d54..064d395 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -7,15 +7,14 @@ pub use crate::{ Result as CodempResult, }; -pub use crate::woot::crdt::Op as CodempOp; - pub use crate::api::{ Controller as CodempController, TextChange as CodempTextChange, + Cursor as CodempCursor, + Op as CodempOp, }; pub use crate::{ - // Instance as CodempInstance, client::Client as CodempClient, workspace::Workspace as CodempWorkspace, workspace::UserInfo as CodempUserInfo,