Move protobuf logic from buffer crate to language crate

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

Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
Max Brunsfeld 2021-11-01 14:05:19 -07:00
parent 78d97a3db2
commit 40c861c249
7 changed files with 322 additions and 338 deletions

1
Cargo.lock generated
View file

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

View file

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

View file

@ -19,11 +19,9 @@ pub use point_utf16::*;
pub use random_char_iter::*; pub use random_char_iter::*;
use rope::TextDimension; use rope::TextDimension;
pub use rope::{Chunks, Rope, TextSummary}; pub use rope::{Chunks, Rope, TextSummary};
use rpc::proto;
pub use selection::*; pub use selection::*;
use std::{ use std::{
cmp::{self, Reverse}, cmp::{self, Reverse},
convert::TryFrom,
iter::Iterator, iter::Iterator,
ops::{self, Range}, ops::{self, Range},
str, str,
@ -35,7 +33,7 @@ use sum_tree::{FilterCursor, SumTree};
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
#[derive(Clone, Default)] #[derive(Clone, Default)]
struct DeterministicState; pub struct DeterministicState;
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
impl std::hash::BuildHasher for DeterministicState { impl std::hash::BuildHasher for DeterministicState {
@ -344,10 +342,10 @@ impl<D1, D2> Edit<(D1, D2)> {
} }
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
struct InsertionTimestamp { pub struct InsertionTimestamp {
replica_id: ReplicaId, pub replica_id: ReplicaId,
local: clock::Seq, pub local: clock::Seq,
lamport: clock::Seq, pub lamport: clock::Seq,
} }
impl InsertionTimestamp { impl InsertionTimestamp {
@ -422,18 +420,18 @@ pub enum Operation {
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub struct EditOperation { pub struct EditOperation {
timestamp: InsertionTimestamp, pub timestamp: InsertionTimestamp,
version: clock::Global, pub version: clock::Global,
ranges: Vec<Range<FullOffset>>, pub ranges: Vec<Range<FullOffset>>,
new_text: Option<String>, pub new_text: Option<String>,
} }
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub struct UndoOperation { pub struct UndoOperation {
id: clock::Local, pub id: clock::Local,
counts: HashMap<clock::Local, u32>, pub counts: HashMap<clock::Local, u32>,
ranges: Vec<Range<FullOffset>>, pub ranges: Vec<Range<FullOffset>>,
version: clock::Global, pub version: clock::Global,
} }
impl Buffer { 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 { pub fn version(&self) -> clock::Global {
self.version.clone() self.version.clone()
} }
@ -1203,6 +1173,14 @@ impl Buffer {
.retain(|set_id, _| set_id.replica_id != replica_id) .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> { pub fn undo(&mut self) -> Vec<Operation> {
let mut ops = Vec::new(); let mut ops = Vec::new();
if let Some(transaction) = self.history.pop_undo().cloned() { 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( pub fn set_active_selection_set(
&mut self, &mut self,
set_id: Option<SelectionSetId>, set_id: Option<SelectionSetId>,
@ -2157,18 +2139,10 @@ impl Default for FragmentSummary {
} }
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct FullOffset(usize); pub struct FullOffset(pub usize);
impl FullOffset { impl FullOffset {
const MAX: Self = FullOffset(usize::MAX); 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 { 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 { pub trait ToOffset {
fn to_offset<'a>(&self, content: impl Into<Content<'a>>) -> usize; fn to_offset<'a>(&self, content: impl Into<Content<'a>>) -> usize;

View file

@ -1,7 +1,5 @@
use super::{AnchorRangeMap, Buffer, Content, FullOffset, Point, ToOffset, ToPoint}; use super::{AnchorRangeMap, Buffer, Content, Point, ToOffset, ToPoint};
use rpc::proto;
use std::{cmp::Ordering, ops::Range, sync::Arc}; use std::{cmp::Ordering, ops::Range, sync::Arc};
use sum_tree::Bias;
pub type SelectionSetId = clock::Lamport; pub type SelectionSetId = clock::Lamport;
pub type SelectionsVersion = usize; 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(),
)),
}
}
}

View file

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

View file

@ -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(),
)),
}
}

View file

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