Compare commits

...
Sign in to create a new pull request.

25 commits

Author SHA1 Message Date
Piotr Osiewicz
fdfd09a360 Revert change to rpc/build.rs 2024-03-06 15:53:58 +01:00
Piotr Osiewicz
70b7718f18 Add comment to peer_has_changes 2024-03-06 15:43:38 +01:00
Piotr Osiewicz
7eede88ee0 fixup! Track conflicts during diffing 2024-03-06 15:31:23 +01:00
Piotr Osiewicz
84a9acd194 Track conflicts during diffing 2024-03-06 15:28:42 +01:00
Piotr Osiewicz
d00a9b3682 Remove todo comment 2024-03-06 15:12:58 +01:00
Piotr Osiewicz
1d2df5641f Remove saved_undo_top 2024-03-06 15:07:56 +01:00
Piotr Osiewicz
094a411dc5 Test fixups 2024-03-06 14:54:22 +01:00
Piotr Osiewicz
a8e2ccb6be Fix up test_buffer_is_dirty 2024-03-05 19:09:29 +01:00
Piotr Osiewicz
e400dbcc5b Fix last outstanding test 2024-03-05 18:47:14 +01:00
Piotr Osiewicz
7af2adb048 more of clippy fun 2024-03-05 18:38:10 +01:00
Piotr Osiewicz
110714313c Remove bromberg_sl2 dependency 2024-03-05 18:29:49 +01:00
Piotr Osiewicz
8883339b5b clippy fixes 2024-03-05 18:28:19 +01:00
Piotr Osiewicz
72a1c4a095 Remove unused dependency 2024-03-05 18:25:02 +01:00
Piotr Osiewicz
c2025f1327 Track just the local dirty state 2024-03-05 18:15:43 +01:00
Piotr Osiewicz
e4c2d86a80 fixup! Merge branch 'main' into piecemeal-hashing 2024-03-05 13:01:43 +01:00
Piotr Osiewicz
92483cb1b0 Merge branch 'main' into piecemeal-hashing 2024-03-05 12:59:54 +01:00
Piotr Osiewicz
3a6f491d8a fixup! Deserialize saved_undo_top 2024-02-20 12:52:30 +01:00
Piotr Osiewicz
30e02cc6c6 Deserialize saved_undo_top 2024-02-20 12:35:27 +01:00
Piotr Osiewicz
28a1ff7e91 Propagate undo stack in rpc 2024-02-20 12:24:18 +01:00
Piotr Osiewicz
31cb576c90 Get rid of fingerprint in proto 2024-02-20 12:00:37 +01:00
Piotr Osiewicz
9a0b8e6b44 Dirty-bit-implementation; do not track contents, undoing clears dirty bit 2024-02-20 11:27:25 +01:00
Piotr Osiewicz
8f8c022507 Do not bail on empty old range, use new one instead 2024-02-19 21:55:42 +01:00
Piotr Osiewicz
3de191c950 Compare bytes, not [u8]s 2024-02-19 21:49:29 +01:00
Piotr Osiewicz
3fad54a8de Move to direct comparisons of contents 2024-02-19 20:08:20 +01:00
Piotr Osiewicz
e75e9f7a0f text: Swap out hash function, store hash on language::Buffer object. 2024-02-19 18:35:50 +01:00
14 changed files with 109 additions and 132 deletions

18
Cargo.lock generated
View file

@ -1595,17 +1595,6 @@ dependencies = [
"workspace",
]
[[package]]
name = "bromberg_sl2"
version = "0.6.0"
source = "git+https://github.com/zed-industries/bromberg_sl2?rev=950bc5482c216c395049ae33ae4501e08975f17f#950bc5482c216c395049ae33ae4501e08975f17f"
dependencies = [
"digest 0.9.0",
"lazy_static",
"rayon",
"seq-macro",
]
[[package]]
name = "bstr"
version = "1.6.2"
@ -7996,7 +7985,6 @@ name = "rope"
version = "0.1.0"
dependencies = [
"arrayvec 0.7.4",
"bromberg_sl2",
"gpui",
"log",
"rand 0.8.5",
@ -8637,12 +8625,6 @@ version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
[[package]]
name = "seq-macro"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a9f47faea3cad316faa914d013d24f471cd90bfca1a0c70f05a3f42c6441e99"
[[package]]
name = "serde"
version = "1.0.196"

View file

@ -205,6 +205,7 @@ impl ChannelBuffer {
pub fn acknowledge_buffer_version(&mut self, cx: &mut ModelContext<'_, ChannelBuffer>) {
let buffer = self.buffer.read(cx);
let is_dirty = buffer.has_local_changes();
let version = buffer.version();
let buffer_id = buffer.remote_id().into();
let client = self.client.clone();
@ -219,6 +220,7 @@ impl ChannelBuffer {
buffer_id,
epoch,
version: serialize_version(&version),
is_dirty,
})
.ok();
Ok(())

View file

@ -1345,12 +1345,10 @@ impl RandomizedTest for ProjectCollaborationTest {
client.username
);
let host_saved_version_fingerprint =
host_buffer.read_with(host_cx, |b, _| b.saved_version_fingerprint());
let guest_saved_version_fingerprint =
guest_buffer.read_with(client_cx, |b, _| b.saved_version_fingerprint());
let host_is_dirty = host_buffer.read_with(host_cx, |b, _| b.is_dirty());
let guest_is_dirty = guest_buffer.read_with(client_cx, |b, _| b.is_dirty());
assert_eq!(
guest_saved_version_fingerprint, host_saved_version_fingerprint,
guest_is_dirty, host_is_dirty,
"guest {} saved fingerprint does not match host's for path {path:?} in project {project_id}",
client.username
);

View file

@ -1270,7 +1270,6 @@ mod tests {
&self,
_: BufferId,
_: &clock::Global,
_: language::RopeFingerprint,
_: language::LineEnding,
_: std::time::SystemTime,
_: &mut AppContext,

View file

@ -743,9 +743,9 @@ impl Item for Editor {
buffer
.update(&mut cx, |buffer, cx| {
let version = buffer.saved_version().clone();
let fingerprint = buffer.saved_version_fingerprint();
let mtime = buffer.saved_mtime();
buffer.did_save(version, fingerprint, mtime, cx);
buffer.did_save(version, mtime, cx);
})
.ok();
}

View file

@ -22,6 +22,7 @@ use gpui::{AppContext, EventEmitter, HighlightStyle, ModelContext, Task, TaskLab
use lazy_static::lazy_static;
use lsp::LanguageServerId;
use parking_lot::Mutex;
use rpc::ConnectionId;
use similar::{ChangeTag, TextDiff};
use smallvec::SmallVec;
use smol::future::yield_now;
@ -45,9 +46,9 @@ use text::operation_queue::OperationQueue;
use text::*;
pub use text::{
Anchor, Bias, Buffer as TextBuffer, BufferId, BufferSnapshot as TextBufferSnapshot, Edit,
OffsetRangeExt, OffsetUtf16, Patch, Point, PointUtf16, Rope, RopeFingerprint, Selection,
SelectionGoal, Subscription, TextDimension, TextSummary, ToOffset, ToOffsetUtf16, ToPoint,
ToPointUtf16, Transaction, TransactionId, Unclipped,
OffsetRangeExt, OffsetUtf16, Patch, Point, PointUtf16, Rope, Selection, SelectionGoal,
Subscription, TextDimension, TextSummary, ToOffset, ToOffsetUtf16, ToPoint, ToPointUtf16,
Transaction, TransactionId, Unclipped,
};
use theme::SyntaxTheme;
#[cfg(any(test, feature = "test-support"))]
@ -87,8 +88,15 @@ pub struct Buffer {
/// The version vector when this buffer was last loaded from
/// or saved to disk.
saved_version: clock::Global,
/// A hash of the current contents of the buffer's file.
file_fingerprint: RopeFingerprint,
/// True if a peer with given id has any local changes to the buffer.
/// For remote peers this should have just one entry (to a host).
/// For a host, this tracks has_local_changes result for all peers.
/// When it sends out updates, it ORs it's own has_local_changes with these of peers.
/// That way, any other peer will know whether the buffer is dirty based on it's knowledge of
/// local changes + changes made upstream.
peer_has_changes: BTreeMap<ConnectionId, bool>,
/// Marked as true if a conflict is detected during diffing, cleared on save.
has_conflict: bool,
transaction_depth: usize,
was_dirty_before_starting_transaction: Option<bool>,
reload_task: Option<Task<Result<()>>>,
@ -401,7 +409,6 @@ pub trait LocalFile: File {
&self,
buffer_id: BufferId,
version: &clock::Global,
fingerprint: RopeFingerprint,
line_ending: LineEnding,
mtime: SystemTime,
cx: &mut AppContext,
@ -571,7 +578,6 @@ impl Buffer {
.ok_or_else(|| anyhow!("missing line_ending"))?,
));
this.saved_version = proto::deserialize_version(&message.saved_version);
this.file_fingerprint = proto::deserialize_fingerprint(&message.saved_version_fingerprint)?;
this.saved_mtime = message
.saved_mtime
.ok_or_else(|| anyhow!("invalid saved_mtime"))?
@ -588,7 +594,6 @@ impl Buffer {
diff_base: self.diff_base.as_ref().map(|h| h.to_string()),
line_ending: proto::serialize_line_ending(self.line_ending()) as i32,
saved_version: proto::serialize_version(&self.saved_version),
saved_version_fingerprint: proto::serialize_fingerprint(self.file_fingerprint),
saved_mtime: Some(self.saved_mtime.into()),
}
}
@ -668,11 +673,9 @@ impl Buffer {
} else {
UNIX_EPOCH
};
Self {
saved_mtime,
saved_version: buffer.version(),
file_fingerprint: buffer.as_rope().fingerprint(),
reload_task: None,
transaction_depth: 0,
was_dirty_before_starting_transaction: None,
@ -698,6 +701,8 @@ impl Buffer {
completion_triggers: Default::default(),
completion_triggers_timestamp: Default::default(),
deferred_ops: OperationQueue::new(),
peer_has_changes: Default::default(),
has_conflict: false,
}
}
@ -746,11 +751,6 @@ impl Buffer {
&self.saved_version
}
/// The fingerprint of the buffer's text when the buffer was last saved or reloaded from disk.
pub fn saved_version_fingerprint(&self) -> RopeFingerprint {
self.file_fingerprint
}
/// The mtime of the buffer's file when the buffer was last saved or reloaded from disk.
pub fn saved_mtime(&self) -> SystemTime {
self.saved_mtime
@ -783,13 +783,15 @@ impl Buffer {
pub fn did_save(
&mut self,
version: clock::Global,
fingerprint: RopeFingerprint,
mtime: SystemTime,
cx: &mut ModelContext<Self>,
) {
self.saved_version = version;
self.file_fingerprint = fingerprint;
self.saved_mtime = mtime;
self.peer_has_changes
.values_mut()
.for_each(|has_changed| *has_changed = false);
self.has_conflict = false;
cx.emit(Event::Saved);
cx.notify();
}
@ -819,18 +821,20 @@ impl Buffer {
this.finalize_last_transaction();
this.apply_diff(diff, cx);
tx.send(this.finalize_last_transaction().cloned()).ok();
this.has_conflict = false;
this.did_reload(this.version(), this.line_ending(), new_mtime, cx);
} else {
if !diff.edits.is_empty()
|| this
.edits_since::<usize>(&diff.base_version)
.next()
.is_some()
{
this.has_conflict = true;
}
this.did_reload(
this.version(),
this.as_rope().fingerprint(),
this.line_ending(),
new_mtime,
cx,
);
} else {
this.did_reload(
prev_version,
Rope::text_fingerprint(&new_text),
prev_version.clone(),
this.line_ending(),
this.saved_mtime,
cx,
@ -847,20 +851,20 @@ impl Buffer {
pub fn did_reload(
&mut self,
version: clock::Global,
fingerprint: RopeFingerprint,
line_ending: LineEnding,
mtime: SystemTime,
cx: &mut ModelContext<Self>,
) {
self.saved_version = version;
self.file_fingerprint = fingerprint;
self.text.set_line_ending(line_ending);
self.saved_mtime = mtime;
self.peer_has_changes
.values_mut()
.for_each(|has_changed| *has_changed = false);
if let Some(file) = self.file.as_ref().and_then(|f| f.as_local()) {
file.buffer_reloaded(
self.remote_id(),
&self.saved_version,
self.file_fingerprint,
self.line_ending(),
self.saved_mtime,
cx,
@ -1518,16 +1522,29 @@ impl Buffer {
self.end_transaction(cx)
}
pub fn has_local_changes(&self) -> bool {
self.edits_since::<usize>(&self.saved_version)
.next()
.is_some()
}
pub fn content_differs(&self) -> bool {
self.has_conflict
|| self.has_local_changes()
|| self
.peer_has_changes
.values()
.any(|has_changes| *has_changes)
}
/// Checks if the buffer has unsaved changes.
pub fn is_dirty(&self) -> bool {
self.file_fingerprint != self.as_rope().fingerprint()
|| self.file.as_ref().map_or(false, |file| file.is_deleted())
self.content_differs() || self.file.as_ref().map_or(false, |file| file.is_deleted())
}
/// Checks if the buffer and its file have both changed since the buffer
/// was last saved or reloaded.
pub fn has_conflict(&self) -> bool {
self.file_fingerprint != self.as_rope().fingerprint()
self.content_differs()
&& self
.file
.as_ref()
@ -1830,6 +1847,10 @@ impl Buffer {
cx.notify();
}
pub fn mark_dirty(&mut self, peer_id: ConnectionId, dirty: bool) {
*self.peer_has_changes.entry(peer_id).or_default() = dirty;
}
/// Applies the given remote operations to the buffer.
pub fn apply_ops<I: IntoIterator<Item = Operation>>(
&mut self,
@ -1980,6 +2001,10 @@ impl Buffer {
/// Removes the selections for a given peer.
pub fn remove_peer(&mut self, replica_id: ReplicaId, cx: &mut ModelContext<Self>) {
// We do not clear peer_has_changes, as if we're the host, we want to know if that peer
// made any changes even if they're no longer around; the entry for corresponding peer will
// be cleared on save/buffer close. If we were to clear peer_has_changes there, we would lose track
// of dirty state for that peer.
self.remote_selections.remove(&replica_id);
cx.notify();
}

View file

@ -13,17 +13,6 @@ use text::*;
pub use proto::{BufferState, Operation};
/// Serializes a [`RopeFingerprint`] to be sent over RPC.
pub fn serialize_fingerprint(fingerprint: RopeFingerprint) -> String {
fingerprint.to_hex()
}
/// Deserializes a [`RopeFingerprint`] from the RPC representation.
pub fn deserialize_fingerprint(fingerprint: &str) -> Result<RopeFingerprint> {
RopeFingerprint::from_hex(fingerprint)
.map_err(|error| anyhow!("invalid fingerprint: {}", error))
}
/// Deserializes a `[text::LineEnding]` from the RPC representation.
pub fn deserialize_line_ending(message: proto::LineEnding) -> text::LineEnding {
match message {

View file

@ -33,8 +33,8 @@ use language::{
language_settings::{language_settings, FormatOnSave, Formatter, InlayHintKind},
markdown, point_to_lsp,
proto::{
deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version,
serialize_anchor, serialize_version, split_operations,
deserialize_anchor, deserialize_line_ending, deserialize_version, serialize_anchor,
serialize_version, split_operations,
},
range_from_lsp, range_to_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, Capability,
CodeAction, CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff,
@ -185,6 +185,8 @@ enum BufferOrderedMessage {
Operation {
buffer_id: BufferId,
operation: proto::Operation,
is_dirty: bool,
is_local_dirty: bool,
},
LanguageServerUpdate {
language_server_id: LanguageServerId,
@ -2210,18 +2212,19 @@ impl Project {
let mut operations_by_buffer_id = HashMap::default();
async fn flush_operations(
this: &WeakModel<Project>,
operations_by_buffer_id: &mut HashMap<BufferId, Vec<proto::Operation>>,
operations_by_buffer_id: &mut HashMap<BufferId, (Vec<proto::Operation>, bool)>,
needs_resync_with_host: &mut bool,
is_local: bool,
cx: &mut AsyncAppContext,
) -> Result<()> {
for (buffer_id, operations) in operations_by_buffer_id.drain() {
for (buffer_id, (operations, is_dirty)) in operations_by_buffer_id.drain() {
let request = this.update(cx, |this, _| {
let project_id = this.remote_id()?;
Some(this.client.request(proto::UpdateBuffer {
buffer_id: buffer_id.into(),
project_id,
operations,
is_dirty,
}))
})?;
if let Some(request) = request {
@ -2239,20 +2242,22 @@ impl Project {
while let Some(changes) = changes.next().await {
let is_local = this.update(&mut cx, |this, _| this.is_local())?;
for change in changes {
match change {
BufferOrderedMessage::Operation {
buffer_id,
operation,
is_dirty,
is_local_dirty,
} => {
if needs_resync_with_host {
continue;
}
let is_dirty = if is_local { is_dirty } else { is_local_dirty };
operations_by_buffer_id
.entry(buffer_id)
.or_insert(Vec::new())
.or_insert((Vec::new(), is_dirty))
.0
.push(operation);
}
@ -2323,10 +2328,13 @@ impl Project {
match event {
BufferEvent::Operation(operation) => {
let buffer = buffer.read(cx);
self.buffer_ordered_messages_tx
.unbounded_send(BufferOrderedMessage::Operation {
buffer_id: buffer.read(cx).remote_id(),
buffer_id: buffer.remote_id(),
operation: language::proto::serialize_operation(operation),
is_dirty: buffer.is_dirty(),
is_local_dirty: buffer.has_local_changes(),
})
.ok();
}
@ -7742,7 +7750,10 @@ impl Project {
match this.opened_buffers.entry(buffer_id) {
hash_map::Entry::Occupied(mut e) => match e.get_mut() {
OpenBuffer::Strong(buffer) => {
buffer.update(cx, |buffer, cx| buffer.apply_ops(ops, cx))?;
buffer.update(cx, |buffer, cx| {
buffer.mark_dirty(envelope.sender_id, payload.is_dirty);
buffer.apply_ops(ops, cx)
})?;
}
OpenBuffer::Operations(operations) => operations.extend_from_slice(&ops),
OpenBuffer::Weak(_) => {}
@ -7915,7 +7926,6 @@ impl Project {
buffer_id: buffer_id.into(),
version: serialize_version(buffer.saved_version()),
mtime: Some(buffer.saved_mtime().into()),
fingerprint: language::proto::serialize_fingerprint(buffer.saved_version_fingerprint()),
})
}
@ -8008,15 +8018,12 @@ impl Project {
buffer_id: buffer_id.into(),
version: language::proto::serialize_version(buffer.saved_version()),
mtime: Some(buffer.saved_mtime().into()),
fingerprint: language::proto::serialize_fingerprint(
buffer.saved_version_fingerprint(),
),
line_ending: language::proto::serialize_line_ending(
buffer.line_ending(),
) as i32,
})
.log_err();
let is_dirty = buffer.is_dirty();
cx.background_executor()
.spawn(
async move {
@ -8027,6 +8034,7 @@ impl Project {
project_id,
buffer_id: buffer_id.into(),
operations: chunk,
is_dirty,
})
.await?;
}
@ -8655,9 +8663,10 @@ impl Project {
.iter()
.filter_map(|(id, buffer)| {
let buffer = buffer.upgrade()?;
let buffer = buffer.read(cx);
Some(proto::BufferVersion {
id: (*id).into(),
version: language::proto::serialize_version(&buffer.read(cx).version),
version: language::proto::serialize_version(&buffer.version),
})
})
.collect();
@ -8692,6 +8701,7 @@ impl Project {
if let Some(buffer) = this.buffer_for_id(buffer_id) {
let operations =
buffer.read(cx).serialize_ops(Some(remote_version), cx);
let is_dirty = buffer.read(cx).is_dirty();
cx.background_executor().spawn(async move {
let operations = operations.await;
for chunk in split_operations(operations) {
@ -8700,6 +8710,7 @@ impl Project {
project_id,
buffer_id: buffer_id.into(),
operations: chunk,
is_dirty,
})
.await?;
}
@ -8862,7 +8873,6 @@ impl Project {
_: Arc<Client>,
mut cx: AsyncAppContext,
) -> Result<()> {
let fingerprint = deserialize_fingerprint(&envelope.payload.fingerprint)?;
let version = deserialize_version(&envelope.payload.version);
let buffer_id = BufferId::new(envelope.payload.buffer_id)?;
let mtime = envelope
@ -8883,7 +8893,7 @@ impl Project {
});
if let Some(buffer) = buffer {
buffer.update(cx, |buffer, cx| {
buffer.did_save(version, fingerprint, mtime, cx);
buffer.did_save(version, mtime, cx);
});
}
Ok(())
@ -8898,7 +8908,6 @@ impl Project {
) -> Result<()> {
let payload = envelope.payload;
let version = deserialize_version(&payload.version);
let fingerprint = deserialize_fingerprint(&payload.fingerprint)?;
let line_ending = deserialize_line_ending(
proto::LineEnding::from_i32(payload.line_ending)
.ok_or_else(|| anyhow!("missing line ending"))?,
@ -8921,7 +8930,7 @@ impl Project {
});
if let Some(buffer) = buffer {
buffer.update(cx, |buffer, cx| {
buffer.did_reload(version, fingerprint, line_ending, mtime, cx);
buffer.did_reload(version, line_ending, mtime, cx);
});
}
Ok(())

View file

@ -3097,12 +3097,7 @@ async fn test_buffer_is_dirty(cx: &mut gpui::TestAppContext) {
&[language::Event::Edited, language::Event::DirtyChanged]
);
events.lock().clear();
buffer.did_save(
buffer.version(),
buffer.as_rope().fingerprint(),
buffer.file().unwrap().mtime(),
cx,
);
buffer.did_save(buffer.version(), buffer.file().unwrap().mtime(), cx);
});
// after saving, the buffer is not dirty, and emits a saved event.

View file

@ -28,12 +28,8 @@ use gpui::{
};
use itertools::Itertools;
use language::{
proto::{
deserialize_fingerprint, deserialize_version, serialize_fingerprint, serialize_line_ending,
serialize_version,
},
Buffer, Capability, DiagnosticEntry, File as _, LineEnding, PointUtf16, Rope, RopeFingerprint,
Unclipped,
proto::{deserialize_version, serialize_line_ending, serialize_version},
Buffer, Capability, DiagnosticEntry, File as _, LineEnding, PointUtf16, Rope, Unclipped,
};
use lsp::LanguageServerId;
use parking_lot::Mutex;
@ -1109,7 +1105,6 @@ impl LocalWorktree {
let project_id = self.share.as_ref().map(|share| share.project_id);
let text = buffer.as_rope().clone();
let fingerprint = text.fingerprint();
let version = buffer.version();
let save = self.write_file(path.as_ref(), text, buffer.line_ending(), cx);
let fs = Arc::clone(&self.fs);
@ -1172,12 +1167,11 @@ impl LocalWorktree {
buffer_id,
version: serialize_version(&version),
mtime: Some(mtime.into()),
fingerprint: serialize_fingerprint(fingerprint),
})?;
}
buffer_handle.update(&mut cx, |buffer, cx| {
buffer.did_save(version.clone(), fingerprint, mtime, cx);
buffer.did_save(version.clone(), mtime, cx);
})?;
Ok(())
@ -1578,14 +1572,13 @@ impl RemoteWorktree {
})
.await?;
let version = deserialize_version(&response.version);
let fingerprint = deserialize_fingerprint(&response.fingerprint)?;
let mtime = response
.mtime
.ok_or_else(|| anyhow!("missing mtime"))?
.into();
buffer_handle.update(&mut cx, |buffer, cx| {
buffer.did_save(version.clone(), fingerprint, mtime, cx);
buffer.did_save(version.clone(), mtime, cx);
})?;
Ok(())
@ -2944,7 +2937,6 @@ impl language::LocalFile for File {
&self,
buffer_id: BufferId,
version: &clock::Global,
fingerprint: RopeFingerprint,
line_ending: LineEnding,
mtime: SystemTime,
cx: &mut AppContext,
@ -2958,7 +2950,7 @@ impl language::LocalFile for File {
buffer_id: buffer_id.into(),
version: serialize_version(version),
mtime: Some(mtime.into()),
fingerprint: serialize_fingerprint(fingerprint),
line_ending: serialize_line_ending(line_ending) as i32,
})
.log_err();

View file

@ -10,7 +10,6 @@ path = "src/rope.rs"
[dependencies]
arrayvec = "0.7.1"
bromberg_sl2 = { git = "https://github.com/zed-industries/bromberg_sl2", rev = "950bc5482c216c395049ae33ae4501e08975f17f" }
log.workspace = true
smallvec.workspace = true
sum_tree.workspace = true

View file

@ -4,7 +4,6 @@ mod point_utf16;
mod unclipped;
use arrayvec::ArrayString;
use bromberg_sl2::HashMatrix;
use smallvec::SmallVec;
use std::{
cmp, fmt, io, mem,
@ -25,12 +24,6 @@ const CHUNK_BASE: usize = 6;
#[cfg(not(test))]
const CHUNK_BASE: usize = 16;
/// Type alias to [`HashMatrix`], an implementation of a homomorphic hash function. Two [`Rope`] instances
/// containing the same text will produce the same fingerprint. This hash function is special in that
/// it allows us to hash individual chunks and aggregate them up the [`Rope`]'s tree, with the resulting
/// hash being equivalent to hashing all the text contained in the [`Rope`] at once.
pub type RopeFingerprint = HashMatrix;
#[derive(Clone, Default)]
pub struct Rope {
chunks: SumTree<Chunk>,
@ -41,10 +34,6 @@ impl Rope {
Self::default()
}
pub fn text_fingerprint(text: &str) -> RopeFingerprint {
bromberg_sl2::hash_strict(text.as_bytes())
}
pub fn append(&mut self, rope: Rope) {
let mut chunks = rope.chunks.cursor::<()>();
chunks.next(&());
@ -378,10 +367,6 @@ impl Rope {
self.clip_point(Point::new(row, u32::MAX), Bias::Left)
.column
}
pub fn fingerprint(&self) -> RopeFingerprint {
self.chunks.summary().fingerprint
}
}
impl<'a> From<&'a str> for Rope {
@ -932,14 +917,12 @@ impl sum_tree::Item for Chunk {
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct ChunkSummary {
text: TextSummary,
fingerprint: RopeFingerprint,
}
impl<'a> From<&'a str> for ChunkSummary {
fn from(text: &'a str) -> Self {
Self {
text: TextSummary::from(text),
fingerprint: Rope::text_fingerprint(text),
}
}
}
@ -949,7 +932,6 @@ impl sum_tree::Summary for ChunkSummary {
fn add_summary(&mut self, summary: &Self, _: &()) {
self.text += &summary.text;
self.fingerprint = self.fingerprint * summary.fingerprint;
}
}

View file

@ -640,6 +640,7 @@ message UpdateBuffer {
uint64 project_id = 1;
uint64 buffer_id = 2;
repeated Operation operations = 3;
bool is_dirty = 4;
}
message UpdateChannelBuffer {
@ -664,7 +665,7 @@ message BufferSaved {
uint64 buffer_id = 2;
repeated VectorClockEntry version = 3;
Timestamp mtime = 4;
string fingerprint = 5;
reserved 5;
}
message BufferReloaded {
@ -672,7 +673,7 @@ message BufferReloaded {
uint64 buffer_id = 2;
repeated VectorClockEntry version = 3;
Timestamp mtime = 4;
string fingerprint = 5;
reserved 5;
LineEnding line_ending = 6;
}
@ -1239,6 +1240,7 @@ message AckBufferOperation {
uint64 buffer_id = 1;
uint64 epoch = 2;
repeated VectorClockEntry version = 3;
bool is_dirty = 4;
}
message JoinChannelBufferResponse {
@ -1477,7 +1479,7 @@ message BufferState {
optional string diff_base = 4;
LineEnding line_ending = 5;
repeated VectorClockEntry saved_version = 6;
string saved_version_fingerprint = 7;
reserved 7;
Timestamp saved_mtime = 8;
}

View file

@ -104,7 +104,7 @@ pub struct BufferSnapshot {
pub version: clock::Global,
}
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub struct HistoryEntry {
transaction: Transaction,
first_edit_at: Instant,
@ -112,7 +112,7 @@ pub struct HistoryEntry {
suppress_grouping: bool,
}
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub struct Transaction {
pub id: TransactionId,
pub edit_ids: Vec<clock::Lamport>,
@ -123,6 +123,9 @@ impl HistoryEntry {
pub fn transaction_id(&self) -> TransactionId {
self.transaction.id
}
pub fn transaction(&self) -> &Transaction {
&self.transaction
}
}
struct History {