Keep selections at the top of the project diagnostics view when it is first populated

Max Brunsfeld and Nathan Sobo created

Co-Authored-By: Nathan Sobo <nathan@zed.dev>

Change summary

crates/diagnostics/src/diagnostics.rs | 23 +++++++
crates/editor/src/editor.rs           | 78 ++++++++++++++--------------
2 files changed, 60 insertions(+), 41 deletions(-)

Detailed changes

crates/diagnostics/src/diagnostics.rs 🔗

@@ -9,7 +9,7 @@ use gpui::{
     action, elements::*, keymap::Binding, AppContext, Entity, ModelHandle, MutableAppContext,
     RenderContext, Task, View, ViewContext, ViewHandle,
 };
-use language::{Bias, Buffer, Diagnostic, DiagnosticEntry, Point};
+use language::{Bias, Buffer, Diagnostic, DiagnosticEntry, Point, Selection, SelectionGoal};
 use postage::watch;
 use project::{Project, ProjectPath, WorktreeId};
 use std::{cmp::Ordering, ops::Range, path::Path, sync::Arc};
@@ -183,6 +183,7 @@ impl ProjectDiagnosticsEditor {
             }
         }
 
+        let was_empty = self.path_states.is_empty();
         let path_ix = match self
             .path_states
             .binary_search_by_key(&path.as_ref(), |e| e.0.as_ref())
@@ -375,7 +376,21 @@ impl ProjectDiagnosticsEditor {
                 group_state.blocks = block_ids.by_ref().take(group_state.block_count).collect();
             }
 
-            editor.refresh_selections(cx);
+            if was_empty {
+                editor.update_selections(
+                    vec![Selection {
+                        id: 0,
+                        start: 0,
+                        end: 0,
+                        reversed: false,
+                        goal: SelectionGoal::None,
+                    }],
+                    None,
+                    cx,
+                );
+            } else {
+                editor.refresh_selections(cx);
+            }
         });
 
         for ix in group_ixs_to_remove.into_iter().rev() {
@@ -681,6 +696,10 @@ mod tests {
                     "}"
                 )
             );
+
+            view.editor.update(cx, |editor, cx| {
+                assert_eq!(editor.selected_ranges::<usize>(cx), [0..0]);
+            });
         });
 
         worktree.update(&mut cx, |worktree, cx| {

crates/editor/src/editor.rs 🔗

@@ -1056,6 +1056,45 @@ impl Editor {
         }
     }
 
+    #[cfg(any(test, feature = "test-support"))]
+    pub fn selected_ranges<D: TextDimension + Ord + Sub<D, Output = D>>(
+        &self,
+        cx: &mut MutableAppContext,
+    ) -> Vec<Range<D>> {
+        self.local_selections::<D>(cx)
+            .iter()
+            .map(|s| {
+                if s.reversed {
+                    s.end.clone()..s.start.clone()
+                } else {
+                    s.start.clone()..s.end.clone()
+                }
+            })
+            .collect()
+    }
+
+    #[cfg(any(test, feature = "test-support"))]
+    pub fn selected_display_ranges(&self, cx: &mut MutableAppContext) -> Vec<Range<DisplayPoint>> {
+        let display_map = self
+            .display_map
+            .update(cx, |display_map, cx| display_map.snapshot(cx));
+        self.selections
+            .iter()
+            .chain(
+                self.pending_selection
+                    .as_ref()
+                    .map(|pending| &pending.selection),
+            )
+            .map(|s| {
+                if s.reversed {
+                    s.end.to_display_point(&display_map)..s.start.to_display_point(&display_map)
+                } else {
+                    s.start.to_display_point(&display_map)..s.end.to_display_point(&display_map)
+                }
+            })
+            .collect()
+    }
+
     pub fn select_ranges<I, T>(
         &mut self,
         ranges: I,
@@ -6179,45 +6218,6 @@ mod tests {
         });
     }
 
-    impl Editor {
-        fn selected_ranges<D: TextDimension + Ord + Sub<D, Output = D>>(
-            &self,
-            cx: &mut MutableAppContext,
-        ) -> Vec<Range<D>> {
-            self.local_selections::<D>(cx)
-                .iter()
-                .map(|s| {
-                    if s.reversed {
-                        s.end.clone()..s.start.clone()
-                    } else {
-                        s.start.clone()..s.end.clone()
-                    }
-                })
-                .collect()
-        }
-
-        fn selected_display_ranges(&self, cx: &mut MutableAppContext) -> Vec<Range<DisplayPoint>> {
-            let display_map = self
-                .display_map
-                .update(cx, |display_map, cx| display_map.snapshot(cx));
-            self.selections
-                .iter()
-                .chain(
-                    self.pending_selection
-                        .as_ref()
-                        .map(|pending| &pending.selection),
-                )
-                .map(|s| {
-                    if s.reversed {
-                        s.end.to_display_point(&display_map)..s.start.to_display_point(&display_map)
-                    } else {
-                        s.start.to_display_point(&display_map)..s.end.to_display_point(&display_map)
-                    }
-                })
-                .collect()
-        }
-    }
-
     fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
         let point = DisplayPoint::new(row as u32, column as u32);
         point..point