Ensure collaborators cursor colors are the same in channel buffers as in projects
Co-authored-by: Mikayla <mikayla@zed.dev>
This commit is contained in:
parent
3268cce41a
commit
24141c2f16
10 changed files with 190 additions and 62 deletions
|
@ -21,8 +21,12 @@ pub struct ChannelBuffer {
|
||||||
_subscription: client::Subscription,
|
_subscription: client::Subscription,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum Event {
|
||||||
|
CollaboratorsChanged,
|
||||||
|
}
|
||||||
|
|
||||||
impl Entity for ChannelBuffer {
|
impl Entity for ChannelBuffer {
|
||||||
type Event = ();
|
type Event = Event;
|
||||||
|
|
||||||
fn release(&mut self, _: &mut AppContext) {
|
fn release(&mut self, _: &mut AppContext) {
|
||||||
self.client
|
self.client
|
||||||
|
@ -54,8 +58,9 @@ impl ChannelBuffer {
|
||||||
|
|
||||||
let collaborators = response.collaborators;
|
let collaborators = response.collaborators;
|
||||||
|
|
||||||
let buffer =
|
let buffer = cx.add_model(|_| {
|
||||||
cx.add_model(|cx| language::Buffer::new(response.replica_id as u16, base_text, cx));
|
language::Buffer::remote(response.buffer_id, response.replica_id as u16, base_text)
|
||||||
|
});
|
||||||
buffer.update(&mut cx, |buffer, cx| buffer.apply_ops(operations, cx))?;
|
buffer.update(&mut cx, |buffer, cx| buffer.apply_ops(operations, cx))?;
|
||||||
|
|
||||||
let subscription = client.subscribe_to_entity(channel_id)?;
|
let subscription = client.subscribe_to_entity(channel_id)?;
|
||||||
|
@ -111,6 +116,7 @@ impl ChannelBuffer {
|
||||||
|
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.collaborators.push(collaborator);
|
this.collaborators.push(collaborator);
|
||||||
|
cx.emit(Event::CollaboratorsChanged);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -134,6 +140,7 @@ impl ChannelBuffer {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
cx.emit(Event::CollaboratorsChanged);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -63,6 +63,10 @@ async fn test_core_channel_buffers(
|
||||||
|
|
||||||
// Client B sees the correct text, and then edits it
|
// Client B sees the correct text, and then edits it
|
||||||
let buffer_b = channel_buffer_b.read_with(cx_b, |buffer, _| buffer.buffer());
|
let buffer_b = channel_buffer_b.read_with(cx_b, |buffer, _| buffer.buffer());
|
||||||
|
assert_eq!(
|
||||||
|
buffer_b.read_with(cx_b, |buffer, _| buffer.remote_id()),
|
||||||
|
buffer_a.read_with(cx_a, |buffer, _| buffer.remote_id())
|
||||||
|
);
|
||||||
assert_eq!(buffer_text(&buffer_b, cx_b), "hello, cruel world");
|
assert_eq!(buffer_text(&buffer_b, cx_b), "hello, cruel world");
|
||||||
buffer_b.update(cx_b, |buffer, cx| {
|
buffer_b.update(cx_b, |buffer, cx| {
|
||||||
buffer.edit([(7..12, "beautiful")], None, cx)
|
buffer.edit([(7..12, "beautiful")], None, cx)
|
||||||
|
@ -138,6 +142,7 @@ async fn test_channel_buffer_replica_ids(
|
||||||
|
|
||||||
let active_call_a = cx_a.read(ActiveCall::global);
|
let active_call_a = cx_a.read(ActiveCall::global);
|
||||||
let active_call_b = cx_b.read(ActiveCall::global);
|
let active_call_b = cx_b.read(ActiveCall::global);
|
||||||
|
let active_call_c = cx_c.read(ActiveCall::global);
|
||||||
|
|
||||||
// Clients A and B join a channel.
|
// Clients A and B join a channel.
|
||||||
active_call_a
|
active_call_a
|
||||||
|
@ -190,7 +195,7 @@ async fn test_channel_buffer_replica_ids(
|
||||||
|
|
||||||
// Client C is in a separate project.
|
// Client C is in a separate project.
|
||||||
client_c.fs().insert_tree("/dir", json!({})).await;
|
client_c.fs().insert_tree("/dir", json!({})).await;
|
||||||
let (project_c, _) = client_c.build_local_project("/dir", cx_c).await;
|
let (separate_project_c, _) = client_c.build_local_project("/dir", cx_c).await;
|
||||||
|
|
||||||
// Note that each user has a different replica id in the projects vs the
|
// Note that each user has a different replica id in the projects vs the
|
||||||
// channel buffer.
|
// channel buffer.
|
||||||
|
@ -211,8 +216,14 @@ async fn test_channel_buffer_replica_ids(
|
||||||
.add_window(|cx| ChannelView::new(project_a.clone(), channel_buffer_a.clone(), None, cx));
|
.add_window(|cx| ChannelView::new(project_a.clone(), channel_buffer_a.clone(), None, cx));
|
||||||
let channel_window_b = cx_b
|
let channel_window_b = cx_b
|
||||||
.add_window(|cx| ChannelView::new(project_b.clone(), channel_buffer_b.clone(), None, cx));
|
.add_window(|cx| ChannelView::new(project_b.clone(), channel_buffer_b.clone(), None, cx));
|
||||||
let channel_window_c = cx_c
|
let channel_window_c = cx_c.add_window(|cx| {
|
||||||
.add_window(|cx| ChannelView::new(project_c.clone(), channel_buffer_c.clone(), None, cx));
|
ChannelView::new(
|
||||||
|
separate_project_c.clone(),
|
||||||
|
channel_buffer_c.clone(),
|
||||||
|
None,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
let channel_view_a = channel_window_a.root(cx_a);
|
let channel_view_a = channel_window_a.root(cx_a);
|
||||||
let channel_view_b = channel_window_b.root(cx_b);
|
let channel_view_b = channel_window_b.root(cx_b);
|
||||||
|
@ -222,24 +233,54 @@ async fn test_channel_buffer_replica_ids(
|
||||||
// so that they match the same users' replica ids in their shared project.
|
// so that they match the same users' replica ids in their shared project.
|
||||||
channel_view_a.read_with(cx_a, |view, cx| {
|
channel_view_a.read_with(cx_a, |view, cx| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
view.project_replica_ids_by_channel_buffer_replica_id(cx),
|
view.editor.read(cx).replica_id_map().unwrap(),
|
||||||
[(1, 0), (2, 1)].into_iter().collect::<HashMap<_, _>>()
|
&[(1, 0), (2, 1)].into_iter().collect::<HashMap<_, _>>()
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
channel_view_b.read_with(cx_b, |view, cx| {
|
channel_view_b.read_with(cx_b, |view, cx| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
view.project_replica_ids_by_channel_buffer_replica_id(cx),
|
view.editor.read(cx).replica_id_map().unwrap(),
|
||||||
[(1, 0), (2, 1)].into_iter().collect::<HashMap<u16, u16>>(),
|
&[(1, 0), (2, 1)].into_iter().collect::<HashMap<u16, u16>>(),
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
// Client C only sees themself, as they're not part of any shared project
|
// Client C only sees themself, as they're not part of any shared project
|
||||||
channel_view_c.read_with(cx_c, |view, cx| {
|
channel_view_c.read_with(cx_c, |view, cx| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
view.project_replica_ids_by_channel_buffer_replica_id(cx),
|
view.editor.read(cx).replica_id_map().unwrap(),
|
||||||
[(0, 0)].into_iter().collect::<HashMap<u16, u16>>(),
|
&[(0, 0)].into_iter().collect::<HashMap<u16, u16>>(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Client C joins the project that clients A and B are in.
|
||||||
|
active_call_c
|
||||||
|
.update(cx_c, |call, cx| call.join_channel(channel_id, cx))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let project_c = client_c.build_remote_project(shared_project_id, cx_c).await;
|
||||||
|
deterministic.run_until_parked();
|
||||||
|
project_c.read_with(cx_c, |project, _| {
|
||||||
|
assert_eq!(project.replica_id(), 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
// For clients A and B, client C's replica id in the channel buffer is
|
||||||
|
// now mapped to their replica id in the shared project.
|
||||||
|
channel_view_a.read_with(cx_a, |view, cx| {
|
||||||
|
assert_eq!(
|
||||||
|
view.editor.read(cx).replica_id_map().unwrap(),
|
||||||
|
&[(1, 0), (2, 1), (0, 2)]
|
||||||
|
.into_iter()
|
||||||
|
.collect::<HashMap<_, _>>()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
channel_view_b.read_with(cx_b, |view, cx| {
|
||||||
|
assert_eq!(
|
||||||
|
view.editor.read(cx).replica_id_map().unwrap(),
|
||||||
|
&[(1, 0), (2, 1), (0, 2)]
|
||||||
|
.into_iter()
|
||||||
|
.collect::<HashMap<_, _>>(),
|
||||||
|
)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use channel::channel_buffer::ChannelBuffer;
|
use channel::channel_buffer::{self, ChannelBuffer};
|
||||||
use client::proto;
|
use client::proto;
|
||||||
use clock::ReplicaId;
|
use clock::ReplicaId;
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
|
@ -24,7 +24,7 @@ pub(crate) fn init(cx: &mut AppContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ChannelView {
|
pub struct ChannelView {
|
||||||
editor: ViewHandle<Editor>,
|
pub editor: ViewHandle<Editor>,
|
||||||
project: ModelHandle<Project>,
|
project: ModelHandle<Project>,
|
||||||
channel_buffer: ModelHandle<ChannelBuffer>,
|
channel_buffer: ModelHandle<ChannelBuffer>,
|
||||||
remote_id: Option<ViewId>,
|
remote_id: Option<ViewId>,
|
||||||
|
@ -43,6 +43,10 @@ impl ChannelView {
|
||||||
let editor = cx.add_view(|cx| Editor::for_buffer(buffer, None, cx));
|
let editor = cx.add_view(|cx| Editor::for_buffer(buffer, None, cx));
|
||||||
let _editor_event_subscription = cx.subscribe(&editor, |_, _, e, cx| cx.emit(e.clone()));
|
let _editor_event_subscription = cx.subscribe(&editor, |_, _, e, cx| cx.emit(e.clone()));
|
||||||
|
|
||||||
|
cx.subscribe(&project, Self::handle_project_event).detach();
|
||||||
|
cx.subscribe(&channel_buffer, Self::handle_channel_buffer_event)
|
||||||
|
.detach();
|
||||||
|
|
||||||
let this = Self {
|
let this = Self {
|
||||||
editor,
|
editor,
|
||||||
project,
|
project,
|
||||||
|
@ -50,38 +54,70 @@ impl ChannelView {
|
||||||
remote_id: None,
|
remote_id: None,
|
||||||
_editor_event_subscription,
|
_editor_event_subscription,
|
||||||
};
|
};
|
||||||
let mapping = this.project_replica_ids_by_channel_buffer_replica_id(cx);
|
this.refresh_replica_id_map(cx);
|
||||||
this.editor
|
|
||||||
.update(cx, |editor, cx| editor.set_replica_id_mapping(mapping, cx));
|
|
||||||
|
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Channel Buffer Replica ID -> Project Replica ID
|
fn handle_project_event(
|
||||||
pub fn project_replica_ids_by_channel_buffer_replica_id(
|
&mut self,
|
||||||
&self,
|
_: ModelHandle<Project>,
|
||||||
cx: &AppContext,
|
event: &project::Event,
|
||||||
) -> HashMap<ReplicaId, ReplicaId> {
|
cx: &mut ViewContext<Self>,
|
||||||
let project = self.project.read(cx);
|
) {
|
||||||
let mut result = HashMap::default();
|
match event {
|
||||||
result.insert(
|
project::Event::RemoteIdChanged(_) => {}
|
||||||
self.channel_buffer.read(cx).replica_id(cx),
|
project::Event::DisconnectedFromHost => {}
|
||||||
project.replica_id(),
|
project::Event::Closed => {}
|
||||||
);
|
project::Event::CollaboratorUpdated { .. } => {}
|
||||||
for collaborator in self.channel_buffer.read(cx).collaborators() {
|
project::Event::CollaboratorLeft(_) => {}
|
||||||
let project_replica_id =
|
project::Event::CollaboratorJoined(_) => {}
|
||||||
project
|
_ => return,
|
||||||
.collaborators()
|
|
||||||
.values()
|
|
||||||
.find_map(|project_collaborator| {
|
|
||||||
(project_collaborator.user_id == collaborator.user_id)
|
|
||||||
.then_some(project_collaborator.replica_id)
|
|
||||||
});
|
|
||||||
if let Some(project_replica_id) = project_replica_id {
|
|
||||||
result.insert(collaborator.replica_id as ReplicaId, project_replica_id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
result
|
self.refresh_replica_id_map(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_channel_buffer_event(
|
||||||
|
&mut self,
|
||||||
|
_: ModelHandle<ChannelBuffer>,
|
||||||
|
_: &channel_buffer::Event,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) {
|
||||||
|
self.refresh_replica_id_map(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build a mapping of channel buffer replica ids to the corresponding
|
||||||
|
/// replica ids in the current project.
|
||||||
|
///
|
||||||
|
/// Using this mapping, a given user can be displayed with the same color
|
||||||
|
/// in the channel buffer as in other files in the project. Users who are
|
||||||
|
/// in the channel buffer but not the project will not have a color.
|
||||||
|
fn refresh_replica_id_map(&self, cx: &mut ViewContext<Self>) {
|
||||||
|
let mut project_replica_ids_by_channel_buffer_replica_id = HashMap::default();
|
||||||
|
let project = self.project.read(cx);
|
||||||
|
let channel_buffer = self.channel_buffer.read(cx);
|
||||||
|
project_replica_ids_by_channel_buffer_replica_id
|
||||||
|
.insert(channel_buffer.replica_id(cx), project.replica_id());
|
||||||
|
project_replica_ids_by_channel_buffer_replica_id.extend(
|
||||||
|
channel_buffer
|
||||||
|
.collaborators()
|
||||||
|
.iter()
|
||||||
|
.filter_map(|channel_buffer_collaborator| {
|
||||||
|
project
|
||||||
|
.collaborators()
|
||||||
|
.values()
|
||||||
|
.find_map(|project_collaborator| {
|
||||||
|
(project_collaborator.user_id == channel_buffer_collaborator.user_id)
|
||||||
|
.then_some((
|
||||||
|
channel_buffer_collaborator.replica_id as ReplicaId,
|
||||||
|
project_collaborator.replica_id,
|
||||||
|
))
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.editor.update(cx, |editor, cx| {
|
||||||
|
editor.set_replica_id_map(Some(project_replica_ids_by_channel_buffer_replica_id), cx)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1606,12 +1606,16 @@ impl Editor {
|
||||||
self.read_only = read_only;
|
self.read_only = read_only;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_replica_id_mapping(
|
pub fn replica_id_map(&self) -> Option<&HashMap<ReplicaId, ReplicaId>> {
|
||||||
|
self.replica_id_mapping.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_replica_id_map(
|
||||||
&mut self,
|
&mut self,
|
||||||
mapping: HashMap<ReplicaId, ReplicaId>,
|
mapping: Option<HashMap<ReplicaId, ReplicaId>>,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
self.replica_id_mapping = Some(mapping);
|
self.replica_id_mapping = mapping;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -62,6 +62,7 @@ struct SelectionLayout {
|
||||||
head: DisplayPoint,
|
head: DisplayPoint,
|
||||||
cursor_shape: CursorShape,
|
cursor_shape: CursorShape,
|
||||||
is_newest: bool,
|
is_newest: bool,
|
||||||
|
is_local: bool,
|
||||||
range: Range<DisplayPoint>,
|
range: Range<DisplayPoint>,
|
||||||
active_rows: Range<u32>,
|
active_rows: Range<u32>,
|
||||||
}
|
}
|
||||||
|
@ -73,6 +74,7 @@ impl SelectionLayout {
|
||||||
cursor_shape: CursorShape,
|
cursor_shape: CursorShape,
|
||||||
map: &DisplaySnapshot,
|
map: &DisplaySnapshot,
|
||||||
is_newest: bool,
|
is_newest: bool,
|
||||||
|
is_local: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let point_selection = selection.map(|p| p.to_point(&map.buffer_snapshot));
|
let point_selection = selection.map(|p| p.to_point(&map.buffer_snapshot));
|
||||||
let display_selection = point_selection.map(|p| p.to_display_point(map));
|
let display_selection = point_selection.map(|p| p.to_display_point(map));
|
||||||
|
@ -109,6 +111,7 @@ impl SelectionLayout {
|
||||||
head,
|
head,
|
||||||
cursor_shape,
|
cursor_shape,
|
||||||
is_newest,
|
is_newest,
|
||||||
|
is_local,
|
||||||
range,
|
range,
|
||||||
active_rows,
|
active_rows,
|
||||||
}
|
}
|
||||||
|
@ -763,7 +766,6 @@ impl EditorElement {
|
||||||
cx: &mut PaintContext<Editor>,
|
cx: &mut PaintContext<Editor>,
|
||||||
) {
|
) {
|
||||||
let style = &self.style;
|
let style = &self.style;
|
||||||
let local_replica_id = editor.replica_id(cx);
|
|
||||||
let scroll_position = layout.position_map.snapshot.scroll_position();
|
let scroll_position = layout.position_map.snapshot.scroll_position();
|
||||||
let start_row = layout.visible_display_row_range.start;
|
let start_row = layout.visible_display_row_range.start;
|
||||||
let scroll_top = scroll_position.y() * layout.position_map.line_height;
|
let scroll_top = scroll_position.y() * layout.position_map.line_height;
|
||||||
|
@ -852,15 +854,13 @@ impl EditorElement {
|
||||||
|
|
||||||
for (replica_id, selections) in &layout.selections {
|
for (replica_id, selections) in &layout.selections {
|
||||||
let replica_id = *replica_id;
|
let replica_id = *replica_id;
|
||||||
let selection_style = style.replica_selection_style(replica_id);
|
let selection_style = if let Some(replica_id) = replica_id {
|
||||||
|
style.replica_selection_style(replica_id)
|
||||||
|
} else {
|
||||||
|
&style.absent_selection
|
||||||
|
};
|
||||||
|
|
||||||
for selection in selections {
|
for selection in selections {
|
||||||
if !selection.range.is_empty()
|
|
||||||
&& (replica_id == local_replica_id
|
|
||||||
|| Some(replica_id) == editor.leader_replica_id)
|
|
||||||
{
|
|
||||||
invisible_display_ranges.push(selection.range.clone());
|
|
||||||
}
|
|
||||||
self.paint_highlighted_range(
|
self.paint_highlighted_range(
|
||||||
scene,
|
scene,
|
||||||
selection.range.clone(),
|
selection.range.clone(),
|
||||||
|
@ -874,7 +874,10 @@ impl EditorElement {
|
||||||
bounds,
|
bounds,
|
||||||
);
|
);
|
||||||
|
|
||||||
if editor.show_local_cursors(cx) || replica_id != local_replica_id {
|
if selection.is_local && !selection.range.is_empty() {
|
||||||
|
invisible_display_ranges.push(selection.range.clone());
|
||||||
|
}
|
||||||
|
if !selection.is_local || editor.show_local_cursors(cx) {
|
||||||
let cursor_position = selection.head;
|
let cursor_position = selection.head;
|
||||||
if layout
|
if layout
|
||||||
.visible_display_row_range
|
.visible_display_row_range
|
||||||
|
@ -2124,7 +2127,7 @@ impl Element<Editor> for EditorElement {
|
||||||
.anchor_before(DisplayPoint::new(end_row, 0).to_offset(&snapshot, Bias::Right))
|
.anchor_before(DisplayPoint::new(end_row, 0).to_offset(&snapshot, Bias::Right))
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut selections: Vec<(ReplicaId, Vec<SelectionLayout>)> = Vec::new();
|
let mut selections: Vec<(Option<ReplicaId>, Vec<SelectionLayout>)> = Vec::new();
|
||||||
let mut active_rows = BTreeMap::new();
|
let mut active_rows = BTreeMap::new();
|
||||||
let mut fold_ranges = Vec::new();
|
let mut fold_ranges = Vec::new();
|
||||||
let is_singleton = editor.is_singleton(cx);
|
let is_singleton = editor.is_singleton(cx);
|
||||||
|
@ -2155,8 +2158,14 @@ impl Element<Editor> for EditorElement {
|
||||||
.buffer_snapshot
|
.buffer_snapshot
|
||||||
.remote_selections_in_range(&(start_anchor..end_anchor))
|
.remote_selections_in_range(&(start_anchor..end_anchor))
|
||||||
{
|
{
|
||||||
|
let replica_id = if let Some(mapping) = &editor.replica_id_mapping {
|
||||||
|
mapping.get(&replica_id).copied()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
// The local selections match the leader's selections.
|
// The local selections match the leader's selections.
|
||||||
if Some(replica_id) == editor.leader_replica_id {
|
if replica_id.is_some() && replica_id == editor.leader_replica_id {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
remote_selections
|
remote_selections
|
||||||
|
@ -2168,6 +2177,7 @@ impl Element<Editor> for EditorElement {
|
||||||
cursor_shape,
|
cursor_shape,
|
||||||
&snapshot.display_snapshot,
|
&snapshot.display_snapshot,
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
selections.extend(remote_selections);
|
selections.extend(remote_selections);
|
||||||
|
@ -2191,6 +2201,7 @@ impl Element<Editor> for EditorElement {
|
||||||
editor.cursor_shape,
|
editor.cursor_shape,
|
||||||
&snapshot.display_snapshot,
|
&snapshot.display_snapshot,
|
||||||
is_newest,
|
is_newest,
|
||||||
|
true,
|
||||||
);
|
);
|
||||||
if is_newest {
|
if is_newest {
|
||||||
newest_selection_head = Some(layout.head);
|
newest_selection_head = Some(layout.head);
|
||||||
|
@ -2206,11 +2217,18 @@ impl Element<Editor> for EditorElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render the local selections in the leader's color when following.
|
// Render the local selections in the leader's color when following.
|
||||||
let local_replica_id = editor
|
let local_replica_id = if let Some(leader_replica_id) = editor.leader_replica_id {
|
||||||
.leader_replica_id
|
leader_replica_id
|
||||||
.unwrap_or_else(|| editor.replica_id(cx));
|
} else {
|
||||||
|
let replica_id = editor.replica_id(cx);
|
||||||
|
if let Some(mapping) = &editor.replica_id_mapping {
|
||||||
|
mapping.get(&replica_id).copied().unwrap_or(replica_id)
|
||||||
|
} else {
|
||||||
|
replica_id
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
selections.push((local_replica_id, layouts));
|
selections.push((Some(local_replica_id), layouts));
|
||||||
}
|
}
|
||||||
|
|
||||||
let scrollbar_settings = &settings::get::<EditorSettings>(cx).scrollbar;
|
let scrollbar_settings = &settings::get::<EditorSettings>(cx).scrollbar;
|
||||||
|
@ -2591,7 +2609,7 @@ pub struct LayoutState {
|
||||||
blocks: Vec<BlockLayout>,
|
blocks: Vec<BlockLayout>,
|
||||||
highlighted_ranges: Vec<(Range<DisplayPoint>, Color)>,
|
highlighted_ranges: Vec<(Range<DisplayPoint>, Color)>,
|
||||||
fold_ranges: Vec<(BufferRow, Range<DisplayPoint>, Color)>,
|
fold_ranges: Vec<(BufferRow, Range<DisplayPoint>, Color)>,
|
||||||
selections: Vec<(ReplicaId, Vec<SelectionLayout>)>,
|
selections: Vec<(Option<ReplicaId>, Vec<SelectionLayout>)>,
|
||||||
scrollbar_row_range: Range<f32>,
|
scrollbar_row_range: Range<f32>,
|
||||||
show_scrollbars: bool,
|
show_scrollbars: bool,
|
||||||
is_singleton: bool,
|
is_singleton: bool,
|
||||||
|
|
|
@ -359,6 +359,14 @@ impl Buffer {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn remote(remote_id: u64, replica_id: ReplicaId, base_text: String) -> Self {
|
||||||
|
Self::build(
|
||||||
|
TextBuffer::new(replica_id, remote_id, base_text),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn from_proto(
|
pub fn from_proto(
|
||||||
replica_id: ReplicaId,
|
replica_id: ReplicaId,
|
||||||
message: proto::BufferState,
|
message: proto::BufferState,
|
||||||
|
|
|
@ -282,6 +282,7 @@ pub enum Event {
|
||||||
old_peer_id: proto::PeerId,
|
old_peer_id: proto::PeerId,
|
||||||
new_peer_id: proto::PeerId,
|
new_peer_id: proto::PeerId,
|
||||||
},
|
},
|
||||||
|
CollaboratorJoined(proto::PeerId),
|
||||||
CollaboratorLeft(proto::PeerId),
|
CollaboratorLeft(proto::PeerId),
|
||||||
RefreshInlayHints,
|
RefreshInlayHints,
|
||||||
}
|
}
|
||||||
|
@ -5931,6 +5932,7 @@ impl Project {
|
||||||
let collaborator = Collaborator::from_proto(collaborator)?;
|
let collaborator = Collaborator::from_proto(collaborator)?;
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.shared_buffers.remove(&collaborator.peer_id);
|
this.shared_buffers.remove(&collaborator.peer_id);
|
||||||
|
cx.emit(Event::CollaboratorJoined(collaborator.peer_id));
|
||||||
this.collaborators
|
this.collaborators
|
||||||
.insert(collaborator.peer_id, collaborator);
|
.insert(collaborator.peer_id, collaborator);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::{cmp::Ordering, fmt::Debug};
|
||||||
|
|
||||||
use crate::{Bias, Dimension, Edit, Item, KeyedItem, SeekTarget, SumTree, Summary};
|
use crate::{Bias, Dimension, Edit, Item, KeyedItem, SeekTarget, SumTree, Summary};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, PartialEq, Eq)]
|
||||||
pub struct TreeMap<K, V>(SumTree<MapEntry<K, V>>)
|
pub struct TreeMap<K, V>(SumTree<MapEntry<K, V>>)
|
||||||
where
|
where
|
||||||
K: Clone + Debug + Default + Ord,
|
K: Clone + Debug + Default + Ord,
|
||||||
|
@ -162,6 +162,16 @@ impl<K: Clone + Debug + Default + Ord, V: Clone + Debug> TreeMap<K, V> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<K: Debug, V: Debug> Debug for TreeMap<K, V>
|
||||||
|
where
|
||||||
|
K: Clone + Debug + Default + Ord,
|
||||||
|
V: Clone + Debug,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_map().entries(self.iter()).finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct MapSeekTargetAdaptor<'a, T>(&'a T);
|
struct MapSeekTargetAdaptor<'a, T>(&'a T);
|
||||||
|
|
||||||
|
|
|
@ -756,6 +756,7 @@ pub struct Editor {
|
||||||
pub line_number: Color,
|
pub line_number: Color,
|
||||||
pub line_number_active: Color,
|
pub line_number_active: Color,
|
||||||
pub guest_selections: Vec<SelectionStyle>,
|
pub guest_selections: Vec<SelectionStyle>,
|
||||||
|
pub absent_selection: SelectionStyle,
|
||||||
pub syntax: Arc<SyntaxTheme>,
|
pub syntax: Arc<SyntaxTheme>,
|
||||||
pub hint: HighlightStyle,
|
pub hint: HighlightStyle,
|
||||||
pub suggestion: HighlightStyle,
|
pub suggestion: HighlightStyle,
|
||||||
|
|
|
@ -184,6 +184,7 @@ export default function editor(): any {
|
||||||
theme.players[6],
|
theme.players[6],
|
||||||
theme.players[7],
|
theme.players[7],
|
||||||
],
|
],
|
||||||
|
absent_selection: theme.players[7],
|
||||||
autocomplete: {
|
autocomplete: {
|
||||||
background: background(theme.middle),
|
background: background(theme.middle),
|
||||||
corner_radius: 8,
|
corner_radius: 8,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue