Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions crates/editor/src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1910,6 +1910,14 @@ impl Editor {
project,
window,
|editor, _, event, window, cx| match event {
project::Event::RemoteIdChanged(Some(_))
| project::Event::Reshared
| project::Event::HostReshared => {
// The per-change selection broadcast is skipped while the
// project is unshared, so re-publish current selections
// once it becomes (re)shared.
editor.republish_active_selections(window, cx);
}
project::Event::RefreshCodeLens => {
editor.refresh_code_lenses(None, window, cx);
}
Expand Down Expand Up @@ -11093,13 +11101,30 @@ pub trait CollaborationHub {
fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;

/// Whether local selection changes need to be broadcast to other
/// participants. Defaults to `true`; hubs that can be certain there is no
/// audience (e.g. an unshared local project) override this so the editor can
/// skip the per-keystroke `set_active_selections` work, which is
/// `O(selections)` and pure overhead when nobody is observing.
fn should_broadcast_selections(&self, _: &App) -> bool {
true
}
}

impl CollaborationHub for Entity<Project> {
fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
self.read(cx).collaborators()
}

fn should_broadcast_selections(&self, cx: &App) -> bool {
// `is_shared()` is true for a host that has shared the project and for a
// collab guest, and stays correct even before peer-join notifications
// have propagated locally (unlike a live collaborator count). A purely
// local project has no audience, so selections need not be broadcast.
self.read(cx).is_shared()
}

fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
self.read(cx).user_store().read(cx).participant_indices()
}
Expand Down
9 changes: 5 additions & 4 deletions crates/editor/src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -503,11 +503,12 @@ impl Editor {
this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
if this.hard_wrap.is_some() {
let latest: Range<Point> = this.selections.newest(&map).range();
// Reuse the post-edit snapshot captured in `map` above; the buffer
// is not mutated between there and here (only selections move), so a
// fresh `buffer().snapshot(cx)` would be redundant.
if latest.is_empty()
&& this
.buffer()
.read(cx)
.snapshot(cx)
&& map
.buffer_snapshot()
.line_len(MultiBufferRow(latest.start.row))
== latest.start.column
{
Expand Down
36 changes: 35 additions & 1 deletion crates/editor/src/selection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1514,6 +1514,34 @@ impl Editor {
}
}

/// Re-publishes the current selections to collaborators immediately. The
/// per-change broadcast in `selections_did_change` is skipped while the
/// project is unshared, so when a project becomes shared this is called to
/// re-establish this editor's cursor for peers, who would otherwise not see
/// it until the next selection change.
pub(crate) fn republish_active_selections(
&mut self,
window: &mut Window,
cx: &mut Context<Self>,
) {
let should_broadcast_selections = self
.collaboration_hub()
.is_some_and(|hub| hub.should_broadcast_selections(cx));
if should_broadcast_selections
&& self.focus_handle.is_focused(window)
&& self.leader_id.is_none()
{
self.buffer.update(cx, |buffer, cx| {
buffer.set_active_selections(
&self.selections.disjoint_anchors_arc(),
self.selections.line_mode(),
self.cursor_shape,
cx,
)
});
}
}

fn selections_did_change(
&mut self,
local: bool,
Expand Down Expand Up @@ -1552,7 +1580,13 @@ impl Editor {

let selection_anchors = self.selections.disjoint_anchors_arc();

if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
let should_broadcast_selections = self
.collaboration_hub()
.is_some_and(|hub| hub.should_broadcast_selections(cx));
if should_broadcast_selections
&& self.focus_handle.is_focused(window)
&& self.leader_id.is_none()
{
self.buffer.update(cx, |buffer, cx| {
buffer.set_active_selections(
&selection_anchors,
Expand Down