diff --git a/Cargo.lock b/Cargo.lock index 343f1b839a..74debbe726 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index 06a0653862..4848801cd2 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -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 diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index f489ef9e2a..a5c8b4e5cb 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -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, placeholder_text: Option>, - highlighted_rows: Option>, + highlight_order: usize, + highlighted_rows: HashMap, Hsla)>>, background_highlights: BTreeMap, nav_history: Option, context_menu: RwLock>, @@ -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>) { - 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( + &mut self, + rows: Range, + color: Option, + cx: &mut ViewContext, + ) { + let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx); + match self.highlighted_rows.entry(TypeId::of::()) { + 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> { - 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(&mut self) { + self.highlighted_rows.remove(&TypeId::of::()); + } + + /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting. + pub fn highlighted_rows( + &self, + ) -> Option, &Hsla)>> { + Some( + self.highlighted_rows + .get(&TypeId::of::())? + .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 { + 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::::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( diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 7fb3068424..edee7b53f8 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -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, visible_display_row_range: Range, active_rows: BTreeMap, - highlighted_rows: Option>, + highlighted_rows: BTreeMap, line_numbers: Vec>, display_hunks: Vec, blocks: Vec, diff --git a/crates/editor/src/scroll/autoscroll.rs b/crates/editor/src/scroll/autoscroll.rs index 3dfe2d250a..962395e369 100644 --- a/crates/editor/src/scroll/autoscroll.rs +++ b/crates/editor/src/scroll/autoscroll.rs @@ -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::(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); diff --git a/crates/go_to_line/Cargo.toml b/crates/go_to_line/Cargo.toml index 921434a407..59a0613eed 100644 --- a/crates/go_to_line/Cargo.toml +++ b/crates/go_to_line/Cargo.toml @@ -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"] } diff --git a/crates/go_to_line/src/go_to_line.rs b/crates/go_to_line/src/go_to_line.rs index 5957d77b87..895c383bdc 100644 --- a/crates/go_to_line/src/go_to_line.rs +++ b/crates/go_to_line/src/go_to_line.rs @@ -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 for GoToLine {} +enum GoToLineRowHighlights {} + impl GoToLine { fn register(editor: &mut Editor, cx: &mut ViewContext) { 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::(); 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::(); + active_editor.highlight_rows::( + 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::() + .unwrap(); + + let go_to_line_view = open_go_to_line_view(&workspace, cx); + assert_eq!( + highlighted_display_rows(&editor, cx), + Vec::::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::::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::::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::::new(), + "After confirming and closing the modal, no rows should be highlighted" + ); + } + + fn open_go_to_line_view( + workspace: &View, + cx: &mut VisualTestContext, + ) -> View { + cx.dispatch_action(Toggle::default()); + workspace.update(cx, |workspace, cx| { + workspace.active_modal::(cx).unwrap().clone() + }) + } + + fn highlighted_display_rows(editor: &View, cx: &mut VisualTestContext) -> Vec { + editor.update(cx, |editor, cx| { + editor.highlighted_display_rows(cx).into_keys().collect() + }) + } + + fn init_test(cx: &mut TestAppContext) -> Arc { + 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 + }) + } +} diff --git a/crates/outline/Cargo.toml b/crates/outline/Cargo.toml index 540e979ab3..409d4bbc1e 100644 --- a/crates/outline/Cargo.toml +++ b/crates/outline/Cargo.toml @@ -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"] } diff --git a/crates/outline/src/outline.rs b/crates/outline/src/outline.rs index 0a8b9a08ec..93d5f6e900 100644 --- a/crates/outline/src/outline.rs +++ b/crates/outline/src/outline.rs @@ -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::(); 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::(); + active_editor.highlight_rows::( + 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::() + .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::(); 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::() + .unwrap(); + let ensure_outline_view_contents = + |outline_view: &View>, 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::::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::::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::::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::::new(), + "No rows should be highlighted after outline view is confirmed and closed" + ); + } + + fn open_outline_view( + workspace: &View, + cx: &mut VisualTestContext, + ) -> View> { + cx.dispatch_action(Toggle::default()); + workspace.update(cx, |workspace, cx| { + workspace + .active_modal::(cx) + .unwrap() + .read(cx) + .picker + .clone() + }) + } + + fn query( + outline_view: &View>, + cx: &mut VisualTestContext, + ) -> String { + outline_view.update(cx, |outline_view, cx| outline_view.query(cx)) + } + + fn outline_names( + outline_view: &View>, + cx: &mut VisualTestContext, + ) -> Vec { + 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::>() + }) + } + + fn highlighted_display_rows(editor: &View, cx: &mut VisualTestContext) -> Vec { + editor.update(cx, |editor, cx| { + editor.highlighted_display_rows(cx).into_keys().collect() + }) + } + + fn init_test(cx: &mut TestAppContext) -> Arc { + 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 { + 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(), + ) + } +}