Move protobuf logic from buffer crate to language crate

Max Brunsfeld and Nathan Sobo created

This will enable us to add operations that only pertain to the language crate.

Co-Authored-By: Nathan Sobo <nathan@zed.dev>

Change summary

Cargo.lock                     |   1 
crates/buffer/Cargo.toml       |   1 
crates/buffer/src/lib.rs       | 299 +++--------------------------------
crates/buffer/src/selection.rs |  54 ------
crates/language/src/lib.rs     |  38 +++
crates/language/src/proto.rs   | 261 +++++++++++++++++++++++++++++++
crates/project/src/worktree.rs |   6 
7 files changed, 322 insertions(+), 338 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -761,7 +761,6 @@ dependencies = [
  "gpui",
  "log",
  "rand 0.8.3",
- "rpc",
  "seahash",
  "smallvec",
  "sum_tree",

crates/buffer/Cargo.toml 🔗

@@ -8,7 +8,6 @@ test-support = ["rand", "seahash"]
 
 [dependencies]
 clock = { path = "../clock" }
-rpc = { path = "../rpc" }
 sum_tree = { path = "../sum_tree" }
 anyhow = "1.0.38"
 arrayvec = "0.7.1"

crates/buffer/src/lib.rs 🔗

@@ -19,11 +19,9 @@ pub use point_utf16::*;
 pub use random_char_iter::*;
 use rope::TextDimension;
 pub use rope::{Chunks, Rope, TextSummary};
-use rpc::proto;
 pub use selection::*;
 use std::{
     cmp::{self, Reverse},
-    convert::TryFrom,
     iter::Iterator,
     ops::{self, Range},
     str,
@@ -35,7 +33,7 @@ use sum_tree::{FilterCursor, SumTree};
 
 #[cfg(any(test, feature = "test-support"))]
 #[derive(Clone, Default)]
-struct DeterministicState;
+pub struct DeterministicState;
 
 #[cfg(any(test, feature = "test-support"))]
 impl std::hash::BuildHasher for DeterministicState {
@@ -344,10 +342,10 @@ impl<D1, D2> Edit<(D1, D2)> {
 }
 
 #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
-struct InsertionTimestamp {
-    replica_id: ReplicaId,
-    local: clock::Seq,
-    lamport: clock::Seq,
+pub struct InsertionTimestamp {
+    pub replica_id: ReplicaId,
+    pub local: clock::Seq,
+    pub lamport: clock::Seq,
 }
 
 impl InsertionTimestamp {
@@ -422,18 +420,18 @@ pub enum Operation {
 
 #[derive(Clone, Debug, Eq, PartialEq)]
 pub struct EditOperation {
-    timestamp: InsertionTimestamp,
-    version: clock::Global,
-    ranges: Vec<Range<FullOffset>>,
-    new_text: Option<String>,
+    pub timestamp: InsertionTimestamp,
+    pub version: clock::Global,
+    pub ranges: Vec<Range<FullOffset>>,
+    pub new_text: Option<String>,
 }
 
 #[derive(Clone, Debug, Eq, PartialEq)]
 pub struct UndoOperation {
-    id: clock::Local,
-    counts: HashMap<clock::Local, u32>,
-    ranges: Vec<Range<FullOffset>>,
-    version: clock::Global,
+    pub id: clock::Local,
+    pub counts: HashMap<clock::Local, u32>,
+    pub ranges: Vec<Range<FullOffset>>,
+    pub version: clock::Global,
 }
 
 impl Buffer {
@@ -472,34 +470,6 @@ impl Buffer {
         }
     }
 
-    pub fn from_proto(replica_id: u16, message: proto::Buffer) -> Result<Self> {
-        let mut buffer = Buffer::new(replica_id, message.id, History::new(message.content.into()));
-        let ops = message
-            .history
-            .into_iter()
-            .map(|op| Operation::Edit(op.into()));
-        buffer.apply_ops(ops)?;
-        buffer.selections = message
-            .selections
-            .into_iter()
-            .map(|set| {
-                let set = SelectionSet::try_from(set)?;
-                Result::<_, anyhow::Error>::Ok((set.id, set))
-            })
-            .collect::<Result<_, _>>()?;
-        Ok(buffer)
-    }
-
-    pub fn to_proto(&self) -> proto::Buffer {
-        let ops = self.history.ops.values().map(Into::into).collect();
-        proto::Buffer {
-            id: self.remote_id,
-            content: self.history.base_text.to_string(),
-            history: ops,
-            selections: self.selections.iter().map(|(_, set)| set.into()).collect(),
-        }
-    }
-
     pub fn version(&self) -> clock::Global {
         self.version.clone()
     }
@@ -1203,6 +1173,14 @@ impl Buffer {
             .retain(|set_id, _| set_id.replica_id != replica_id)
     }
 
+    pub fn base_text(&self) -> &Arc<str> {
+        &self.history.base_text
+    }
+
+    pub fn history(&self) -> impl Iterator<Item = &EditOperation> {
+        self.history.ops.values()
+    }
+
     pub fn undo(&mut self) -> Vec<Operation> {
         let mut ops = Vec::new();
         if let Some(transaction) = self.history.pop_undo().cloned() {
@@ -1331,6 +1309,10 @@ impl Buffer {
         }
     }
 
+    pub fn add_raw_selection_set(&mut self, id: SelectionSetId, selections: SelectionSet) {
+        self.selections.insert(id, selections);
+    }
+
     pub fn set_active_selection_set(
         &mut self,
         set_id: Option<SelectionSetId>,
@@ -2157,18 +2139,10 @@ impl Default for FragmentSummary {
 }
 
 #[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
-pub struct FullOffset(usize);
+pub struct FullOffset(pub usize);
 
 impl FullOffset {
     const MAX: Self = FullOffset(usize::MAX);
-
-    fn to_proto(self) -> u64 {
-        self.0 as u64
-    }
-
-    fn from_proto(value: u64) -> Self {
-        Self(value as usize)
-    }
 }
 
 impl ops::AddAssign<usize> for FullOffset {
@@ -2298,227 +2272,6 @@ impl Operation {
     }
 }
 
-impl<'a> Into<proto::Operation> for &'a Operation {
-    fn into(self) -> proto::Operation {
-        proto::Operation {
-            variant: Some(match self {
-                Operation::Edit(edit) => proto::operation::Variant::Edit(edit.into()),
-                Operation::Undo {
-                    undo,
-                    lamport_timestamp,
-                } => proto::operation::Variant::Undo(proto::operation::Undo {
-                    replica_id: undo.id.replica_id as u32,
-                    local_timestamp: undo.id.value,
-                    lamport_timestamp: lamport_timestamp.value,
-                    ranges: undo
-                        .ranges
-                        .iter()
-                        .map(|r| proto::Range {
-                            start: r.start.to_proto(),
-                            end: r.end.to_proto(),
-                        })
-                        .collect(),
-                    counts: undo
-                        .counts
-                        .iter()
-                        .map(|(edit_id, count)| proto::operation::UndoCount {
-                            replica_id: edit_id.replica_id as u32,
-                            local_timestamp: edit_id.value,
-                            count: *count,
-                        })
-                        .collect(),
-                    version: From::from(&undo.version),
-                }),
-                Operation::UpdateSelections {
-                    set_id,
-                    selections,
-                    lamport_timestamp,
-                } => proto::operation::Variant::UpdateSelections(
-                    proto::operation::UpdateSelections {
-                        replica_id: set_id.replica_id as u32,
-                        local_timestamp: set_id.value,
-                        lamport_timestamp: lamport_timestamp.value,
-                        version: selections.version().into(),
-                        selections: selections
-                            .raw_entries()
-                            .iter()
-                            .map(|(range, state)| proto::Selection {
-                                id: state.id as u64,
-                                start: range.start.0.to_proto(),
-                                end: range.end.0.to_proto(),
-                                reversed: state.reversed,
-                            })
-                            .collect(),
-                    },
-                ),
-                Operation::RemoveSelections {
-                    set_id,
-                    lamport_timestamp,
-                } => proto::operation::Variant::RemoveSelections(
-                    proto::operation::RemoveSelections {
-                        replica_id: set_id.replica_id as u32,
-                        local_timestamp: set_id.value,
-                        lamport_timestamp: lamport_timestamp.value,
-                    },
-                ),
-                Operation::SetActiveSelections {
-                    set_id,
-                    lamport_timestamp,
-                } => proto::operation::Variant::SetActiveSelections(
-                    proto::operation::SetActiveSelections {
-                        replica_id: lamport_timestamp.replica_id as u32,
-                        local_timestamp: set_id.map(|set_id| set_id.value),
-                        lamport_timestamp: lamport_timestamp.value,
-                    },
-                ),
-                #[cfg(test)]
-                Operation::Test(_) => unimplemented!(),
-            }),
-        }
-    }
-}
-
-impl<'a> Into<proto::operation::Edit> for &'a EditOperation {
-    fn into(self) -> proto::operation::Edit {
-        let ranges = self
-            .ranges
-            .iter()
-            .map(|range| proto::Range {
-                start: range.start.to_proto(),
-                end: range.end.to_proto(),
-            })
-            .collect();
-        proto::operation::Edit {
-            replica_id: self.timestamp.replica_id as u32,
-            local_timestamp: self.timestamp.local,
-            lamport_timestamp: self.timestamp.lamport,
-            version: From::from(&self.version),
-            ranges,
-            new_text: self.new_text.clone(),
-        }
-    }
-}
-
-impl TryFrom<proto::Operation> for Operation {
-    type Error = anyhow::Error;
-
-    fn try_from(message: proto::Operation) -> Result<Self, Self::Error> {
-        Ok(
-            match message
-                .variant
-                .ok_or_else(|| anyhow!("missing operation variant"))?
-            {
-                proto::operation::Variant::Edit(edit) => Operation::Edit(edit.into()),
-                proto::operation::Variant::Undo(undo) => Operation::Undo {
-                    lamport_timestamp: clock::Lamport {
-                        replica_id: undo.replica_id as ReplicaId,
-                        value: undo.lamport_timestamp,
-                    },
-                    undo: UndoOperation {
-                        id: clock::Local {
-                            replica_id: undo.replica_id as ReplicaId,
-                            value: undo.local_timestamp,
-                        },
-                        counts: undo
-                            .counts
-                            .into_iter()
-                            .map(|c| {
-                                (
-                                    clock::Local {
-                                        replica_id: c.replica_id as ReplicaId,
-                                        value: c.local_timestamp,
-                                    },
-                                    c.count,
-                                )
-                            })
-                            .collect(),
-                        ranges: undo
-                            .ranges
-                            .into_iter()
-                            .map(|r| FullOffset::from_proto(r.start)..FullOffset::from_proto(r.end))
-                            .collect(),
-                        version: undo.version.into(),
-                    },
-                },
-                proto::operation::Variant::UpdateSelections(message) => {
-                    let version = message.version.into();
-                    let entries = message
-                        .selections
-                        .iter()
-                        .map(|selection| {
-                            let range = (FullOffset::from_proto(selection.start), Bias::Left)
-                                ..(FullOffset::from_proto(selection.end), Bias::Right);
-                            let state = SelectionState {
-                                id: selection.id as usize,
-                                reversed: selection.reversed,
-                                goal: SelectionGoal::None,
-                            };
-                            (range, state)
-                        })
-                        .collect();
-                    let selections = AnchorRangeMap::from_raw(version, entries);
-
-                    Operation::UpdateSelections {
-                        set_id: clock::Lamport {
-                            replica_id: message.replica_id as ReplicaId,
-                            value: message.local_timestamp,
-                        },
-                        lamport_timestamp: clock::Lamport {
-                            replica_id: message.replica_id as ReplicaId,
-                            value: message.lamport_timestamp,
-                        },
-                        selections: Arc::from(selections),
-                    }
-                }
-                proto::operation::Variant::RemoveSelections(message) => {
-                    Operation::RemoveSelections {
-                        set_id: clock::Lamport {
-                            replica_id: message.replica_id as ReplicaId,
-                            value: message.local_timestamp,
-                        },
-                        lamport_timestamp: clock::Lamport {
-                            replica_id: message.replica_id as ReplicaId,
-                            value: message.lamport_timestamp,
-                        },
-                    }
-                }
-                proto::operation::Variant::SetActiveSelections(message) => {
-                    Operation::SetActiveSelections {
-                        set_id: message.local_timestamp.map(|value| clock::Lamport {
-                            replica_id: message.replica_id as ReplicaId,
-                            value,
-                        }),
-                        lamport_timestamp: clock::Lamport {
-                            replica_id: message.replica_id as ReplicaId,
-                            value: message.lamport_timestamp,
-                        },
-                    }
-                }
-            },
-        )
-    }
-}
-
-impl From<proto::operation::Edit> for EditOperation {
-    fn from(edit: proto::operation::Edit) -> Self {
-        let ranges = edit
-            .ranges
-            .into_iter()
-            .map(|range| FullOffset::from_proto(range.start)..FullOffset::from_proto(range.end))
-            .collect();
-        EditOperation {
-            timestamp: InsertionTimestamp {
-                replica_id: edit.replica_id as ReplicaId,
-                local: edit.local_timestamp,
-                lamport: edit.lamport_timestamp,
-            },
-            version: edit.version.into(),
-            ranges,
-            new_text: edit.new_text,
-        }
-    }
-}
-
 pub trait ToOffset {
     fn to_offset<'a>(&self, content: impl Into<Content<'a>>) -> usize;
 

crates/buffer/src/selection.rs 🔗

@@ -1,7 +1,5 @@
-use super::{AnchorRangeMap, Buffer, Content, FullOffset, Point, ToOffset, ToPoint};
-use rpc::proto;
+use super::{AnchorRangeMap, Buffer, Content, Point, ToOffset, ToPoint};
 use std::{cmp::Ordering, ops::Range, sync::Arc};
-use sum_tree::Bias;
 
 pub type SelectionSetId = clock::Lamport;
 pub type SelectionsVersion = usize;
@@ -129,53 +127,3 @@ impl SelectionSet {
             })
     }
 }
-
-impl<'a> Into<proto::SelectionSet> for &'a SelectionSet {
-    fn into(self) -> proto::SelectionSet {
-        let version = self.selections.version();
-        let entries = self.selections.raw_entries();
-        proto::SelectionSet {
-            replica_id: self.id.replica_id as u32,
-            lamport_timestamp: self.id.value as u32,
-            is_active: self.active,
-            version: version.into(),
-            selections: entries
-                .iter()
-                .map(|(range, state)| proto::Selection {
-                    id: state.id as u64,
-                    start: range.start.0.to_proto(),
-                    end: range.end.0.to_proto(),
-                    reversed: state.reversed,
-                })
-                .collect(),
-        }
-    }
-}
-
-impl From<proto::SelectionSet> for SelectionSet {
-    fn from(set: proto::SelectionSet) -> Self {
-        Self {
-            id: clock::Lamport {
-                replica_id: set.replica_id as u16,
-                value: set.lamport_timestamp,
-            },
-            active: set.is_active,
-            selections: Arc::new(AnchorRangeMap::from_raw(
-                set.version.into(),
-                set.selections
-                    .into_iter()
-                    .map(|selection| {
-                        let range = (FullOffset::from_proto(selection.start), Bias::Left)
-                            ..(FullOffset::from_proto(selection.end), Bias::Right);
-                        let state = SelectionState {
-                            id: selection.id as usize,
-                            reversed: selection.reversed,
-                            goal: SelectionGoal::None,
-                        };
-                        (range, state)
-                    })
-                    .collect(),
-            )),
-        }
-    }
-}

crates/language/src/lib.rs 🔗

@@ -1,5 +1,6 @@
 mod highlight_map;
 mod language;
+pub mod proto;
 #[cfg(test)]
 mod tests;
 
@@ -16,7 +17,6 @@ use lazy_static::lazy_static;
 use lsp::LanguageServer;
 use parking_lot::Mutex;
 use postage::{prelude::Stream, sink::Sink, watch};
-use rpc::proto;
 use similar::{ChangeTag, TextDiff};
 use smol::future::yield_now;
 use std::{
@@ -251,10 +251,34 @@ impl Buffer {
         message: proto::Buffer,
         file: Option<Box<dyn File>>,
     ) -> Result<Self> {
-        Ok(Self::build(
-            TextBuffer::from_proto(replica_id, message)?,
-            file,
-        ))
+        let mut buffer =
+            buffer::Buffer::new(replica_id, message.id, History::new(message.content.into()));
+        let ops = message
+            .history
+            .into_iter()
+            .map(|op| Operation::Edit(proto::deserialize_edit_operation(op)));
+        buffer.apply_ops(ops)?;
+        for set in message.selections {
+            let set = proto::deserialize_selection_set(set);
+            buffer.add_raw_selection_set(set.id, set);
+        }
+        Ok(Self::build(buffer, file))
+    }
+
+    pub fn to_proto(&self) -> proto::Buffer {
+        proto::Buffer {
+            id: self.remote_id(),
+            content: self.text.base_text().to_string(),
+            history: self
+                .text
+                .history()
+                .map(proto::serialize_edit_operation)
+                .collect(),
+            selections: self
+                .selection_sets()
+                .map(|(_, set)| proto::serialize_selection_set(set))
+                .collect(),
+        }
     }
 
     pub fn with_language(
@@ -319,7 +343,7 @@ impl Buffer {
             .as_ref()
             .ok_or_else(|| anyhow!("buffer has no file"))?;
         let text = self.as_rope().clone();
-        let version = self.version.clone();
+        let version = self.version();
         let save = file.save(self.remote_id(), text, version, cx.as_mut());
         Ok(cx.spawn(|this, mut cx| async move {
             let (version, mtime) = save.await?;
@@ -494,7 +518,7 @@ impl Buffer {
                                     .await;
                                 this.update(&mut cx, |this, cx| {
                                     if this.apply_diff(diff, cx) {
-                                        this.saved_version = this.version.clone();
+                                        this.saved_version = this.version();
                                         this.saved_mtime = new_mtime;
                                         cx.emit(Event::Reloaded);
                                     }

crates/language/src/proto.rs 🔗

@@ -0,0 +1,261 @@
+use std::sync::Arc;
+
+use anyhow::{anyhow, Result};
+use buffer::*;
+use clock::ReplicaId;
+use rpc::proto;
+
+pub use proto::Buffer;
+
+pub fn serialize_operation(operation: &Operation) -> proto::Operation {
+    proto::Operation {
+        variant: Some(match operation {
+            Operation::Edit(edit) => {
+                proto::operation::Variant::Edit(serialize_edit_operation(edit))
+            }
+            Operation::Undo {
+                undo,
+                lamport_timestamp,
+            } => proto::operation::Variant::Undo(proto::operation::Undo {
+                replica_id: undo.id.replica_id as u32,
+                local_timestamp: undo.id.value,
+                lamport_timestamp: lamport_timestamp.value,
+                ranges: undo
+                    .ranges
+                    .iter()
+                    .map(|r| proto::Range {
+                        start: r.start.0 as u64,
+                        end: r.end.0 as u64,
+                    })
+                    .collect(),
+                counts: undo
+                    .counts
+                    .iter()
+                    .map(|(edit_id, count)| proto::operation::UndoCount {
+                        replica_id: edit_id.replica_id as u32,
+                        local_timestamp: edit_id.value,
+                        count: *count,
+                    })
+                    .collect(),
+                version: From::from(&undo.version),
+            }),
+            Operation::UpdateSelections {
+                set_id,
+                selections,
+                lamport_timestamp,
+            } => proto::operation::Variant::UpdateSelections(proto::operation::UpdateSelections {
+                replica_id: set_id.replica_id as u32,
+                local_timestamp: set_id.value,
+                lamport_timestamp: lamport_timestamp.value,
+                version: selections.version().into(),
+                selections: selections
+                    .raw_entries()
+                    .iter()
+                    .map(|(range, state)| proto::Selection {
+                        id: state.id as u64,
+                        start: range.start.0 .0 as u64,
+                        end: range.end.0 .0 as u64,
+                        reversed: state.reversed,
+                    })
+                    .collect(),
+            }),
+            Operation::RemoveSelections {
+                set_id,
+                lamport_timestamp,
+            } => proto::operation::Variant::RemoveSelections(proto::operation::RemoveSelections {
+                replica_id: set_id.replica_id as u32,
+                local_timestamp: set_id.value,
+                lamport_timestamp: lamport_timestamp.value,
+            }),
+            Operation::SetActiveSelections {
+                set_id,
+                lamport_timestamp,
+            } => proto::operation::Variant::SetActiveSelections(
+                proto::operation::SetActiveSelections {
+                    replica_id: lamport_timestamp.replica_id as u32,
+                    local_timestamp: set_id.map(|set_id| set_id.value),
+                    lamport_timestamp: lamport_timestamp.value,
+                },
+            ),
+        }),
+    }
+}
+
+pub fn serialize_edit_operation(operation: &EditOperation) -> proto::operation::Edit {
+    let ranges = operation
+        .ranges
+        .iter()
+        .map(|range| proto::Range {
+            start: range.start.0 as u64,
+            end: range.end.0 as u64,
+        })
+        .collect();
+    proto::operation::Edit {
+        replica_id: operation.timestamp.replica_id as u32,
+        local_timestamp: operation.timestamp.local,
+        lamport_timestamp: operation.timestamp.lamport,
+        version: From::from(&operation.version),
+        ranges,
+        new_text: operation.new_text.clone(),
+    }
+}
+
+pub fn serialize_selection_set(set: &SelectionSet) -> proto::SelectionSet {
+    let version = set.selections.version();
+    let entries = set.selections.raw_entries();
+    proto::SelectionSet {
+        replica_id: set.id.replica_id as u32,
+        lamport_timestamp: set.id.value as u32,
+        is_active: set.active,
+        version: version.into(),
+        selections: entries
+            .iter()
+            .map(|(range, state)| proto::Selection {
+                id: state.id as u64,
+                start: range.start.0 .0 as u64,
+                end: range.end.0 .0 as u64,
+                reversed: state.reversed,
+            })
+            .collect(),
+    }
+}
+
+pub fn deserialize_operation(message: proto::Operation) -> Result<Operation> {
+    Ok(
+        match message
+            .variant
+            .ok_or_else(|| anyhow!("missing operation variant"))?
+        {
+            proto::operation::Variant::Edit(edit) => {
+                Operation::Edit(deserialize_edit_operation(edit))
+            }
+            proto::operation::Variant::Undo(undo) => Operation::Undo {
+                lamport_timestamp: clock::Lamport {
+                    replica_id: undo.replica_id as ReplicaId,
+                    value: undo.lamport_timestamp,
+                },
+                undo: UndoOperation {
+                    id: clock::Local {
+                        replica_id: undo.replica_id as ReplicaId,
+                        value: undo.local_timestamp,
+                    },
+                    counts: undo
+                        .counts
+                        .into_iter()
+                        .map(|c| {
+                            (
+                                clock::Local {
+                                    replica_id: c.replica_id as ReplicaId,
+                                    value: c.local_timestamp,
+                                },
+                                c.count,
+                            )
+                        })
+                        .collect(),
+                    ranges: undo
+                        .ranges
+                        .into_iter()
+                        .map(|r| FullOffset(r.start as usize)..FullOffset(r.end as usize))
+                        .collect(),
+                    version: undo.version.into(),
+                },
+            },
+            proto::operation::Variant::UpdateSelections(message) => {
+                let version = message.version.into();
+                let entries = message
+                    .selections
+                    .iter()
+                    .map(|selection| {
+                        let range = (FullOffset(selection.start as usize), Bias::Left)
+                            ..(FullOffset(selection.end as usize), Bias::Right);
+                        let state = SelectionState {
+                            id: selection.id as usize,
+                            reversed: selection.reversed,
+                            goal: SelectionGoal::None,
+                        };
+                        (range, state)
+                    })
+                    .collect();
+                let selections = AnchorRangeMap::from_raw(version, entries);
+
+                Operation::UpdateSelections {
+                    set_id: clock::Lamport {
+                        replica_id: message.replica_id as ReplicaId,
+                        value: message.local_timestamp,
+                    },
+                    lamport_timestamp: clock::Lamport {
+                        replica_id: message.replica_id as ReplicaId,
+                        value: message.lamport_timestamp,
+                    },
+                    selections: Arc::from(selections),
+                }
+            }
+            proto::operation::Variant::RemoveSelections(message) => Operation::RemoveSelections {
+                set_id: clock::Lamport {
+                    replica_id: message.replica_id as ReplicaId,
+                    value: message.local_timestamp,
+                },
+                lamport_timestamp: clock::Lamport {
+                    replica_id: message.replica_id as ReplicaId,
+                    value: message.lamport_timestamp,
+                },
+            },
+            proto::operation::Variant::SetActiveSelections(message) => {
+                Operation::SetActiveSelections {
+                    set_id: message.local_timestamp.map(|value| clock::Lamport {
+                        replica_id: message.replica_id as ReplicaId,
+                        value,
+                    }),
+                    lamport_timestamp: clock::Lamport {
+                        replica_id: message.replica_id as ReplicaId,
+                        value: message.lamport_timestamp,
+                    },
+                }
+            }
+        },
+    )
+}
+
+pub fn deserialize_edit_operation(edit: proto::operation::Edit) -> EditOperation {
+    let ranges = edit
+        .ranges
+        .into_iter()
+        .map(|range| FullOffset(range.start as usize)..FullOffset(range.end as usize))
+        .collect();
+    EditOperation {
+        timestamp: InsertionTimestamp {
+            replica_id: edit.replica_id as ReplicaId,
+            local: edit.local_timestamp,
+            lamport: edit.lamport_timestamp,
+        },
+        version: edit.version.into(),
+        ranges,
+        new_text: edit.new_text,
+    }
+}
+
+pub fn deserialize_selection_set(set: proto::SelectionSet) -> SelectionSet {
+    SelectionSet {
+        id: clock::Lamport {
+            replica_id: set.replica_id as u16,
+            value: set.lamport_timestamp,
+        },
+        active: set.is_active,
+        selections: Arc::new(AnchorRangeMap::from_raw(
+            set.version.into(),
+            set.selections
+                .into_iter()
+                .map(|selection| {
+                    let range = (FullOffset(selection.start as usize), Bias::Left)
+                        ..(FullOffset(selection.end as usize), Bias::Right);
+                    let state = SelectionState {
+                        id: selection.id as usize,
+                        reversed: selection.reversed,
+                        goal: SelectionGoal::None,
+                    };
+                    (range, state)
+                })
+                .collect(),
+        )),
+    }
+}

crates/project/src/worktree.rs 🔗

@@ -430,8 +430,8 @@ impl Worktree {
         let ops = payload
             .operations
             .into_iter()
-            .map(|op| op.try_into())
-            .collect::<anyhow::Result<Vec<_>>>()?;
+            .map(|op| language::proto::deserialize_operation(op))
+            .collect::<Result<Vec<_>, _>>()?;
 
         match self {
             Worktree::Local(worktree) => {
@@ -1944,7 +1944,7 @@ impl language::File for File {
                         .request(proto::UpdateBuffer {
                             worktree_id: remote_id,
                             buffer_id,
-                            operations: vec![(&operation).into()],
+                            operations: vec![language::proto::serialize_operation(&operation)],
                         })
                         .await
                     {