feat: wrappers for Cursor and Op

so plugins dont need to interact directly with our underlying proto
struct, nor our underlying crdt ops. also for wrapping directly in glue
This commit is contained in:
əlemi 2024-08-05 19:15:30 +02:00
parent 5e4cbe5fb1
commit 39533ae086
Signed by: alemi
GPG key ID: A4895B84D311642C
6 changed files with 87 additions and 14 deletions

View file

@ -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<Vec<Op>> {
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),
}
},

63
src/api/cursor.rs Normal file
View file

@ -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<Uuid>,
}
impl From<proto::cursor::CursorPosition> 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<Cursor> 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<proto::cursor::CursorEvent> 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<Cursor> 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 },
}
}
}
}

View file

@ -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;

View file

@ -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<TextChange> 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<TextChange> for BufferWorker {
res = rx.message() => match res {
Err(_e) => break,
Ok(None) => break,
Ok(Some(change)) => match postcard::from_bytes::<Op>(&change.op.data) {
Ok(Some(change)) => match postcard::from_bytes::<woot::crdt::Op>(&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");

View file

@ -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<CursorEvent> for CursorWorker {
impl ControllerWorker<Cursor> for CursorWorker {
type Controller = CursorController;
type Tx = mpsc::Sender<CursorPosition>;
type Rx = Streaming<CursorEvent>;

View file

@ -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,