diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 6ff7490181..fba9fe4d2f 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -160,7 +160,7 @@ impl ProjectDiagnosticsEditor { editor.set_vertical_scroll_margin(5, cx); editor }); - cx.subscribe(&editor, |_, _, event, cx| cx.emit(*event)) + cx.subscribe(&editor, |_, _, event, cx| cx.emit(event.clone())) .detach(); let project = project_handle.read(cx); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 4a7a7893a1..cc873cbffa 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -6587,8 +6587,16 @@ fn compute_scroll_position( scroll_position } -#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum Event { + ExcerptsAdded { + buffer: ModelHandle, + predecessor: ExcerptId, + excerpts: Vec<(ExcerptId, ExcerptRange)>, + }, + ExcerptsRemoved { + ids: Vec, + }, BufferEdited, Edited, Reparsed, @@ -6596,8 +6604,12 @@ pub enum Event { DirtyChanged, Saved, TitleChanged, - SelectionsChanged { local: bool }, - ScrollPositionChanged { local: bool }, + SelectionsChanged { + local: bool, + }, + ScrollPositionChanged { + local: bool, + }, Closed, IgnoredInput, } diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 8ac1f9a3fc..a980622077 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -38,7 +38,7 @@ fn test_edit_events(cx: &mut MutableAppContext) { event, Event::Edited | Event::BufferEdited | Event::DirtyChanged ) { - events.borrow_mut().push(("editor1", *event)); + events.borrow_mut().push(("editor1", event.clone())); } }) .detach(); @@ -53,7 +53,7 @@ fn test_edit_events(cx: &mut MutableAppContext) { event, Event::Edited | Event::BufferEdited | Event::DirtyChanged ) { - events.borrow_mut().push(("editor2", *event)); + events.borrow_mut().push(("editor2", event.clone())); } }) .detach(); diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index ad90f90b9d..2c0b8b834e 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -1,14 +1,16 @@ use crate::{ display_map::ToDisplayPoint, link_go_to_definition::hide_link_definition, - movement::surrounding_word, Anchor, Autoscroll, Editor, Event, ExcerptId, MultiBuffer, - MultiBufferSnapshot, NavigationData, ToPoint as _, FORMAT_TIMEOUT, + movement::surrounding_word, Anchor, Autoscroll, Editor, Event, ExcerptId, ExcerptRange, + MultiBuffer, MultiBufferSnapshot, NavigationData, ToPoint as _, FORMAT_TIMEOUT, }; use anyhow::{anyhow, Result}; +use collections::HashSet; use futures::FutureExt; use gpui::{ elements::*, geometry::vector::vec2f, AppContext, Entity, ModelHandle, MutableAppContext, RenderContext, Subscription, Task, View, ViewContext, ViewHandle, }; +use language::proto::serialize_anchor as serialize_text_anchor; use language::{Bias, Buffer, File as _, OffsetRangeExt, Point, SelectionGoal}; use project::{File, FormatTrigger, Project, ProjectEntryId, ProjectPath}; use rpc::proto::{self, update_view}; @@ -18,6 +20,7 @@ use std::{ borrow::Cow, cmp::{self, Ordering}, fmt::Write, + iter, ops::Range, path::{Path, PathBuf}, }; @@ -48,22 +51,75 @@ impl FollowableItem for Editor { return None; }; - let buffer = project.update(cx, |project, cx| { - project.open_buffer_by_id(state.buffer_id, cx) + let replica_id = project.read(cx).replica_id(); + let buffer_ids = state + .excerpts + .iter() + .map(|excerpt| excerpt.buffer_id) + .collect::>(); + let buffers = project.update(cx, |project, cx| { + buffer_ids + .iter() + .map(|id| project.open_buffer_by_id(*id, cx)) + .collect::>() }); + Some(cx.spawn(|mut cx| async move { - let buffer = buffer.await?; - let editor = pane - .read_with(&cx, |pane, cx| { - pane.items_of_type::().find(|editor| { - editor.read(cx).buffer.read(cx).as_singleton().as_ref() == Some(&buffer) + let mut buffers = futures::future::try_join_all(buffers).await?; + let editor = pane.read_with(&cx, |pane, cx| { + let mut editors = pane.items_of_type::(); + if state.singleton && buffers.len() == 1 { + editors.find(|editor| { + editor.read(cx).buffer.read(cx).as_singleton().as_ref() == Some(&buffers[0]) }) + } else if let Some(title) = &state.title { + editors.find(|editor| { + editor.read(cx).buffer().read(cx).title(cx).as_ref() == title + }) + } else { + None + } + }); + + let editor = editor.unwrap_or_else(|| { + pane.update(&mut cx, |_, cx| { + let multibuffer = cx.add_model(|cx| { + let mut multibuffer; + if state.singleton && buffers.len() == 1 { + multibuffer = MultiBuffer::singleton(buffers.pop().unwrap(), cx) + } else { + multibuffer = MultiBuffer::new(replica_id); + let mut excerpts = state.excerpts.into_iter().peekable(); + while let Some(excerpt) = excerpts.peek() { + let buffer_id = excerpt.buffer_id; + let buffer_excerpts = iter::from_fn(|| { + let excerpt = excerpts.peek()?; + (excerpt.buffer_id == buffer_id) + .then(|| excerpts.next().unwrap()) + }); + let buffer = + buffers.iter().find(|b| b.read(cx).remote_id() == buffer_id); + if let Some(buffer) = buffer { + multibuffer.push_excerpts( + buffer.clone(), + buffer_excerpts.filter_map(deserialize_excerpt_range), + cx, + ); + } + } + }; + + if let Some(title) = &state.title { + multibuffer = multibuffer.with_title(title.clone()) + } + + multibuffer + }); + + cx.add_view(|cx| Editor::for_multibuffer(multibuffer, Some(project), cx)) }) - .unwrap_or_else(|| { - pane.update(&mut cx, |_, cx| { - cx.add_view(|cx| Editor::for_buffer(buffer, Some(project), cx)) - }) - }); + }); + editor.update(&mut cx, |editor, cx| { let buffer = editor.buffer.read(cx).read(cx); let selections = state @@ -90,8 +146,9 @@ impl FollowableItem for Editor { ); } - Ok::<_, anyhow::Error>(()) + anyhow::Ok(()) })?; + Ok(editor) })) } @@ -122,9 +179,30 @@ impl FollowableItem for Editor { } fn to_state_proto(&self, cx: &AppContext) -> Option { - let buffer_id = self.buffer.read(cx).as_singleton()?.read(cx).remote_id(); + let buffer = self.buffer.read(cx); + let excerpts = buffer + .read(cx) + .excerpts() + .map(|(id, buffer, range)| proto::Excerpt { + id: id.to_proto(), + buffer_id: buffer.remote_id(), + context_start: Some(serialize_text_anchor(&range.context.start)), + context_end: Some(serialize_text_anchor(&range.context.end)), + primary_start: range + .primary + .as_ref() + .map(|range| serialize_text_anchor(&range.start)), + primary_end: range + .primary + .as_ref() + .map(|range| serialize_text_anchor(&range.end)), + }) + .collect(); + Some(proto::view::Variant::Editor(proto::view::Editor { - buffer_id, + singleton: buffer.is_singleton(), + title: (!buffer.is_singleton()).then(|| buffer.title(cx).into()), + excerpts, scroll_top_anchor: Some(serialize_anchor(&self.scroll_top_anchor)), scroll_x: self.scroll_position.x(), scroll_y: self.scroll_position.y(), @@ -141,13 +219,39 @@ impl FollowableItem for Editor { &self, event: &Self::Event, update: &mut Option, - _: &AppContext, + cx: &AppContext, ) -> bool { let update = update.get_or_insert_with(|| proto::update_view::Variant::Editor(Default::default())); match update { proto::update_view::Variant::Editor(update) => match event { + Event::ExcerptsAdded { + buffer, + predecessor, + excerpts, + } => { + let mut excerpts = excerpts.iter(); + if let Some((id, range)) = excerpts.next() { + update.inserted_excerpts.push(proto::ExcerptInsertion { + previous_excerpt_id: Some(predecessor.to_proto()), + excerpt: serialize_excerpt(buffer, id, range, cx), + }); + update.inserted_excerpts.extend(excerpts.map(|(id, range)| { + proto::ExcerptInsertion { + previous_excerpt_id: None, + excerpt: serialize_excerpt(buffer, id, range, cx), + } + })) + } + true + } + Event::ExcerptsRemoved { ids } => { + update + .deleted_excerpts + .extend(ids.iter().map(ExcerptId::to_proto)); + true + } Event::ScrollPositionChanged { .. } => { update.scroll_top_anchor = Some(serialize_anchor(&self.scroll_top_anchor)); update.scroll_x = self.scroll_position.x(); @@ -213,6 +317,28 @@ impl FollowableItem for Editor { } } +fn serialize_excerpt( + buffer: &ModelHandle, + id: &ExcerptId, + range: &ExcerptRange, + cx: &AppContext, +) -> Option { + Some(proto::Excerpt { + id: id.to_proto(), + buffer_id: buffer.read(cx).remote_id(), + context_start: Some(serialize_text_anchor(&range.context.start)), + context_end: Some(serialize_text_anchor(&range.context.end)), + primary_start: range + .primary + .as_ref() + .map(|r| serialize_text_anchor(&r.start)), + primary_end: range + .primary + .as_ref() + .map(|r| serialize_text_anchor(&r.end)), + }) +} + fn serialize_selection(selection: &Selection) -> proto::Selection { proto::Selection { id: selection.id as u64, @@ -225,10 +351,27 @@ fn serialize_selection(selection: &Selection) -> proto::Selection { fn serialize_anchor(anchor: &Anchor) -> proto::EditorAnchor { proto::EditorAnchor { excerpt_id: anchor.excerpt_id.to_proto(), - anchor: Some(language::proto::serialize_anchor(&anchor.text_anchor)), + anchor: Some(serialize_text_anchor(&anchor.text_anchor)), } } +fn deserialize_excerpt_range(excerpt: proto::Excerpt) -> Option> { + let context = { + let start = language::proto::deserialize_anchor(excerpt.context_start?)?; + let end = language::proto::deserialize_anchor(excerpt.context_end?)?; + start..end + }; + let primary = excerpt + .primary_start + .zip(excerpt.primary_end) + .and_then(|(start, end)| { + let start = language::proto::deserialize_anchor(start)?; + let end = language::proto::deserialize_anchor(end)?; + Some(start..end) + }); + Some(ExcerptRange { context, primary }) +} + fn deserialize_selection( buffer: &MultiBufferSnapshot, selection: proto::Selection, diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 35e26534d7..d0dd34a931 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -50,6 +50,26 @@ pub struct MultiBuffer { title: Option, } +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Event { + ExcerptsAdded { + buffer: ModelHandle, + predecessor: ExcerptId, + excerpts: Vec<(ExcerptId, ExcerptRange)>, + }, + ExcerptsRemoved { + ids: Vec, + }, + Edited, + Reloaded, + Reparsed, + Saved, + FileHandleChanged, + Closed, + DirtyChanged, + DiagnosticsUpdated, +} + #[derive(Clone)] struct History { next_transaction_id: TransactionId, @@ -1650,26 +1670,6 @@ impl MultiBuffer { } } -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum Event { - ExcerptsAdded { - buffer: ModelHandle, - predecessor: ExcerptId, - excerpts: Vec<(ExcerptId, ExcerptRange)>, - }, - ExcerptsRemoved { - ids: Vec, - }, - Edited, - Reloaded, - Reparsed, - Saved, - FileHandleChanged, - Closed, - DirtyChanged, - DiagnosticsUpdated, -} - impl Entity for MultiBuffer { type Event = Event; } @@ -2517,6 +2517,14 @@ impl MultiBufferSnapshot { } } + pub fn excerpts( + &self, + ) -> impl Iterator)> { + self.excerpts + .iter() + .map(|excerpt| (excerpt.id, &excerpt.buffer, excerpt.range.clone())) + } + pub fn excerpt_boundaries_in_range( &self, range: R, diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 3bc612bdc4..451c1539ea 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -847,10 +847,12 @@ message UpdateView { } message Editor { - repeated Selection selections = 1; - EditorAnchor scroll_top_anchor = 2; - float scroll_x = 3; - float scroll_y = 4; + repeated ExcerptInsertion inserted_excerpts = 1; + repeated uint64 deleted_excerpts = 2; + repeated Selection selections = 3; + EditorAnchor scroll_top_anchor = 4; + float scroll_x = 5; + float scroll_y = 6; } } @@ -863,11 +865,13 @@ message View { } message Editor { - uint64 buffer_id = 1; - repeated Selection selections = 2; - EditorAnchor scroll_top_anchor = 3; - float scroll_x = 4; - float scroll_y = 5; + bool singleton = 1; + optional string title = 2; + repeated Excerpt excerpts = 3; + repeated Selection selections = 4; + EditorAnchor scroll_top_anchor = 5; + float scroll_x = 6; + float scroll_y = 7; } } @@ -939,6 +943,20 @@ enum CursorShape { CursorHollow = 3; } +message ExcerptInsertion { + Excerpt excerpt = 1; + optional uint64 previous_excerpt_id = 2; +} + +message Excerpt { + uint64 id = 1; + uint64 buffer_id = 2; + Anchor context_start = 3; + Anchor context_end = 4; + Anchor primary_start = 5; + Anchor primary_end = 6; +} + message Anchor { uint32 replica_id = 1; uint32 local_timestamp = 2; diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 5e935a6ae3..41f48f4b5a 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -388,7 +388,7 @@ impl ProjectSearchView { }); // Subcribe to query_editor in order to reraise editor events for workspace item activation purposes cx.subscribe(&query_editor, |_, _, event, cx| { - cx.emit(ViewEvent::EditorEvent(*event)) + cx.emit(ViewEvent::EditorEvent(event.clone())) }) .detach(); @@ -405,7 +405,7 @@ impl ProjectSearchView { this.update_match_index(cx); } // Reraise editor events for workspace item activation purposes - cx.emit(ViewEvent::EditorEvent(*event)); + cx.emit(ViewEvent::EditorEvent(event.clone())); }) .detach(); diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index 5c2f7b7a51..5aa91ede8a 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -1496,6 +1496,10 @@ impl BufferSnapshot { &self.visible_text } + pub fn remote_id(&self) -> u64 { + self.remote_id + } + pub fn replica_id(&self) -> ReplicaId { self.replica_id }