Compare commits
25 commits
main
...
piecemeal-
Author | SHA1 | Date | |
---|---|---|---|
![]() |
fdfd09a360 | ||
![]() |
70b7718f18 | ||
![]() |
7eede88ee0 | ||
![]() |
84a9acd194 | ||
![]() |
d00a9b3682 | ||
![]() |
1d2df5641f | ||
![]() |
094a411dc5 | ||
![]() |
a8e2ccb6be | ||
![]() |
e400dbcc5b | ||
![]() |
7af2adb048 | ||
![]() |
110714313c | ||
![]() |
8883339b5b | ||
![]() |
72a1c4a095 | ||
![]() |
c2025f1327 | ||
![]() |
e4c2d86a80 | ||
![]() |
92483cb1b0 | ||
![]() |
3a6f491d8a | ||
![]() |
30e02cc6c6 | ||
![]() |
28a1ff7e91 | ||
![]() |
31cb576c90 | ||
![]() |
9a0b8e6b44 | ||
![]() |
8f8c022507 | ||
![]() |
3de191c950 | ||
![]() |
3fad54a8de | ||
![]() |
e75e9f7a0f |
14 changed files with 109 additions and 132 deletions
18
Cargo.lock
generated
18
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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(())
|
||||
|
|
|
@ -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
|
||||
);
|
||||
|
|
|
@ -1270,7 +1270,6 @@ mod tests {
|
|||
&self,
|
||||
_: BufferId,
|
||||
_: &clock::Global,
|
||||
_: language::RopeFingerprint,
|
||||
_: language::LineEnding,
|
||||
_: std::time::SystemTime,
|
||||
_: &mut AppContext,
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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(())
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue