Detailed changes
@@ -4287,9 +4287,15 @@ version = "0.1.0"
dependencies = [
"editor",
"gpui",
+ "indoc",
+ "language",
"menu",
+ "project",
+ "serde_json",
"text",
"theme",
+ "tree-sitter-rust",
+ "tree-sitter-typescript",
"ui",
"util",
"workspace",
@@ -6614,12 +6620,18 @@ dependencies = [
"editor",
"fuzzy",
"gpui",
+ "indoc",
"language",
+ "menu",
"ordered-float 2.10.0",
"picker",
+ "project",
+ "serde_json",
"settings",
"smol",
"theme",
+ "tree-sitter-rust",
+ "tree-sitter-typescript",
"ui",
"util",
"workspace",
@@ -41,7 +41,7 @@ futures.workspace = true
fuzzy.workspace = true
git.workspace = true
gpui.workspace = true
-indoc = "1.0.4"
+indoc.workspace = true
itertools.workspace = true
language.workspace = true
lazy_static.workspace = true
@@ -43,7 +43,7 @@ use anyhow::{anyhow, Context as _, Result};
use blink_manager::BlinkManager;
use client::{Collaborator, ParticipantIndex};
use clock::ReplicaId;
-use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque};
+use collections::{hash_map, BTreeMap, Bound, HashMap, HashSet, VecDeque};
use convert_case::{Case, Casing};
use copilot::Copilot;
use debounced_delay::DebouncedDelay;
@@ -386,7 +386,8 @@ pub struct Editor {
show_gutter: bool,
show_wrap_guides: Option<bool>,
placeholder_text: Option<Arc<str>>,
- highlighted_rows: Option<Range<u32>>,
+ highlight_order: usize,
+ highlighted_rows: HashMap<TypeId, Vec<(usize, Range<Anchor>, Hsla)>>,
background_highlights: BTreeMap<TypeId, BackgroundHighlight>,
nav_history: Option<ItemNavHistory>,
context_menu: RwLock<Option<ContextMenu>>,
@@ -1523,7 +1524,8 @@ impl Editor {
show_gutter: mode == EditorMode::Full,
show_wrap_guides: None,
placeholder_text: None,
- highlighted_rows: None,
+ highlight_order: 0,
+ highlighted_rows: HashMap::default(),
background_highlights: Default::default(),
nav_history: None,
context_menu: RwLock::new(None),
@@ -8921,12 +8923,93 @@ impl Editor {
}
}
- pub fn highlight_rows(&mut self, rows: Option<Range<u32>>) {
- self.highlighted_rows = rows;
+ /// Adds or removes (on `None` color) a highlight for the rows corresponding to the anchor range given.
+ /// On matching anchor range, replaces the old highlight; does not clear the other existing highlights.
+ /// If multiple anchor ranges will produce highlights for the same row, the last range added will be used.
+ pub fn highlight_rows<T: 'static>(
+ &mut self,
+ rows: Range<Anchor>,
+ color: Option<Hsla>,
+ cx: &mut ViewContext<Self>,
+ ) {
+ let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
+ match self.highlighted_rows.entry(TypeId::of::<T>()) {
+ hash_map::Entry::Occupied(o) => {
+ let row_highlights = o.into_mut();
+ let existing_highlight_index =
+ row_highlights.binary_search_by(|(_, highlight_range, _)| {
+ highlight_range
+ .start
+ .cmp(&rows.start, &multi_buffer_snapshot)
+ .then(highlight_range.end.cmp(&rows.end, &multi_buffer_snapshot))
+ });
+ match color {
+ Some(color) => {
+ let insert_index = match existing_highlight_index {
+ Ok(i) => i,
+ Err(i) => i,
+ };
+ row_highlights.insert(
+ insert_index,
+ (post_inc(&mut self.highlight_order), rows, color),
+ );
+ }
+ None => {
+ if let Ok(i) = existing_highlight_index {
+ row_highlights.remove(i);
+ }
+ }
+ }
+ }
+ hash_map::Entry::Vacant(v) => {
+ if let Some(color) = color {
+ v.insert(vec![(post_inc(&mut self.highlight_order), rows, color)]);
+ }
+ }
+ }
}
- pub fn highlighted_rows(&self) -> Option<Range<u32>> {
- self.highlighted_rows.clone()
+ /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
+ pub fn clear_row_highlights<T: 'static>(&mut self) {
+ self.highlighted_rows.remove(&TypeId::of::<T>());
+ }
+
+ /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
+ pub fn highlighted_rows<T: 'static>(
+ &self,
+ ) -> Option<impl Iterator<Item = (&Range<Anchor>, &Hsla)>> {
+ Some(
+ self.highlighted_rows
+ .get(&TypeId::of::<T>())?
+ .iter()
+ .map(|(_, range, color)| (range, color)),
+ )
+ }
+
+ // Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
+ // Rerturns a map of display rows that are highlighted and their corresponding highlight color.
+ pub fn highlighted_display_rows(&mut self, cx: &mut WindowContext) -> BTreeMap<u32, Hsla> {
+ let snapshot = self.snapshot(cx);
+ let mut used_highlight_orders = HashMap::default();
+ self.highlighted_rows
+ .iter()
+ .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
+ .fold(
+ BTreeMap::<u32, Hsla>::new(),
+ |mut unique_rows, (highlight_order, anchor_range, hsla)| {
+ let start_row = anchor_range.start.to_display_point(&snapshot).row();
+ let end_row = anchor_range.end.to_display_point(&snapshot).row();
+ for row in start_row..=end_row {
+ let used_index =
+ used_highlight_orders.entry(row).or_insert(*highlight_order);
+ if highlight_order >= used_index {
+ *used_index = *highlight_order;
+ unique_rows.insert(row, *hsla);
+ }
+ }
+ unique_rows
+ },
+ )
}
pub fn highlight_background<T: 'static>(
@@ -665,19 +665,53 @@ impl EditorElement {
}
}
- if let Some(highlighted_rows) = &layout.highlighted_rows {
+ let mut paint_highlight = |highlight_row_start: u32, highlight_row_end: u32, color| {
let origin = point(
bounds.origin.x,
bounds.origin.y
- + (layout.position_map.line_height * highlighted_rows.start as f32)
+ + (layout.position_map.line_height * highlight_row_start as f32)
- scroll_top,
);
let size = size(
bounds.size.width,
- layout.position_map.line_height * highlighted_rows.len() as f32,
+ layout.position_map.line_height
+ * (highlight_row_end + 1 - highlight_row_start) as f32,
);
- let highlighted_line_bg = cx.theme().colors().editor_highlighted_line_background;
- cx.paint_quad(fill(Bounds { origin, size }, highlighted_line_bg));
+ cx.paint_quad(fill(Bounds { origin, size }, color));
+ };
+ let mut last_row = None;
+ let mut highlight_row_start = 0u32;
+ let mut highlight_row_end = 0u32;
+ for (&row, &color) in &layout.highlighted_rows {
+ let paint = last_row.map_or(false, |(last_row, last_color)| {
+ last_color != color || last_row + 1 < row
+ });
+
+ if paint {
+ let paint_range_is_unfinished = highlight_row_end == 0;
+ if paint_range_is_unfinished {
+ highlight_row_end = row;
+ last_row = None;
+ }
+ paint_highlight(highlight_row_start, highlight_row_end, color);
+ highlight_row_start = 0;
+ highlight_row_end = 0;
+ if !paint_range_is_unfinished {
+ highlight_row_start = row;
+ last_row = Some((row, color));
+ }
+ } else {
+ if last_row.is_none() {
+ highlight_row_start = row;
+ } else {
+ highlight_row_end = row;
+ }
+ last_row = Some((row, color));
+ }
+ }
+ if let Some((row, hsla)) = last_row {
+ highlight_row_end = row;
+ paint_highlight(highlight_row_start, highlight_row_end, hsla);
}
let scroll_left =
@@ -2064,7 +2098,7 @@ impl EditorElement {
let mut active_rows = BTreeMap::new();
let is_singleton = editor.is_singleton(cx);
- let highlighted_rows = editor.highlighted_rows();
+ let highlighted_rows = editor.highlighted_display_rows(cx);
let highlighted_ranges = editor.background_highlights_in_range(
start_anchor..end_anchor,
&snapshot.display_snapshot,
@@ -3198,7 +3232,7 @@ pub struct LayoutState {
visible_anchor_range: Range<Anchor>,
visible_display_row_range: Range<u32>,
active_rows: BTreeMap<u32, bool>,
- highlighted_rows: Option<Range<u32>>,
+ highlighted_rows: BTreeMap<u32, Hsla>,
line_numbers: Vec<Option<ShapedLine>>,
display_hunks: Vec<DisplayDiffHunk>,
blocks: Vec<BlockLayout>,
@@ -81,8 +81,8 @@ impl Editor {
let mut target_top;
let mut target_bottom;
- if let Some(highlighted_rows) = &self.highlighted_rows {
- target_top = highlighted_rows.start as f32;
+ if let Some(first_highlighted_row) = &self.highlighted_display_rows(cx).first_entry() {
+ target_top = *first_highlighted_row.key() as f32;
target_bottom = target_top + 1.;
} else {
let selections = self.selections.all::<Point>(cx);
@@ -205,10 +205,7 @@ impl Editor {
let mut target_left;
let mut target_right;
- if self.highlighted_rows.is_some() {
- target_left = px(0.);
- target_right = px(0.);
- } else {
+ if self.highlighted_rows.is_empty() {
target_left = px(f32::INFINITY);
target_right = px(0.);
for selection in selections {
@@ -229,6 +226,9 @@ impl Editor {
);
}
}
+ } else {
+ target_left = px(0.);
+ target_right = px(0.);
}
target_right = target_right.min(scroll_width);
@@ -24,3 +24,12 @@ workspace.workspace = true
[dev-dependencies]
editor = { workspace = true, features = ["test-support"] }
+gpui = { workspace = true, features = ["test-support"] }
+indoc.workspace = true
+language = { workspace = true, features = ["test-support"] }
+menu.workspace = true
+project = { workspace = true, features = ["test-support"] }
+serde_json.workspace = true
+tree-sitter-rust.workspace = true
+tree-sitter-typescript.workspace = true
+workspace = { workspace = true, features = ["test-support"] }
@@ -1,6 +1,6 @@
pub mod cursor_position;
-use editor::{display_map::ToDisplayPoint, scroll::Autoscroll, Editor};
+use editor::{scroll::Autoscroll, Editor};
use gpui::{
actions, div, prelude::*, AnyWindowHandle, AppContext, DismissEvent, EventEmitter, FocusHandle,
FocusableView, Render, SharedString, Styled, Subscription, View, ViewContext, VisualContext,
@@ -34,6 +34,8 @@ impl FocusableView for GoToLine {
}
impl EventEmitter<DismissEvent> for GoToLine {}
+enum GoToLineRowHighlights {}
+
impl GoToLine {
fn register(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
let handle = cx.view().downgrade();
@@ -84,7 +86,7 @@ impl GoToLine {
.update(cx, |_, cx| {
let scroll_position = self.prev_scroll_position.take();
self.active_editor.update(cx, |editor, cx| {
- editor.highlight_rows(None);
+ editor.clear_row_highlights::<GoToLineRowHighlights>();
if let Some(scroll_position) = scroll_position {
editor.set_scroll_position(scroll_position, cx);
}
@@ -112,9 +114,13 @@ impl GoToLine {
self.active_editor.update(cx, |active_editor, cx| {
let snapshot = active_editor.snapshot(cx).display_snapshot;
let point = snapshot.buffer_snapshot.clip_point(point, Bias::Left);
- let display_point = point.to_display_point(&snapshot);
- let row = display_point.row();
- active_editor.highlight_rows(Some(row..row + 1));
+ let anchor = snapshot.buffer_snapshot.anchor_before(point);
+ active_editor.clear_row_highlights::<GoToLineRowHighlights>();
+ active_editor.highlight_rows::<GoToLineRowHighlights>(
+ anchor..anchor,
+ Some(cx.theme().colors().editor_highlighted_line_background),
+ cx,
+ );
active_editor.request_autoscroll(Autoscroll::center(), cx);
});
cx.notify();
@@ -207,3 +213,140 @@ impl Render for GoToLine {
)
}
}
+
+#[cfg(test)]
+mod tests {
+ use std::sync::Arc;
+
+ use gpui::{TestAppContext, VisualTestContext};
+ use indoc::indoc;
+ use project::{FakeFs, Project};
+ use serde_json::json;
+ use workspace::{AppState, Workspace};
+
+ use super::*;
+
+ #[gpui::test]
+ async fn test_go_to_line_view_row_highlights(cx: &mut TestAppContext) {
+ init_test(cx);
+ let fs = FakeFs::new(cx.executor());
+ fs.insert_tree(
+ "/dir",
+ json!({
+ "a.rs": indoc!{"
+ struct SingleLine; // display line 0
+ // display line 1
+ struct MultiLine { // display line 2
+ field_1: i32, // display line 3
+ field_2: i32, // display line 4
+ } // display line 5
+ // display line 7
+ struct Another { // display line 8
+ field_1: i32, // display line 9
+ field_2: i32, // display line 10
+ field_3: i32, // display line 11
+ field_4: i32, // display line 12
+ } // display line 13
+ "}
+ }),
+ )
+ .await;
+
+ let project = Project::test(fs, ["/dir".as_ref()], cx).await;
+ let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
+ let worktree_id = workspace.update(cx, |workspace, cx| {
+ workspace.project().update(cx, |project, cx| {
+ project.worktrees().next().unwrap().read(cx).id()
+ })
+ });
+ let _buffer = project
+ .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
+ .await
+ .unwrap();
+ let editor = workspace
+ .update(cx, |workspace, cx| {
+ workspace.open_path((worktree_id, "a.rs"), None, true, cx)
+ })
+ .await
+ .unwrap()
+ .downcast::<Editor>()
+ .unwrap();
+
+ let go_to_line_view = open_go_to_line_view(&workspace, cx);
+ assert_eq!(
+ highlighted_display_rows(&editor, cx),
+ Vec::<u32>::new(),
+ "Initially opened go to line modal should not highlight any rows"
+ );
+
+ cx.simulate_input("1");
+ assert_eq!(
+ highlighted_display_rows(&editor, cx),
+ vec![0],
+ "Go to line modal should highlight a row, corresponding to the query"
+ );
+
+ cx.simulate_input("8");
+ assert_eq!(
+ highlighted_display_rows(&editor, cx),
+ vec![13],
+ "If the query is too large, the last row should be highlighted"
+ );
+
+ cx.dispatch_action(menu::Cancel);
+ drop(go_to_line_view);
+ editor.update(cx, |_, _| {});
+ assert_eq!(
+ highlighted_display_rows(&editor, cx),
+ Vec::<u32>::new(),
+ "After cancelling and closing the modal, no rows should be highlighted"
+ );
+
+ let go_to_line_view = open_go_to_line_view(&workspace, cx);
+ assert_eq!(
+ highlighted_display_rows(&editor, cx),
+ Vec::<u32>::new(),
+ "Reopened modal should not highlight any rows"
+ );
+
+ cx.simulate_input("5");
+ assert_eq!(highlighted_display_rows(&editor, cx), vec![4]);
+
+ cx.dispatch_action(menu::Confirm);
+ drop(go_to_line_view);
+ editor.update(cx, |_, _| {});
+ assert_eq!(
+ highlighted_display_rows(&editor, cx),
+ Vec::<u32>::new(),
+ "After confirming and closing the modal, no rows should be highlighted"
+ );
+ }
+
+ fn open_go_to_line_view(
+ workspace: &View<Workspace>,
+ cx: &mut VisualTestContext,
+ ) -> View<GoToLine> {
+ cx.dispatch_action(Toggle::default());
+ workspace.update(cx, |workspace, cx| {
+ workspace.active_modal::<GoToLine>(cx).unwrap().clone()
+ })
+ }
+
+ fn highlighted_display_rows(editor: &View<Editor>, cx: &mut VisualTestContext) -> Vec<u32> {
+ editor.update(cx, |editor, cx| {
+ editor.highlighted_display_rows(cx).into_keys().collect()
+ })
+ }
+
+ fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {
+ cx.update(|cx| {
+ let state = AppState::test(cx);
+ language::init(cx);
+ crate::init(cx);
+ editor::init(cx);
+ workspace::init_settings(cx);
+ Project::init_settings(cx);
+ state
+ })
+ }
+}
@@ -28,3 +28,12 @@ workspace.workspace = true
[dev-dependencies]
editor = { workspace = true, features = ["test-support"] }
+gpui = { workspace = true, features = ["test-support"] }
+indoc.workspace = true
+language = { workspace = true, features = ["test-support"] }
+menu.workspace = true
+project = { workspace = true, features = ["test-support"] }
+serde_json.workspace = true
+tree-sitter-rust.workspace = true
+tree-sitter-typescript.workspace = true
+workspace = { workspace = true, features = ["test-support"] }
@@ -1,7 +1,4 @@
-use editor::{
- display_map::ToDisplayPoint, scroll::Autoscroll, Anchor, AnchorRangeExt, DisplayPoint, Editor,
- EditorMode, ToPoint,
-};
+use editor::{scroll::Autoscroll, Anchor, AnchorRangeExt, Editor, EditorMode};
use fuzzy::StringMatch;
use gpui::{
actions, div, rems, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView,
@@ -121,7 +118,7 @@ impl OutlineViewDelegate {
fn restore_active_editor(&mut self, cx: &mut WindowContext) {
self.active_editor.update(cx, |editor, cx| {
- editor.highlight_rows(None);
+ editor.clear_row_highlights::<OutlineRowHighlights>();
if let Some(scroll_position) = self.prev_scroll_position {
editor.set_scroll_position(scroll_position, cx);
}
@@ -141,19 +138,20 @@ impl OutlineViewDelegate {
let outline_item = &self.outline.items[selected_match.candidate_id];
self.active_editor.update(cx, |active_editor, cx| {
- let snapshot = active_editor.snapshot(cx).display_snapshot;
- let buffer_snapshot = &snapshot.buffer_snapshot;
- let start = outline_item.range.start.to_point(buffer_snapshot);
- let end = outline_item.range.end.to_point(buffer_snapshot);
- let display_rows = start.to_display_point(&snapshot).row()
- ..end.to_display_point(&snapshot).row() + 1;
- active_editor.highlight_rows(Some(display_rows));
+ active_editor.clear_row_highlights::<OutlineRowHighlights>();
+ active_editor.highlight_rows::<OutlineRowHighlights>(
+ outline_item.range.clone(),
+ Some(cx.theme().colors().editor_highlighted_line_background),
+ cx,
+ );
active_editor.request_autoscroll(Autoscroll::center(), cx);
});
}
}
}
+enum OutlineRowHighlights {}
+
impl PickerDelegate for OutlineViewDelegate {
type ListItem = ListItem;
@@ -240,13 +238,13 @@ impl PickerDelegate for OutlineViewDelegate {
self.prev_scroll_position.take();
self.active_editor.update(cx, |active_editor, cx| {
- if let Some(rows) = active_editor.highlighted_rows() {
- let snapshot = active_editor.snapshot(cx).display_snapshot;
- let position = DisplayPoint::new(rows.start, 0).to_point(&snapshot);
- active_editor.change_selections(Some(Autoscroll::center()), cx, |s| {
- s.select_ranges([position..position])
- });
- active_editor.highlight_rows(None);
+ if let Some(rows) = active_editor
+ .highlighted_rows::<OutlineRowHighlights>()
+ .and_then(|highlights| highlights.into_iter().next().map(|(rows, _)| rows.clone()))
+ {
+ active_editor
+ .change_selections(Some(Autoscroll::center()), cx, |s| s.select_ranges([rows]));
+ active_editor.clear_row_highlights::<OutlineRowHighlights>();
active_editor.focus(cx);
}
});
@@ -314,3 +312,260 @@ impl PickerDelegate for OutlineViewDelegate {
)
}
}
+
+#[cfg(test)]
+mod tests {
+ use gpui::{TestAppContext, VisualTestContext};
+ use indoc::indoc;
+ use language::{Language, LanguageConfig, LanguageMatcher};
+ use project::{FakeFs, Project};
+ use serde_json::json;
+ use workspace::{AppState, Workspace};
+
+ use super::*;
+
+ #[gpui::test]
+ async fn test_outline_view_row_highlights(cx: &mut TestAppContext) {
+ init_test(cx);
+ let fs = FakeFs::new(cx.executor());
+ fs.insert_tree(
+ "/dir",
+ json!({
+ "a.rs": indoc!{"
+ struct SingleLine; // display line 0
+ // display line 1
+ struct MultiLine { // display line 2
+ field_1: i32, // display line 3
+ field_2: i32, // display line 4
+ } // display line 5
+ "}
+ }),
+ )
+ .await;
+
+ let project = Project::test(fs, ["/dir".as_ref()], cx).await;
+ project.read_with(cx, |project, _| project.languages().add(rust_lang()));
+
+ let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
+ let worktree_id = workspace.update(cx, |workspace, cx| {
+ workspace.project().update(cx, |project, cx| {
+ project.worktrees().next().unwrap().read(cx).id()
+ })
+ });
+ let _buffer = project
+ .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
+ .await
+ .unwrap();
+ let editor = workspace
+ .update(cx, |workspace, cx| {
+ workspace.open_path((worktree_id, "a.rs"), None, true, cx)
+ })
+ .await
+ .unwrap()
+ .downcast::<Editor>()
+ .unwrap();
+ let ensure_outline_view_contents =
+ |outline_view: &View<Picker<OutlineViewDelegate>>, cx: &mut VisualTestContext| {
+ assert_eq!(query(&outline_view, cx), "");
+ assert_eq!(
+ outline_names(&outline_view, cx),
+ vec![
+ "struct SingleLine",
+ "struct MultiLine",
+ "field_1",
+ "field_2"
+ ],
+ );
+ };
+
+ let outline_view = open_outline_view(&workspace, cx);
+ ensure_outline_view_contents(&outline_view, cx);
+ assert_eq!(
+ highlighted_display_rows(&editor, cx),
+ Vec::<u32>::new(),
+ "Initially opened outline view should have no highlights"
+ );
+
+ cx.dispatch_action(menu::SelectNext);
+ ensure_outline_view_contents(&outline_view, cx);
+ assert_eq!(
+ highlighted_display_rows(&editor, cx),
+ vec![2, 3, 4, 5],
+ "Second struct's rows should be highlighted"
+ );
+
+ cx.dispatch_action(menu::SelectPrev);
+ ensure_outline_view_contents(&outline_view, cx);
+ assert_eq!(
+ highlighted_display_rows(&editor, cx),
+ vec![0],
+ "First struct's row should be highlighted"
+ );
+
+ cx.dispatch_action(menu::Cancel);
+ ensure_outline_view_contents(&outline_view, cx);
+ assert_eq!(
+ highlighted_display_rows(&editor, cx),
+ Vec::<u32>::new(),
+ "No rows should be highlighted after outline view is cancelled and closed"
+ );
+
+ let outline_view = open_outline_view(&workspace, cx);
+ ensure_outline_view_contents(&outline_view, cx);
+ assert_eq!(
+ highlighted_display_rows(&editor, cx),
+ Vec::<u32>::new(),
+ "Reopened outline view should have no highlights"
+ );
+
+ cx.dispatch_action(menu::SelectNext);
+ ensure_outline_view_contents(&outline_view, cx);
+ assert_eq!(highlighted_display_rows(&editor, cx), vec![2, 3, 4, 5]);
+
+ cx.dispatch_action(menu::Confirm);
+ ensure_outline_view_contents(&outline_view, cx);
+ assert_eq!(
+ highlighted_display_rows(&editor, cx),
+ Vec::<u32>::new(),
+ "No rows should be highlighted after outline view is confirmed and closed"
+ );
+ }
+
+ fn open_outline_view(
+ workspace: &View<Workspace>,
+ cx: &mut VisualTestContext,
+ ) -> View<Picker<OutlineViewDelegate>> {
+ cx.dispatch_action(Toggle::default());
+ workspace.update(cx, |workspace, cx| {
+ workspace
+ .active_modal::<OutlineView>(cx)
+ .unwrap()
+ .read(cx)
+ .picker
+ .clone()
+ })
+ }
+
+ fn query(
+ outline_view: &View<Picker<OutlineViewDelegate>>,
+ cx: &mut VisualTestContext,
+ ) -> String {
+ outline_view.update(cx, |outline_view, cx| outline_view.query(cx))
+ }
+
+ fn outline_names(
+ outline_view: &View<Picker<OutlineViewDelegate>>,
+ cx: &mut VisualTestContext,
+ ) -> Vec<String> {
+ outline_view.update(cx, |outline_view, _| {
+ let items = &outline_view.delegate.outline.items;
+ outline_view
+ .delegate
+ .matches
+ .iter()
+ .map(|hit| items[hit.candidate_id].text.clone())
+ .collect::<Vec<_>>()
+ })
+ }
+
+ fn highlighted_display_rows(editor: &View<Editor>, cx: &mut VisualTestContext) -> Vec<u32> {
+ editor.update(cx, |editor, cx| {
+ editor.highlighted_display_rows(cx).into_keys().collect()
+ })
+ }
+
+ fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {
+ cx.update(|cx| {
+ let state = AppState::test(cx);
+ language::init(cx);
+ crate::init(cx);
+ editor::init(cx);
+ workspace::init_settings(cx);
+ Project::init_settings(cx);
+ state
+ })
+ }
+
+ fn rust_lang() -> Arc<Language> {
+ Arc::new(
+ Language::new(
+ LanguageConfig {
+ name: "Rust".into(),
+ matcher: LanguageMatcher {
+ path_suffixes: vec!["rs".to_string()],
+ ..Default::default()
+ },
+ ..Default::default()
+ },
+ Some(tree_sitter_rust::language()),
+ )
+ .with_outline_query(
+ r#"(struct_item
+ (visibility_modifier)? @context
+ "struct" @context
+ name: (_) @name) @item
+
+ (enum_item
+ (visibility_modifier)? @context
+ "enum" @context
+ name: (_) @name) @item
+
+ (enum_variant
+ (visibility_modifier)? @context
+ name: (_) @name) @item
+
+ (impl_item
+ "impl" @context
+ trait: (_)? @name
+ "for"? @context
+ type: (_) @name) @item
+
+ (trait_item
+ (visibility_modifier)? @context
+ "trait" @context
+ name: (_) @name) @item
+
+ (function_item
+ (visibility_modifier)? @context
+ (function_modifiers)? @context
+ "fn" @context
+ name: (_) @name) @item
+
+ (function_signature_item
+ (visibility_modifier)? @context
+ (function_modifiers)? @context
+ "fn" @context
+ name: (_) @name) @item
+
+ (macro_definition
+ . "macro_rules!" @context
+ name: (_) @name) @item
+
+ (mod_item
+ (visibility_modifier)? @context
+ "mod" @context
+ name: (_) @name) @item
+
+ (type_item
+ (visibility_modifier)? @context
+ "type" @context
+ name: (_) @name) @item
+
+ (associated_type
+ "type" @context
+ name: (_) @name) @item
+
+ (const_item
+ (visibility_modifier)? @context
+ "const" @context
+ name: (_) @name) @item
+
+ (field_declaration
+ (visibility_modifier)? @context
+ name: (_) @name) @item
+"#,
+ )
+ .unwrap(),
+ )
+ }
+}