mirror of
https://github.com/hexedtech/codemp.git
synced 2024-11-26 08:54:49 +01:00
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:
parent
5e4cbe5fb1
commit
39533ae086
6 changed files with 87 additions and 14 deletions
|
@ -3,7 +3,13 @@
|
||||||
//! an editor-friendly representation of a text change in a buffer
|
//! an editor-friendly representation of a text change in a buffer
|
||||||
//! to easily interface with codemp from various editors
|
//! 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
|
/// 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>> {
|
pub fn transform(self, woot: &Woot) -> WootResult<Vec<Op>> {
|
||||||
let mut out = Vec::new();
|
let mut out = Vec::new();
|
||||||
if self.is_empty() { return Ok(out); } // no-op
|
if self.is_empty() { return Ok(out); } // no-op
|
||||||
|
@ -73,11 +79,11 @@ impl TextChange {
|
||||||
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),
|
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(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),
|
Err(e) => tracing::error!("could not create insertion: {}", e),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
63
src/api/cursor.rs
Normal file
63
src/api/cursor.rs
Normal 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 },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,5 +10,10 @@ pub mod controller;
|
||||||
/// a generic representation of a text change
|
/// a generic representation of a text change
|
||||||
pub mod change;
|
pub mod change;
|
||||||
|
|
||||||
|
/// representation for an user's cursor
|
||||||
|
pub mod cursor;
|
||||||
|
|
||||||
pub use controller::Controller;
|
pub use controller::Controller;
|
||||||
pub use change::TextChange;
|
pub use change::TextChange;
|
||||||
|
pub use change::Op;
|
||||||
|
pub use cursor::Cursor;
|
||||||
|
|
|
@ -4,7 +4,7 @@ use std::hash::{Hash, Hasher};
|
||||||
use tokio::sync::{watch, mpsc, oneshot};
|
use tokio::sync::{watch, mpsc, oneshot};
|
||||||
use tonic::{async_trait, Streaming};
|
use tonic::{async_trait, Streaming};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use woot::crdt::{Op, CRDT};
|
use woot::crdt::CRDT;
|
||||||
use woot::woot::Woot;
|
use woot::woot::Woot;
|
||||||
|
|
||||||
use crate::errors::IgnorableError;
|
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),
|
Err(e) => break tracing::error!("could not apply operation from client: {}", e),
|
||||||
Ok(ops) => {
|
Ok(ops) => {
|
||||||
for op in ops {
|
for op in ops {
|
||||||
self.buffer.merge(op.clone());
|
self.buffer.merge(op.0.clone());
|
||||||
let operation = Operation {
|
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 {
|
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())
|
self.content.send(self.buffer.view())
|
||||||
|
@ -117,7 +117,7 @@ impl ControllerWorker<TextChange> for BufferWorker {
|
||||||
res = rx.message() => match res {
|
res = rx.message() => match res {
|
||||||
Err(_e) => break,
|
Err(_e) => break,
|
||||||
Ok(None) => 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?
|
Ok(op) => { // TODO here in change we receive info about the author, maybe propagate?
|
||||||
self.buffer.merge(op);
|
self.buffer.merge(op);
|
||||||
self.content.send(self.buffer.view()).unwrap_or_warn("could not send buffer update");
|
self.content.send(self.buffer.view()).unwrap_or_warn("could not send buffer update");
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::sync::Arc;
|
||||||
use tokio::sync::{mpsc, broadcast::{self}, Mutex, watch};
|
use tokio::sync::{mpsc, broadcast::{self}, Mutex, watch};
|
||||||
use tonic::{Streaming, async_trait};
|
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 codemp_proto::cursor::{CursorPosition, CursorEvent};
|
||||||
|
|
||||||
use super::controller::CursorController;
|
use super::controller::CursorController;
|
||||||
|
@ -37,7 +37,7 @@ impl Default for CursorWorker {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl ControllerWorker<CursorEvent> for CursorWorker {
|
impl ControllerWorker<Cursor> for CursorWorker {
|
||||||
type Controller = CursorController;
|
type Controller = CursorController;
|
||||||
type Tx = mpsc::Sender<CursorPosition>;
|
type Tx = mpsc::Sender<CursorPosition>;
|
||||||
type Rx = Streaming<CursorEvent>;
|
type Rx = Streaming<CursorEvent>;
|
||||||
|
|
|
@ -7,15 +7,14 @@ pub use crate::{
|
||||||
Result as CodempResult,
|
Result as CodempResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use crate::woot::crdt::Op as CodempOp;
|
|
||||||
|
|
||||||
pub use crate::api::{
|
pub use crate::api::{
|
||||||
Controller as CodempController,
|
Controller as CodempController,
|
||||||
TextChange as CodempTextChange,
|
TextChange as CodempTextChange,
|
||||||
|
Cursor as CodempCursor,
|
||||||
|
Op as CodempOp,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
// Instance as CodempInstance,
|
|
||||||
client::Client as CodempClient,
|
client::Client as CodempClient,
|
||||||
workspace::Workspace as CodempWorkspace,
|
workspace::Workspace as CodempWorkspace,
|
||||||
workspace::UserInfo as CodempUserInfo,
|
workspace::UserInfo as CodempUserInfo,
|
||||||
|
|
Loading…
Reference in a new issue