Start on rendering remote selections

Antonio Scandurra created

Change summary

zed/src/editor.rs         | 111 +++++++++++++++-----------------
zed/src/editor/buffer.rs  |  51 +++++++++-----
zed/src/editor/element.rs | 137 +++++++++++++++++++++++-----------------
3 files changed, 161 insertions(+), 138 deletions(-)

Detailed changes

zed/src/editor.rs 🔗

@@ -622,8 +622,8 @@ impl Editor {
 
     fn end_selection(&mut self, cx: &mut ViewContext<Self>) {
         if let Some(selection) = self.pending_selection.take() {
-            let ix = self.selection_insertion_index(&selection.start, cx.as_ref());
             let mut selections = self.selections(cx.as_ref()).to_vec();
+            let ix = self.selection_insertion_index(&selections, &selection.start, cx.as_ref());
             selections.insert(ix, selection);
             self.update_selections(selections, false, cx);
         } else {
@@ -1916,31 +1916,53 @@ impl Editor {
         }
     }
 
+    pub fn active_selection_sets<'a>(
+        &'a self,
+        cx: &'a AppContext,
+    ) -> impl 'a + Iterator<Item = SelectionSetId> {
+        self.buffer
+            .read(cx)
+            .selection_sets()
+            .filter(|(_, set)| set.active)
+            .map(|(set_id, _)| *set_id)
+    }
+
     pub fn selections_in_range<'a>(
         &'a self,
+        set_id: SelectionSetId,
         range: Range<DisplayPoint>,
         cx: &'a AppContext,
     ) -> impl 'a + Iterator<Item = Range<DisplayPoint>> {
+        let buffer = self.buffer.read(cx);
+        let selections = &buffer.selection_set(set_id).unwrap().selections;
         let start = self.display_map.anchor_before(range.start, Bias::Left, cx);
-        let start_index = self.selection_insertion_index(&start, cx);
-        let pending_selection = self.pending_selection.as_ref().and_then(|s| {
-            let selection_range = s.display_range(&self.display_map, cx);
-            if selection_range.start <= range.end || selection_range.end <= range.end {
-                Some(selection_range)
-            } else {
-                None
-            }
-        });
-        self.selections(cx)[start_index..]
+        let start_index = self.selection_insertion_index(selections, &start, cx);
+        let pending_selection = if set_id.replica_id == self.buffer.read(cx).replica_id() {
+            self.pending_selection.as_ref().and_then(|s| {
+                let selection_range = s.display_range(&self.display_map, cx);
+                if selection_range.start <= range.end || selection_range.end <= range.end {
+                    Some(selection_range)
+                } else {
+                    None
+                }
+            })
+        } else {
+            None
+        };
+        selections[start_index..]
             .iter()
             .map(move |s| s.display_range(&self.display_map, cx))
             .take_while(move |r| r.start <= range.end || r.end <= range.end)
             .chain(pending_selection)
     }
 
-    fn selection_insertion_index(&self, start: &Anchor, cx: &AppContext) -> usize {
+    fn selection_insertion_index(
+        &self,
+        selections: &[Selection],
+        start: &Anchor,
+        cx: &AppContext,
+    ) -> usize {
         let buffer = self.buffer.read(cx);
-        let selections = self.selections(cx);
         match selections.binary_search_by(|probe| probe.start.cmp(&start, buffer).unwrap()) {
             Ok(index) => index,
             Err(index) => {
@@ -1956,10 +1978,11 @@ impl Editor {
     }
 
     fn selections<'a>(&self, cx: &'a AppContext) -> &'a [Selection] {
-        self.buffer
-            .read(cx)
-            .selections(self.selection_set_id)
+        let buffer = self.buffer.read(cx);
+        &buffer
+            .selection_set(self.selection_set_id)
             .unwrap()
+            .selections
     }
 
     fn update_selections(
@@ -2551,14 +2574,8 @@ mod tests {
         });
 
         let view = buffer_view.read(cx);
-        let selections = view
-            .selections_in_range(
-                DisplayPoint::zero()..view.max_point(cx.as_ref()),
-                cx.as_ref(),
-            )
-            .collect::<Vec<_>>();
         assert_eq!(
-            selections,
+            view.selection_ranges(cx.as_ref()),
             [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)]
         );
 
@@ -2567,14 +2584,8 @@ mod tests {
         });
 
         let view = buffer_view.read(cx);
-        let selections = view
-            .selections_in_range(
-                DisplayPoint::zero()..view.max_point(cx.as_ref()),
-                cx.as_ref(),
-            )
-            .collect::<Vec<_>>();
         assert_eq!(
-            selections,
+            view.selection_ranges(cx.as_ref()),
             [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
         );
 
@@ -2583,14 +2594,8 @@ mod tests {
         });
 
         let view = buffer_view.read(cx);
-        let selections = view
-            .selections_in_range(
-                DisplayPoint::zero()..view.max_point(cx.as_ref()),
-                cx.as_ref(),
-            )
-            .collect::<Vec<_>>();
         assert_eq!(
-            selections,
+            view.selection_ranges(cx.as_ref()),
             [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
         );
 
@@ -2600,14 +2605,8 @@ mod tests {
         });
 
         let view = buffer_view.read(cx);
-        let selections = view
-            .selections_in_range(
-                DisplayPoint::zero()..view.max_point(cx.as_ref()),
-                cx.as_ref(),
-            )
-            .collect::<Vec<_>>();
         assert_eq!(
-            selections,
+            view.selection_ranges(cx.as_ref()),
             [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
         );
 
@@ -2617,14 +2616,8 @@ mod tests {
         });
 
         let view = buffer_view.read(cx);
-        let selections = view
-            .selections_in_range(
-                DisplayPoint::zero()..view.max_point(cx.as_ref()),
-                cx.as_ref(),
-            )
-            .collect::<Vec<_>>();
         assert_eq!(
-            selections,
+            view.selection_ranges(cx.as_ref()),
             [
                 DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1),
                 DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)
@@ -2636,14 +2629,8 @@ mod tests {
         });
 
         let view = buffer_view.read(cx);
-        let selections = view
-            .selections_in_range(
-                DisplayPoint::zero()..view.max_point(cx.as_ref()),
-                cx.as_ref(),
-            )
-            .collect::<Vec<_>>();
         assert_eq!(
-            selections,
+            view.selection_ranges(cx.as_ref()),
             [DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)]
         );
     }
@@ -4141,8 +4128,12 @@ mod tests {
 
     impl Editor {
         fn selection_ranges(&self, cx: &AppContext) -> Vec<Range<DisplayPoint>> {
-            self.selections_in_range(DisplayPoint::zero()..self.max_point(cx), cx)
-                .collect::<Vec<_>>()
+            self.selections_in_range(
+                self.selection_set_id,
+                DisplayPoint::zero()..self.max_point(cx),
+                cx,
+            )
+            .collect::<Vec<_>>()
         }
     }
 

zed/src/editor/buffer.rs 🔗

@@ -131,9 +131,9 @@ pub struct Buffer {
 }
 
 #[derive(Clone, Debug, Eq, PartialEq)]
-struct SelectionSet {
-    selections: Arc<[Selection]>,
-    active: bool,
+pub struct SelectionSet {
+    pub selections: Arc<[Selection]>,
+    pub active: bool,
 }
 
 #[derive(Clone)]
@@ -583,6 +583,10 @@ impl Buffer {
         result
     }
 
+    pub fn replica_id(&self) -> ReplicaId {
+        self.local_clock.replica_id
+    }
+
     pub fn snapshot(&self) -> Snapshot {
         Snapshot {
             text: self.visible_text.clone(),
@@ -1200,8 +1204,18 @@ impl Buffer {
         set_id: Option<SelectionSetId>,
         cx: &mut ModelContext<Self>,
     ) -> Result<()> {
-        if set_id.is_some() && !self.selections.contains_key(set_id.as_ref().unwrap()) {
-            return Err(anyhow!("invalid selection set id {:?}", set_id));
+        if let Some(set_id) = set_id {
+            assert_eq!(set_id.replica_id, self.replica_id());
+        }
+
+        for (id, set) in &mut self.selections {
+            if id.replica_id == self.local_clock.replica_id {
+                if Some(*id) == set_id {
+                    set.active = true;
+                } else {
+                    set.active = false;
+                }
+            }
         }
 
         let lamport_timestamp = self.lamport_clock.tick();
@@ -1236,13 +1250,16 @@ impl Buffer {
         Ok(())
     }
 
-    pub fn selections(&self, set_id: SelectionSetId) -> Result<&[Selection]> {
+    pub fn selection_set(&self, set_id: SelectionSetId) -> Result<&SelectionSet> {
         self.selections
             .get(&set_id)
-            .map(|s| s.selections.as_ref())
             .ok_or_else(|| anyhow!("invalid selection set id {:?}", set_id))
     }
 
+    pub fn selection_sets(&self) -> impl Iterator<Item = (&SelectionSetId, &SelectionSet)> {
+        self.selections.iter()
+    }
+
     pub fn apply_ops<I: IntoIterator<Item = Operation>>(
         &mut self,
         ops: I,
@@ -3313,8 +3330,9 @@ mod tests {
             assert!(!buffer.is_dirty());
             assert!(!buffer.has_conflict());
 
-            let selections = buffer.selections(selection_set_id).unwrap();
-            let cursor_positions = selections
+            let set = buffer.selection_set(selection_set_id).unwrap();
+            let cursor_positions = set
+                .selections
                 .iter()
                 .map(|selection| {
                     assert_eq!(selection.start, selection.end);
@@ -3595,8 +3613,8 @@ mod tests {
                     buffer.replica_id
                 );
                 assert_eq!(
-                    buffer.all_selections().collect::<HashMap<_, _>>(),
-                    first_buffer.all_selections().collect::<HashMap<_, _>>()
+                    buffer.selection_sets().collect::<HashMap<_, _>>(),
+                    first_buffer.selection_sets().collect::<HashMap<_, _>>()
                 );
                 assert_eq!(
                     buffer.all_selection_ranges().collect::<HashMap<_, _>>(),
@@ -3843,7 +3861,7 @@ mod tests {
 
             // Randomly add, remove or mutate selection sets.
             let replica_selection_sets = &self
-                .all_selections()
+                .selection_sets()
                 .map(|(set_id, _)| *set_id)
                 .filter(|set_id| self.replica_id == set_id.replica_id)
                 .collect::<Vec<_>>();
@@ -3915,7 +3933,8 @@ mod tests {
 
         pub fn selection_ranges<'a>(&'a self, set_id: SelectionSetId) -> Result<Vec<Range<usize>>> {
             Ok(self
-                .selections(set_id)?
+                .selection_set(set_id)?
+                .selections
                 .iter()
                 .map(move |selection| {
                     let start = selection.start.to_offset(self);
@@ -3929,12 +3948,6 @@ mod tests {
                 .collect())
         }
 
-        pub fn all_selections(&self) -> impl Iterator<Item = (&SelectionSetId, &[Selection])> {
-            self.selections
-                .iter()
-                .map(|(set_id, set)| (set_id, set.selections.as_ref()))
-        }
-
         pub fn all_selection_ranges<'a>(
             &'a self,
         ) -> impl 'a + Iterator<Item = (SelectionSetId, Vec<Range<usize>>)> {

zed/src/editor/element.rs 🔗

@@ -1,6 +1,6 @@
 use super::{DisplayPoint, Editor, SelectAction};
 use gpui::{
-    color::{ColorF, ColorU},
+    color::ColorU,
     geometry::{
         rect::RectF,
         vector::{vec2f, Vector2F},
@@ -217,66 +217,83 @@ impl EditorElement {
 
         // Draw selections
         let corner_radius = 2.5;
+        let colors = [
+            (ColorU::from_u32(0xa3d6ffff), ColorU::from_u32(0x000000ff)),
+            (ColorU::from_u32(0xffaf87ff), ColorU::from_u32(0xff8e72ff)),
+            (ColorU::from_u32(0x86eaccff), ColorU::from_u32(0x377771ff)),
+            (ColorU::from_u32(0xb8b8ffff), ColorU::from_u32(0x9381ffff)),
+            (ColorU::from_u32(0xf5cce8ff), ColorU::from_u32(0x4a2040ff)),
+        ];
         let mut cursors = SmallVec::<[Cursor; 32]>::new();
 
         let content_origin = bounds.origin() + vec2f(-descent, 0.0);
-
-        for selection in view.selections_in_range(
-            DisplayPoint::new(start_row, 0)..DisplayPoint::new(end_row, 0),
-            cx.app,
-        ) {
-            if selection.start != selection.end {
-                let range_start = cmp::min(selection.start, selection.end);
-                let range_end = cmp::max(selection.start, selection.end);
-                let row_range = if range_end.column() == 0 {
-                    cmp::max(range_start.row(), start_row)..cmp::min(range_end.row(), end_row)
-                } else {
-                    cmp::max(range_start.row(), start_row)..cmp::min(range_end.row() + 1, end_row)
-                };
-
-                let selection = Selection {
-                    line_height,
-                    start_y: content_origin.y() + row_range.start as f32 * line_height - scroll_top,
-                    lines: row_range
-                        .into_iter()
-                        .map(|row| {
-                            let line_layout = &layout.line_layouts[(row - start_row) as usize];
-                            SelectionLine {
-                                start_x: if row == range_start.row() {
-                                    content_origin.x()
-                                        + line_layout.x_for_index(range_start.column() as usize)
-                                        - scroll_left
-                                } else {
-                                    content_origin.x() - scroll_left
-                                },
-                                end_x: if row == range_end.row() {
-                                    content_origin.x()
-                                        + line_layout.x_for_index(range_end.column() as usize)
-                                        - scroll_left
-                                } else {
-                                    content_origin.x() + line_layout.width() + corner_radius * 2.0
-                                        - scroll_left
-                                },
-                            }
-                        })
-                        .collect(),
-                };
-
-                selection.paint(bounds, cx.scene);
-            }
-
-            if view.cursors_visible() {
-                let cursor_position = selection.end;
-                if (start_row..end_row).contains(&cursor_position.row()) {
-                    let cursor_row_layout =
-                        &layout.line_layouts[(selection.end.row() - start_row) as usize];
-                    let x = cursor_row_layout.x_for_index(selection.end.column() as usize)
-                        - scroll_left;
-                    let y = selection.end.row() as f32 * line_height - scroll_top;
-                    cursors.push(Cursor {
-                        origin: content_origin + vec2f(x, y),
+        for selection_set_id in view.active_selection_sets(cx.app) {
+            let (selection_color, cursor_color) =
+                colors[selection_set_id.replica_id as usize % colors.len()];
+            for selection in view.selections_in_range(
+                selection_set_id,
+                DisplayPoint::new(start_row, 0)..DisplayPoint::new(end_row, 0),
+                cx.app,
+            ) {
+                if selection.start != selection.end {
+                    let range_start = cmp::min(selection.start, selection.end);
+                    let range_end = cmp::max(selection.start, selection.end);
+                    let row_range = if range_end.column() == 0 {
+                        cmp::max(range_start.row(), start_row)..cmp::min(range_end.row(), end_row)
+                    } else {
+                        cmp::max(range_start.row(), start_row)
+                            ..cmp::min(range_end.row() + 1, end_row)
+                    };
+
+                    let selection = Selection {
+                        color: selection_color,
                         line_height,
-                    });
+                        start_y: content_origin.y() + row_range.start as f32 * line_height
+                            - scroll_top,
+                        lines: row_range
+                            .into_iter()
+                            .map(|row| {
+                                let line_layout = &layout.line_layouts[(row - start_row) as usize];
+                                SelectionLine {
+                                    start_x: if row == range_start.row() {
+                                        content_origin.x()
+                                            + line_layout.x_for_index(range_start.column() as usize)
+                                            - scroll_left
+                                    } else {
+                                        content_origin.x() - scroll_left
+                                    },
+                                    end_x: if row == range_end.row() {
+                                        content_origin.x()
+                                            + line_layout.x_for_index(range_end.column() as usize)
+                                            - scroll_left
+                                    } else {
+                                        content_origin.x()
+                                            + line_layout.width()
+                                            + corner_radius * 2.0
+                                            - scroll_left
+                                    },
+                                }
+                            })
+                            .collect(),
+                    };
+
+                    selection.paint(bounds, cx.scene);
+                }
+
+                if view.cursors_visible() {
+                    let cursor_position = selection.end;
+                    if (start_row..end_row).contains(&cursor_position.row()) {
+                        let cursor_row_layout =
+                            &layout.line_layouts[(selection.end.row() - start_row) as usize];
+                        let x = cursor_row_layout.x_for_index(selection.end.column() as usize)
+                            - scroll_left;
+                        let y = selection.end.row() as f32 * line_height - scroll_top;
+                        cursors.push(Cursor {
+                            color: cursor_color,
+                            origin: content_origin + vec2f(x, y),
+                            line_height,
+                        });
+                    }
                 }
             }
         }
@@ -575,13 +592,14 @@ impl PaintState {
 struct Cursor {
     origin: Vector2F,
     line_height: f32,
+    color: ColorU,
 }
 
 impl Cursor {
     fn paint(&self, cx: &mut PaintContext) {
         cx.scene.push_quad(Quad {
             bounds: RectF::new(self.origin, vec2f(2.0, self.line_height)),
-            background: Some(ColorU::black()),
+            background: Some(self.color),
             border: Border::new(0., ColorU::black()),
             corner_radius: 0.,
         });
@@ -593,6 +611,7 @@ struct Selection {
     start_y: f32,
     line_height: f32,
     lines: Vec<SelectionLine>,
+    color: ColorU,
 }
 
 #[derive(Debug)]
@@ -696,7 +715,7 @@ impl Selection {
         path.curve_to(first_top_left + top_curve_width, first_top_left);
         path.line_to(first_top_right - top_curve_width);
 
-        scene.push_path(path.build(ColorF::new(0.639, 0.839, 1.0, 1.0).to_u8(), Some(bounds)));
+        scene.push_path(path.build(self.color, Some(bounds)));
     }
 }