fix: cleaned up code, fixed multi-op change issues

This commit is contained in:
əlemi 2024-02-09 00:35:08 +01:00
parent 99a268185a
commit c9a36ea8ec
3 changed files with 49 additions and 52 deletions

View file

@ -10,7 +10,7 @@ name = "codemp"
# core
tracing = "0.1"
# woot
codemp-woot = { git = "ssh://git@github.com/codewithotherpeopleandchangenamelater/woot.git", features = ["serde"], tag = "v0.1.1", optional = true }
codemp-woot = { git = "ssh://git@github.com/codewithotherpeopleandchangenamelater/woot.git", features = ["serde"], tag = "v0.1.2", optional = true }
# proto
uuid = { version = "1.3.1", features = ["v4"], optional = true }
tonic = { version = "0.9", features = ["tls", "tls-roots"], optional = true }

View file

@ -3,7 +3,8 @@
//! an editor-friendly representation of a text change in a buffer
//! to easily interface with codemp from various editors
use crate::proto::cursor::RowCol;
#[cfg(feature = "woot")]
use crate::woot::{WootResult, woot::Woot, crdt::{TextEditor, CRDT, Op}};
/// an editor-friendly representation of a text change in a buffer
///
@ -59,6 +60,33 @@ impl TextChange {
}
}
#[cfg(feature = "woot")]
pub fn transform(self, woot: &Woot) -> WootResult<Vec<Op>> {
let mut out = Vec::new();
if self.is_empty() { return Ok(out); } // no-op
let view = woot.view();
let Some(span) = view.get(self.span.clone()) else {
return Err(crate::woot::WootError::OutOfBounds);
};
let diff = similar::TextDiff::from_chars(span, &self.content);
for (i, diff) in diff.iter_all_changes().enumerate() {
match diff.tag() {
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),
},
similar::ChangeTag::Insert => {
match woot.insert(self.span.start + i, diff.value()) {
Ok(mut op) => out.append(&mut op),
Err(e) => tracing::error!("could not create insertion: {}", e),
}
},
}
}
Ok(out)
}
/// returns true if this TextChange deletes existing text
pub fn is_deletion(&self) -> bool {
!self.span.is_empty()
@ -84,11 +112,12 @@ impl TextChange {
/// convert from byte index to row and column
/// txt must be the whole content of the buffer, in order to count lines
pub fn index_to_rowcol(txt: &str, index: usize) -> RowCol {
#[cfg(feature = "transport")]
pub fn index_to_rowcol(txt: &str, index: usize) -> crate::proto::cursor::RowCol {
// FIXME might panic, use .get()
let row = txt[..index].matches('\n').count() as i32;
let col = txt[..index].split('\n').last().unwrap_or("").len() as i32;
RowCol { row, col }
crate::proto::cursor::RowCol { row, col }
}
}

View file

@ -1,11 +1,10 @@
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use similar::{TextDiff, ChangeTag};
use tokio::sync::{watch, mpsc, oneshot};
use tonic::{async_trait, Streaming};
use uuid::Uuid;
use woot::crdt::{Op, CRDT, TextEditor};
use woot::crdt::{Op, CRDT};
use woot::woot::Woot;
use crate::errors::IgnorableError;
@ -95,54 +94,23 @@ impl ControllerWorker<TextChange> for BufferWorker {
// received a text change from editor
res = self.operations.recv() => match res {
None => break,
Some(change) => {
if !change.is_empty() {
let view = self.buffer.view();
match view.get(change.span.clone()) {
None => tracing::error!("received illegal span from client: {:?} but buffer is of len {}", change.span, view.len()),
Some(span) => {
let diff = TextDiff::from_chars(span, &change.content);
let mut i = 0;
let mut ops = Vec::new();
for diff in diff.iter_all_changes() {
match diff.tag() {
ChangeTag::Equal => i += 1,
ChangeTag::Delete => match self.buffer.delete(change.span.start + i) {
Ok(op) => ops.push(op),
Err(e) => tracing::error!("could not apply deletion: {}", e),
},
ChangeTag::Insert => {
for c in diff.value().chars() {
match self.buffer.insert(change.span.start + i, c) {
Ok(op) => {
ops.push(op);
i += 1;
},
Err(e) => tracing::error!("could not apply insertion: {}", e),
}
}
},
}
}
None => break tracing::debug!("stopping: editor closed channel"),
Some(change) => match change.transform(&self.buffer) {
Err(e) => break tracing::error!("could not apply operation from client: {}", e),
Ok(ops) => {
for op in ops {
self.buffer.merge(op.clone());
let operation = Operation {
data: postcard::to_extend(&op, Vec::new()).unwrap(),
};
match tx.send(operation).await {
Err(e) => tracing::error!("server refused to broadcast {}: {}", op, e),
Ok(()) => {
self.content.send(self.buffer.view()).unwrap_or_warn("could not send buffer update");
if let Err(e) = tx.send(operation).await {
tracing::error!("server refused to broadcast {}: {}", op, e);
}
}
self.content.send(self.buffer.view())
.unwrap_or_warn("could not send buffer update");
},
}
}
},
}
}
}
},
// received a message from server