mirror of
https://github.com/hexedtech/codemp.git
synced 2024-11-22 15:24:48 +01:00
fix: cleaned up code, fixed multi-op change issues
This commit is contained in:
parent
99a268185a
commit
c9a36ea8ec
3 changed files with 49 additions and 52 deletions
|
@ -10,7 +10,7 @@ name = "codemp"
|
||||||
# core
|
# core
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
# woot
|
# 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
|
# proto
|
||||||
uuid = { version = "1.3.1", features = ["v4"], optional = true }
|
uuid = { version = "1.3.1", features = ["v4"], optional = true }
|
||||||
tonic = { version = "0.9", features = ["tls", "tls-roots"], optional = true }
|
tonic = { version = "0.9", features = ["tls", "tls-roots"], optional = true }
|
||||||
|
|
|
@ -3,7 +3,8 @@
|
||||||
//! 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::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
|
/// 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
|
/// returns true if this TextChange deletes existing text
|
||||||
pub fn is_deletion(&self) -> bool {
|
pub fn is_deletion(&self) -> bool {
|
||||||
!self.span.is_empty()
|
!self.span.is_empty()
|
||||||
|
@ -84,11 +112,12 @@ impl TextChange {
|
||||||
|
|
||||||
/// convert from byte index to row and column
|
/// convert from byte index to row and column
|
||||||
/// txt must be the whole content of the buffer, in order to count lines
|
/// 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()
|
// FIXME might panic, use .get()
|
||||||
let row = txt[..index].matches('\n').count() as i32;
|
let row = txt[..index].matches('\n').count() as i32;
|
||||||
let col = txt[..index].split('\n').last().unwrap_or("").len() as i32;
|
let col = txt[..index].split('\n').last().unwrap_or("").len() as i32;
|
||||||
RowCol { row, col }
|
crate::proto::cursor::RowCol { row, col }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
use std::collections::hash_map::DefaultHasher;
|
use std::collections::hash_map::DefaultHasher;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
||||||
use similar::{TextDiff, ChangeTag};
|
|
||||||
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, TextEditor};
|
use woot::crdt::{Op, CRDT};
|
||||||
use woot::woot::Woot;
|
use woot::woot::Woot;
|
||||||
|
|
||||||
use crate::errors::IgnorableError;
|
use crate::errors::IgnorableError;
|
||||||
|
@ -95,54 +94,23 @@ impl ControllerWorker<TextChange> for BufferWorker {
|
||||||
|
|
||||||
// received a text change from editor
|
// received a text change from editor
|
||||||
res = self.operations.recv() => match res {
|
res = self.operations.recv() => match res {
|
||||||
None => break,
|
None => break tracing::debug!("stopping: editor closed channel"),
|
||||||
Some(change) => {
|
Some(change) => match change.transform(&self.buffer) {
|
||||||
if !change.is_empty() {
|
Err(e) => break tracing::error!("could not apply operation from client: {}", e),
|
||||||
let view = self.buffer.view();
|
Ok(ops) => {
|
||||||
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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for op in ops {
|
for op in ops {
|
||||||
|
self.buffer.merge(op.clone());
|
||||||
let operation = Operation {
|
let operation = Operation {
|
||||||
data: postcard::to_extend(&op, Vec::new()).unwrap(),
|
data: postcard::to_extend(&op, Vec::new()).unwrap(),
|
||||||
};
|
};
|
||||||
|
if let Err(e) = tx.send(operation).await {
|
||||||
match tx.send(operation).await {
|
tracing::error!("server refused to broadcast {}: {}", op, e);
|
||||||
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");
|
self.content.send(self.buffer.view())
|
||||||
|
.unwrap_or_warn("could not send buffer update");
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// received a message from server
|
// received a message from server
|
||||||
|
|
Loading…
Reference in a new issue