Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bca9319a80 |
23
Cargo.lock
generated
23
Cargo.lock
generated
@@ -9632,6 +9632,29 @@ dependencies = [
|
||||
"util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "text-eg"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clock",
|
||||
"collections",
|
||||
"ctor",
|
||||
"derive_more",
|
||||
"env_logger",
|
||||
"gpui",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"parking_lot",
|
||||
"postage",
|
||||
"rand 0.8.5",
|
||||
"regex",
|
||||
"rope",
|
||||
"smallvec",
|
||||
"sum_tree",
|
||||
"util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.16.0"
|
||||
|
||||
@@ -84,6 +84,7 @@ members = [
|
||||
"crates/terminal",
|
||||
"crates/terminal_view",
|
||||
"crates/text",
|
||||
"crates/text-eg",
|
||||
"crates/theme",
|
||||
"crates/theme_importer",
|
||||
"crates/theme_selector",
|
||||
|
||||
40
crates/text-eg/Cargo.toml
Normal file
40
crates/text-eg/Cargo.toml
Normal file
@@ -0,0 +1,40 @@
|
||||
[package]
|
||||
name = "text-eg"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/text-eg.rs"
|
||||
doctest = false
|
||||
|
||||
[features]
|
||||
test-support = ["rand"]
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
clock.workspace = true
|
||||
collections.workspace = true
|
||||
lazy_static.workspace = true
|
||||
log.workspace = true
|
||||
parking_lot.workspace = true
|
||||
postage.workspace = true
|
||||
rand = { workspace = true, optional = true }
|
||||
regex.workspace = true
|
||||
rope.workspace = true
|
||||
smallvec.workspace = true
|
||||
sum_tree.workspace = true
|
||||
derive_more.workspace = true
|
||||
util.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
collections = { workspace = true, features = ["test-support"] }
|
||||
ctor.workspace = true
|
||||
env_logger.workspace = true
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
rand.workspace = true
|
||||
util = { workspace = true, features = ["test-support"] }
|
||||
239
crates/text-eg/src/text-eg.rs
Normal file
239
crates/text-eg/src/text-eg.rs
Normal file
@@ -0,0 +1,239 @@
|
||||
use std::{cmp::Ordering, ops::Range};
|
||||
|
||||
use collections::HashMap;
|
||||
use rope::Rope;
|
||||
|
||||
#[derive(Eq,PartialEq,Ord,PartialOrd,Hash,Debug,Clone,Copy)]
|
||||
struct ReplicaId(u16);
|
||||
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||
struct Seq(u32);
|
||||
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||
struct LocalVersion(u32);
|
||||
struct RawVersion {
|
||||
version: Seq,
|
||||
replica_id: ReplicaId,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct CausalGraph {
|
||||
heads: Vec<LocalVersion>,
|
||||
entries: Vec<CausalGraphEntry>,
|
||||
observed_versions: HashMap<ReplicaId, Vec<ClientEntry>>
|
||||
}
|
||||
|
||||
struct CausalGraphEntry {
|
||||
version: LocalVersion,
|
||||
end: LocalVersion, // > version.
|
||||
|
||||
replica_id: ReplicaId,
|
||||
seq: Seq, // Seq for version.
|
||||
|
||||
parents: Vec<LocalVersion> // Parents for version
|
||||
}
|
||||
|
||||
struct ClientEntry {
|
||||
seq: Seq,
|
||||
seq_end: Seq,
|
||||
/// Version of the first item in this run
|
||||
version: LocalVersion,
|
||||
}
|
||||
|
||||
impl CausalGraph {
|
||||
fn lv_to_raw_list(&self, heads: Vec<LocalVersion>) -> Vec<RawVersion> {
|
||||
heads.into_iter().map(|head| {
|
||||
let (e, offset) = self.find_entry_containing(head);
|
||||
RawVersion {replica_id: e.replica_id, version: e.seq + offset}
|
||||
}).collect()
|
||||
}
|
||||
|
||||
fn find_entry_containing(head: LocalVersion) -> (CausalGraphEntry, u32) {
|
||||
// Find the entry containing the given local version
|
||||
// Returns the entry and the offset of the version within the entry
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn find_client_entry_raw(&self, replica_id: ReplicaId, seq: Seq) -> Option<ClientEntry> {
|
||||
let Some(agent_versions) = self.observed_versions.get(&replica_id) else {
|
||||
return None
|
||||
};
|
||||
|
||||
let result = agent_versions.binary_search_by(|x| {
|
||||
if x.seq < seq {
|
||||
Ordering::Less
|
||||
} else if x.seq_end >= seq {
|
||||
Ordering::Greater
|
||||
} else {
|
||||
Ordering::Equal
|
||||
}
|
||||
});
|
||||
|
||||
match result {
|
||||
Ok(i) => Some(agent_versions[i]),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
fn find_client_entry(&self, replica_id: ReplicaId, seq: Seq) -> Option<(ClientEntry, Seq)> {
|
||||
self.find_client_entry_raw(replica_id, seq)
|
||||
.map(|client_entry| (client_entry, seq.0 - client_entry.seq.0))
|
||||
}
|
||||
|
||||
fn find_client_entry_trimmed(&self, replica_id: ReplicaId, seq: Seq) -> Option<ClientEntry> {
|
||||
self.find_client_entry(replica_id, seq)
|
||||
.map(|(entry, offset)| {
|
||||
if offset.0 == 0 {
|
||||
entry
|
||||
} else {
|
||||
ClientEntry {
|
||||
seq: offset,
|
||||
seq_end: entry.seq_end,
|
||||
// TODO: These are the same thing????
|
||||
version: LocalVersion(entry.version.0 + offset.0)
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
fn add(&self, replica_id: ReplicaId) {
|
||||
let next_version = LocalVersion(self.entries.last().map(|_| -> u32 {
|
||||
todo!()
|
||||
}).unwrap_or(0));
|
||||
|
||||
|
||||
|
||||
fn find_client_entry() -> Option<CausalGraphEntry> {
|
||||
todo!();
|
||||
}
|
||||
|
||||
loop {
|
||||
|
||||
let existing_entry = self.find_client_entry_trimmed(replica_id, Seq(next_version.0));
|
||||
let Some(existing_entry) = existing_entry else {
|
||||
break
|
||||
};
|
||||
|
||||
// if existing_entry.end
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum Op {
|
||||
Insert(usize, char),
|
||||
Delete(usize)
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct OpLog {
|
||||
ops: Vec<Op>,
|
||||
graph: CausalGraph,
|
||||
}
|
||||
|
||||
impl OpLog {
|
||||
fn get_latest_version(&self) -> Vec<RawVersion> {
|
||||
self.graph.lv_to_raw_list(self.graph.heads)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Buffer {
|
||||
text: Rope,
|
||||
op_log: OpLog,
|
||||
}
|
||||
|
||||
// struct EditContext {
|
||||
// items: [],
|
||||
// del_targets: Vec<()>,
|
||||
// items_by_lv: Vec<()>,
|
||||
// cur_version: [],
|
||||
// }
|
||||
|
||||
impl Buffer {
|
||||
fn new(replica_id: u16, text: &str) -> Self {
|
||||
let mut buffer = Buffer {
|
||||
text: Rope::from(text),
|
||||
op_log: OpLog::default(),
|
||||
};
|
||||
buffer.edit(0..0, text);
|
||||
buffer
|
||||
}
|
||||
|
||||
pub fn edit(&mut self, range: Range<usize>, text: &str) -> Vec<Op>
|
||||
{
|
||||
let mut operations = Vec::new();
|
||||
for _ in range.clone() {
|
||||
operations.push(Op::Delete(range.start));
|
||||
|
||||
}
|
||||
for (ix, char) in text.chars().enumerate() {
|
||||
operations.push(Op::Insert(range.start + ix, char))
|
||||
}
|
||||
self.op_log.ops.extend(operations.clone());
|
||||
return operations
|
||||
|
||||
}
|
||||
|
||||
pub fn apply_op(&mut self, op: Vec<Op>) -> anyhow::Result<()> {
|
||||
// Here's the magic
|
||||
self.op_log.ops.extend(op);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn text(&self) -> String {
|
||||
let mut final_text = String::new();
|
||||
for op in self.op_log.ops.iter() {
|
||||
match op {
|
||||
Op::Insert(ix, char) => {final_text.insert(*ix, *char);},
|
||||
Op::Delete(ix) => {final_text.remove(*ix);},
|
||||
}
|
||||
}
|
||||
final_text
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_edit() {
|
||||
let mut buffer = Buffer::new(0, "abc");
|
||||
assert_eq!(buffer.text(), "abc");
|
||||
buffer.edit(3..3, "def");
|
||||
assert_eq!(buffer.text(), "abcdef");
|
||||
buffer.edit(0..0, "ghi");
|
||||
assert_eq!(buffer.text(), "ghiabcdef");
|
||||
buffer.edit(5..5, "jkl");
|
||||
assert_eq!(buffer.text(), "ghiabjklcdef");
|
||||
buffer.edit(6..7, "");
|
||||
assert_eq!(buffer.text(), "ghiabjlcdef");
|
||||
buffer.edit(4..9, "mno");
|
||||
assert_eq!(buffer.text(), "ghiamnoef");
|
||||
}
|
||||
#[test]
|
||||
fn test_concurrent_edits() {
|
||||
let text = "abcdef";
|
||||
|
||||
let mut buffer1 = Buffer::new(1, text);
|
||||
let mut buffer2 = Buffer::new(2, text);
|
||||
let mut buffer3 = Buffer::new(3, text);
|
||||
|
||||
let buf1_op = buffer1.edit(1..2, "12");
|
||||
assert_eq!(buffer1.text(), "a12cdef");
|
||||
let buf2_op = buffer2.edit(3..4, "34");
|
||||
assert_eq!(buffer2.text(), "abc34ef");
|
||||
let buf3_op = buffer3.edit(5..6, "56");
|
||||
assert_eq!(buffer3.text(), "abcde56");
|
||||
|
||||
buffer1.apply_op(buf2_op.clone()).unwrap();
|
||||
buffer1.apply_op(buf3_op.clone()).unwrap();
|
||||
buffer2.apply_op(buf1_op.clone()).unwrap();
|
||||
buffer2.apply_op(buf3_op).unwrap();
|
||||
buffer3.apply_op(buf1_op).unwrap();
|
||||
buffer3.apply_op(buf2_op).unwrap();
|
||||
|
||||
assert_eq!(buffer1.text(), "a12c34e56");
|
||||
assert_eq!(buffer2.text(), "a12c34e56");
|
||||
assert_eq!(buffer3.text(), "a12c34e56");
|
||||
}
|
||||
Reference in New Issue
Block a user