From 0e62ddbb654d0a3b3716b8fcbdccd6f19e949af0 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 1 Nov 2021 15:28:37 -0700 Subject: [PATCH] Replicate diagnostics to remote buffers Co-Authored-By: Nathan Sobo --- crates/buffer/src/anchor.rs | 44 +++++++++++- crates/language/src/lib.rs | 49 +++++++++++--- crates/language/src/proto.rs | 126 +++++++++++++++++++++++++---------- crates/rpc/proto/zed.proto | 23 +++++++ crates/rpc/src/peer.rs | 4 ++ script/server | 2 +- 6 files changed, 197 insertions(+), 51 deletions(-) diff --git a/crates/buffer/src/anchor.rs b/crates/buffer/src/anchor.rs index b4f01909bf..d1577230cc 100644 --- a/crates/buffer/src/anchor.rs +++ b/crates/buffer/src/anchor.rs @@ -176,12 +176,17 @@ impl AnchorRangeMap { self.entries.len() } - pub fn from_raw(version: clock::Global, entries: Vec<(Range<(FullOffset, Bias)>, T)>) -> Self { + pub fn from_full_offset_ranges( + version: clock::Global, + entries: Vec<(Range<(FullOffset, Bias)>, T)>, + ) -> Self { Self { version, entries } } - pub fn raw_entries(&self) -> &[(Range<(FullOffset, Bias)>, T)] { - &self.entries + pub fn full_offset_ranges(&self) -> impl Iterator, &T)> { + self.entries + .iter() + .map(|(range, value)| (range.start.0..range.end.0, value)) } pub fn point_ranges<'a>( @@ -270,6 +275,10 @@ impl Default for AnchorRangeMultimap { } impl AnchorRangeMultimap { + pub fn version(&self) -> &clock::Global { + &self.version + } + pub fn intersecting_ranges<'a, I, O>( &'a self, range: Range, @@ -336,6 +345,35 @@ impl AnchorRangeMultimap { } }) } + + pub fn from_full_offset_ranges( + version: clock::Global, + start_bias: Bias, + end_bias: Bias, + entries: impl Iterator, T)>, + ) -> Self { + Self { + version, + start_bias, + end_bias, + entries: SumTree::from_iter( + entries.map(|(range, value)| AnchorRangeMultimapEntry { + range: FullOffsetRange { + start: range.start, + end: range.end, + }, + value, + }), + &(), + ), + } + } + + pub fn full_offset_ranges(&self) -> impl Iterator, &T)> { + self.entries + .cursor::<()>() + .map(|entry| (entry.range.start..entry.range.end, &entry.value)) + } } impl sum_tree::Item for AnchorRangeMultimapEntry { diff --git a/crates/language/src/lib.rs b/crates/language/src/lib.rs index 726afd7617..7fa27b154d 100644 --- a/crates/language/src/lib.rs +++ b/crates/language/src/lib.rs @@ -9,7 +9,7 @@ pub use self::{ language::{BracketPair, Language, LanguageConfig, LanguageRegistry}, }; use anyhow::{anyhow, Result}; -pub use buffer::{Buffer as TextBuffer, *}; +pub use buffer::{Buffer as TextBuffer, Operation as _, *}; use clock::ReplicaId; use futures::FutureExt as _; use gpui::{AppContext, Entity, ModelContext, MutableAppContext, Task}; @@ -99,6 +99,12 @@ struct LanguageServerSnapshot { path: Arc, } +#[derive(Clone)] +pub enum Operation { + Buffer(buffer::Operation), + UpdateDiagnostics(AnchorRangeMultimap), +} + #[derive(Clone, Debug, Eq, PartialEq)] pub enum Event { Edited, @@ -256,7 +262,7 @@ impl Buffer { let ops = message .history .into_iter() - .map(|op| Operation::Edit(proto::deserialize_edit_operation(op))); + .map(|op| buffer::Operation::Edit(proto::deserialize_edit_operation(op))); buffer.apply_ops(ops)?; for set in message.selections { let set = proto::deserialize_selection_set(set); @@ -278,6 +284,7 @@ impl Buffer { .selection_sets() .map(|(_, set)| proto::serialize_selection_set(set)) .collect(), + diagnostics: Some(proto::serialize_diagnostics(&self.diagnostics)), } } @@ -761,6 +768,7 @@ impl Buffer { } self.diagnostics_update_count += 1; + self.send_operation(Operation::UpdateDiagnostics(self.diagnostics.clone()), cx); cx.notify(); Ok(()) } @@ -1240,7 +1248,7 @@ impl Buffer { } self.end_transaction(None, cx).unwrap(); - self.send_operation(Operation::Edit(edit), cx); + self.send_operation(Operation::Buffer(buffer::Operation::Edit(edit)), cx); } fn did_edit( @@ -1269,10 +1277,10 @@ impl Buffer { cx: &mut ModelContext, ) -> SelectionSetId { let operation = self.text.add_selection_set(selections); - if let Operation::UpdateSelections { set_id, .. } = &operation { + if let buffer::Operation::UpdateSelections { set_id, .. } = &operation { let set_id = *set_id; cx.notify(); - self.send_operation(operation, cx); + self.send_operation(Operation::Buffer(operation), cx); set_id } else { unreachable!() @@ -1287,7 +1295,7 @@ impl Buffer { ) -> Result<()> { let operation = self.text.update_selection_set(set_id, selections)?; cx.notify(); - self.send_operation(operation, cx); + self.send_operation(Operation::Buffer(operation), cx); Ok(()) } @@ -1297,7 +1305,7 @@ impl Buffer { cx: &mut ModelContext, ) -> Result<()> { let operation = self.text.set_active_selection_set(set_id)?; - self.send_operation(operation, cx); + self.send_operation(Operation::Buffer(operation), cx); Ok(()) } @@ -1308,7 +1316,7 @@ impl Buffer { ) -> Result<()> { let operation = self.text.remove_selection_set(set_id)?; cx.notify(); - self.send_operation(operation, cx); + self.send_operation(Operation::Buffer(operation), cx); Ok(()) } @@ -1320,7 +1328,17 @@ impl Buffer { self.pending_autoindent.take(); let was_dirty = self.is_dirty(); let old_version = self.version.clone(); - self.text.apply_ops(ops)?; + let buffer_ops = ops + .into_iter() + .filter_map(|op| match op { + Operation::Buffer(op) => Some(op), + Operation::UpdateDiagnostics(diagnostics) => { + self.apply_diagnostic_update(diagnostics, cx); + None + } + }) + .collect::>(); + self.text.apply_ops(buffer_ops)?; self.did_edit(&old_version, was_dirty, cx); // Notify independently of whether the buffer was edited as the operations could include a // selection update. @@ -1328,6 +1346,15 @@ impl Buffer { Ok(()) } + fn apply_diagnostic_update( + &mut self, + diagnostics: AnchorRangeMultimap, + cx: &mut ModelContext, + ) { + self.diagnostics = diagnostics; + cx.notify(); + } + #[cfg(not(test))] pub fn send_operation(&mut self, operation: Operation, cx: &mut ModelContext) { if let Some(file) = &self.file { @@ -1350,7 +1377,7 @@ impl Buffer { let old_version = self.version.clone(); for operation in self.text.undo() { - self.send_operation(operation, cx); + self.send_operation(Operation::Buffer(operation), cx); } self.did_edit(&old_version, was_dirty, cx); @@ -1361,7 +1388,7 @@ impl Buffer { let old_version = self.version.clone(); for operation in self.text.redo() { - self.send_operation(operation, cx); + self.send_operation(Operation::Buffer(operation), cx); } self.did_edit(&old_version, was_dirty, cx); diff --git a/crates/language/src/proto.rs b/crates/language/src/proto.rs index 769373b8c8..4e6a8316c2 100644 --- a/crates/language/src/proto.rs +++ b/crates/language/src/proto.rs @@ -1,8 +1,12 @@ use std::sync::Arc; +use crate::Diagnostic; + +use super::Operation; use anyhow::{anyhow, Result}; use buffer::*; use clock::ReplicaId; +use lsp::DiagnosticSeverity; use rpc::proto; pub use proto::Buffer; @@ -10,13 +14,13 @@ pub use proto::Buffer; pub fn serialize_operation(operation: &Operation) -> proto::Operation { proto::Operation { variant: Some(match operation { - Operation::Edit(edit) => { + Operation::Buffer(buffer::Operation::Edit(edit)) => { proto::operation::Variant::Edit(serialize_edit_operation(edit)) } - Operation::Undo { + Operation::Buffer(buffer::Operation::Undo { undo, lamport_timestamp, - } => proto::operation::Variant::Undo(proto::operation::Undo { + }) => 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, @@ -39,44 +43,46 @@ pub fn serialize_operation(operation: &Operation) -> proto::Operation { .collect(), version: From::from(&undo.version), }), - Operation::UpdateSelections { + Operation::Buffer(buffer::Operation::UpdateSelections { set_id, selections, lamport_timestamp, - } => proto::operation::Variant::UpdateSelections(proto::operation::UpdateSelections { + }) => 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() + .full_offset_ranges() .map(|(range, state)| proto::Selection { id: state.id as u64, - start: range.start.0 .0 as u64, - end: range.end.0 .0 as u64, + start: range.start.0 as u64, + end: range.end.0 as u64, reversed: state.reversed, }) .collect(), }), - Operation::RemoveSelections { + Operation::Buffer(buffer::Operation::RemoveSelections { set_id, lamport_timestamp, - } => proto::operation::Variant::RemoveSelections(proto::operation::RemoveSelections { + }) => 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 { + Operation::Buffer(buffer::Operation::SetActiveSelections { set_id, lamport_timestamp, - } => proto::operation::Variant::SetActiveSelections( + }) => 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, }, ), + Operation::UpdateDiagnostics(diagnostic_set) => { + proto::operation::Variant::UpdateDiagnostics(serialize_diagnostics(diagnostic_set)) + } }), } } @@ -102,24 +108,44 @@ pub fn serialize_edit_operation(operation: &EditOperation) -> proto::operation:: pub fn serialize_selection_set(set: &SelectionSet) -> proto::SelectionSet { let version = set.selections.version(); - let entries = set.selections.raw_entries(); + let entries = set.selections.full_offset_ranges(); 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, + start: range.start.0 as u64, + end: range.end.0 as u64, reversed: state.reversed, }) .collect(), } } +pub fn serialize_diagnostics(map: &AnchorRangeMultimap) -> proto::DiagnosticSet { + proto::DiagnosticSet { + version: map.version().into(), + diagnostics: map + .full_offset_ranges() + .map(|(range, diagnostic)| proto::Diagnostic { + start: range.start.0 as u64, + end: range.end.0 as u64, + message: diagnostic.message.clone(), + severity: match diagnostic.severity { + DiagnosticSeverity::ERROR => proto::diagnostic::Severity::Error, + DiagnosticSeverity::WARNING => proto::diagnostic::Severity::Warning, + DiagnosticSeverity::INFORMATION => proto::diagnostic::Severity::Information, + DiagnosticSeverity::HINT => proto::diagnostic::Severity::Hint, + _ => proto::diagnostic::Severity::None, + } as i32, + }) + .collect(), + } +} + pub fn deserialize_operation(message: proto::Operation) -> Result { Ok( match message @@ -127,9 +153,9 @@ pub fn deserialize_operation(message: proto::Operation) -> Result { .ok_or_else(|| anyhow!("missing operation variant"))? { proto::operation::Variant::Edit(edit) => { - Operation::Edit(deserialize_edit_operation(edit)) + Operation::Buffer(buffer::Operation::Edit(deserialize_edit_operation(edit))) } - proto::operation::Variant::Undo(undo) => Operation::Undo { + proto::operation::Variant::Undo(undo) => Operation::Buffer(buffer::Operation::Undo { lamport_timestamp: clock::Lamport { replica_id: undo.replica_id as ReplicaId, value: undo.lamport_timestamp, @@ -159,7 +185,7 @@ pub fn deserialize_operation(message: proto::Operation) -> Result { .collect(), version: undo.version.into(), }, - }, + }), proto::operation::Variant::UpdateSelections(message) => { let version = message.version.into(); let entries = message @@ -176,9 +202,9 @@ pub fn deserialize_operation(message: proto::Operation) -> Result { (range, state) }) .collect(); - let selections = AnchorRangeMap::from_raw(version, entries); + let selections = AnchorRangeMap::from_full_offset_ranges(version, entries); - Operation::UpdateSelections { + Operation::Buffer(buffer::Operation::UpdateSelections { set_id: clock::Lamport { replica_id: message.replica_id as ReplicaId, value: message.local_timestamp, @@ -188,20 +214,22 @@ pub fn deserialize_operation(message: proto::Operation) -> Result { value: message.lamport_timestamp, }, selections: Arc::from(selections), - } + }) + } + proto::operation::Variant::RemoveSelections(message) => { + Operation::Buffer(buffer::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::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 { + Operation::Buffer(buffer::Operation::SetActiveSelections { set_id: message.local_timestamp.map(|value| clock::Lamport { replica_id: message.replica_id as ReplicaId, value, @@ -210,7 +238,10 @@ pub fn deserialize_operation(message: proto::Operation) -> Result { replica_id: message.replica_id as ReplicaId, value: message.lamport_timestamp, }, - } + }) + } + proto::operation::Variant::UpdateDiagnostics(message) => { + Operation::UpdateDiagnostics(deserialize_diagnostics(message)) } }, ) @@ -241,7 +272,7 @@ pub fn deserialize_selection_set(set: proto::SelectionSet) -> SelectionSet { value: set.lamport_timestamp, }, active: set.is_active, - selections: Arc::new(AnchorRangeMap::from_raw( + selections: Arc::new(AnchorRangeMap::from_full_offset_ranges( set.version.into(), set.selections .into_iter() @@ -259,3 +290,26 @@ pub fn deserialize_selection_set(set: proto::SelectionSet) -> SelectionSet { )), } } + +pub fn deserialize_diagnostics(message: proto::DiagnosticSet) -> AnchorRangeMultimap { + AnchorRangeMultimap::from_full_offset_ranges( + message.version.into(), + Bias::Left, + Bias::Right, + message.diagnostics.into_iter().filter_map(|diagnostic| { + Some(( + FullOffset(diagnostic.start as usize)..FullOffset(diagnostic.end as usize), + Diagnostic { + severity: match proto::diagnostic::Severity::from_i32(diagnostic.severity)? { + proto::diagnostic::Severity::Error => DiagnosticSeverity::ERROR, + proto::diagnostic::Severity::Warning => DiagnosticSeverity::WARNING, + proto::diagnostic::Severity::Information => DiagnosticSeverity::INFORMATION, + proto::diagnostic::Severity::Hint => DiagnosticSeverity::HINT, + proto::diagnostic::Severity::None => return None, + }, + message: diagnostic.message, + }, + )) + }), + ) +} diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 2986ab9451..8753f27dba 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -228,6 +228,7 @@ message Buffer { string content = 2; repeated Operation.Edit history = 3; repeated SelectionSet selections = 4; + DiagnosticSet diagnostics = 5; } message SelectionSet { @@ -245,6 +246,27 @@ message Selection { bool reversed = 4; } +message DiagnosticSet { + repeated VectorClockEntry version = 1; + repeated Diagnostic diagnostics = 2; +} + +message Diagnostic { + uint64 start = 1; + uint64 end = 2; + Severity severity = 3; + string message = 4; + enum Severity { + None = 0; + Error = 1; + Warning = 2; + Information = 3; + Hint = 4; + } +} + + + message Operation { oneof variant { Edit edit = 1; @@ -252,6 +274,7 @@ message Operation { UpdateSelections update_selections = 3; RemoveSelections remove_selections = 4; SetActiveSelections set_active_selections = 5; + DiagnosticSet update_diagnostics = 6; } message Edit { diff --git a/crates/rpc/src/peer.rs b/crates/rpc/src/peer.rs index 251ffb5bb5..1a407e512f 100644 --- a/crates/rpc/src/peer.rs +++ b/crates/rpc/src/peer.rs @@ -398,6 +398,7 @@ mod tests { content: "path/one content".to_string(), history: vec![], selections: vec![], + diagnostics: None, }), } ); @@ -419,6 +420,7 @@ mod tests { content: "path/two content".to_string(), history: vec![], selections: vec![], + diagnostics: None, }), } ); @@ -449,6 +451,7 @@ mod tests { content: "path/one content".to_string(), history: vec![], selections: vec![], + diagnostics: None, }), } } @@ -460,6 +463,7 @@ mod tests { content: "path/two content".to_string(), history: vec![], selections: vec![], + diagnostics: None, }), } } diff --git a/script/server b/script/server index 491932c952..f85ab348e1 100755 --- a/script/server +++ b/script/server @@ -2,5 +2,5 @@ set -e -cd server +cd crates/server cargo run $@