diff --git a/Cargo.lock b/Cargo.lock index 04be6b1aab..12818a4993 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9588,7 +9588,7 @@ dependencies = [ "fs", "futures 0.3.32", "futures-lite 1.13.0", - "fuzzy", + "fuzzy_nucleo", "globset", "gpui", "http_client", @@ -12386,13 +12386,12 @@ version = "0.1.0" dependencies = [ "editor", "futures 0.3.32", - "fuzzy", + "fuzzy_nucleo", "gpui", "indoc", "language", "lsp", "menu", - "ordered-float 2.10.1", "picker", "project", "rope", diff --git a/crates/collab_ui/src/channel_view.rs b/crates/collab_ui/src/channel_view.rs index f4aadb7433..bcc91ac093 100644 --- a/crates/collab_ui/src/channel_view.rs +++ b/crates/collab_ui/src/channel_view.rs @@ -324,7 +324,7 @@ impl ChannelView { return; }; - let link = channel.notes_link(closest_heading.map(|heading| heading.text), cx); + let link = channel.notes_link(closest_heading.map(|heading| heading.text.to_string()), cx); cx.write_to_clipboard(ClipboardItem::new_string(link)); self.workspace .update(cx, |workspace, cx| { diff --git a/crates/editor/src/document_symbols.rs b/crates/editor/src/document_symbols.rs index ae28538cc9..8523bcb7b0 100644 --- a/crates/editor/src/document_symbols.rs +++ b/crates/editor/src/document_symbols.rs @@ -284,7 +284,7 @@ fn highlights_from_buffer( .collect::(); let mut outline_text_highlights = Vec::new(); - match search_text.find(outline_text) { + match search_text.find(outline_text.as_str()) { Some(start_index) => { let multibuffer_start = search_start_offset + MultiBufferOffset(start_index); let multibuffer_end = multibuffer_start + MultiBufferOffset(outline_text.len()); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 8f7f0b439b..c225cabd9b 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -10690,7 +10690,7 @@ impl Editor { }; breadcrumbs.extend(symbols.iter().map(|symbol| HighlightedText { - text: symbol.text.clone().into(), + text: symbol.text.clone(), highlights: symbol.highlight_ranges.clone(), })); Some(breadcrumbs) diff --git a/crates/fuzzy_nucleo/src/strings.rs b/crates/fuzzy_nucleo/src/strings.rs index b72c7da205..81b377b598 100644 --- a/crates/fuzzy_nucleo/src/strings.rs +++ b/crates/fuzzy_nucleo/src/strings.rs @@ -24,8 +24,8 @@ pub struct StringMatchCandidate { } impl StringMatchCandidate { - pub fn new(id: usize, string: impl ToString) -> Self { - Self::from_shared(id, SharedString::new(string.to_string())) + pub fn new(id: usize, string: impl Into) -> Self { + Self::from_shared(id, string.into()) } pub fn from_shared(id: usize, string: SharedString) -> Self { @@ -298,7 +298,7 @@ mod tests { strings .iter() .enumerate() - .map(|(id, s)| StringMatchCandidate::new(id, s)) + .map(|(id, s)| StringMatchCandidate::new(id, *s)) .collect() } diff --git a/crates/language/Cargo.toml b/crates/language/Cargo.toml index b4d24765de..bde0659cf1 100644 --- a/crates/language/Cargo.toml +++ b/crates/language/Cargo.toml @@ -36,7 +36,7 @@ encoding_rs.workspace = true fs.workspace = true futures.workspace = true futures-lite.workspace = true -fuzzy.workspace = true +fuzzy_nucleo.workspace = true globset.workspace = true gpui.workspace = true http_client.workspace = true diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 5551a4b5ea..81ffa31da7 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -4600,7 +4600,7 @@ impl BufferSnapshot { depth: 0, // We'll calculate the depth later range: item_point_range, source_range_for_text: source_range_for_text.to_point(self), - text, + text: text.into(), highlight_ranges, name_ranges, body_range: open_point.zip(close_point).map(|(start, end)| start..end), diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index b46b3611a5..86fdf51926 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -850,23 +850,26 @@ async fn test_outline(cx: &mut gpui::TestAppContext) { ] ); - // Without space, we only match on names + // Single-atom queries (no whitespace): all matched chars must land in the leaf, + // so items whose ancestor path coincidentally contains the query chars don't + // show up unless the leaf itself matches. assert_eq!( search(&outline, "oon", cx).await, &[ - ("mod module", vec![]), // included as the parent of a match - ("enum LoginState", vec![]), // included as the parent of a match - ("LoggingOn", vec![1, 7, 8]), // matches - ("impl Drop for Person", vec![7, 18, 19]), // matches in two disjoint names + ("mod module", vec![]), // parent context for LoggingOn + ("enum LoginState", vec![]), // parent context for LoggingOn + ("LoggingOn", vec![1, 7, 8]), // all three chars in leaf + ("impl Eq for Person", vec![9, 16, 17]), // o-o-n in "for Person" + ("impl Drop for Person", vec![11, 18, 19]), // o-o-n in "for Person" ] ); + // Multi-atom queries: rows whose match lives entirely in an ancestor + // are kept as context (empty positions, score zeroed) so descendants + // of a matched container surface alongside it. assert_eq!( search(&outline, "dp p", cx).await, - &[ - ("impl Drop for Person", vec![5, 8, 9, 14]), - ("fn drop", vec![]), - ] + &[("impl Drop for Person", vec![5, 14]), ("fn drop", vec![]),] ); assert_eq!( search(&outline, "dpn", cx).await, @@ -875,8 +878,8 @@ async fn test_outline(cx: &mut gpui::TestAppContext) { assert_eq!( search(&outline, "impl ", cx).await, &[ - ("impl Eq for Person", vec![0, 1, 2, 3, 4]), - ("impl Drop for Person", vec![0, 1, 2, 3, 4]), + ("impl Eq for Person", vec![0, 1, 2, 3]), + ("impl Drop for Person", vec![0, 1, 2, 3]), ("fn drop", vec![]), ] ); @@ -891,12 +894,16 @@ async fn test_outline(cx: &mut gpui::TestAppContext) { query: &'a str, cx: &'a gpui::TestAppContext, ) -> Vec<(&'a str, Vec)> { - let matches = cx + let entries = cx .update(|cx| outline.search(query, cx.background_executor().clone())) .await; - matches + entries .into_iter() - .map(|mat| (outline.items[mat.candidate_id].text.as_str(), mat.positions)) + .map(|entry| { + let candidate_id = entry.candidate_id(); + let positions = entry.into_match().map(|m| m.positions).unwrap_or_default(); + (outline.items[candidate_id].text.as_str(), positions) + }) .collect::>() } } @@ -999,7 +1006,7 @@ fn test_outline_annotations(cx: &mut App) { .items .into_iter() .map(|item| ( - item.text, + item.text.to_string(), item.depth, item.annotation_range .map(|range| { buffer.read(cx).text_for_range(range).collect::() }) @@ -1099,7 +1106,7 @@ async fn test_symbols_containing(cx: &mut gpui::TestAppContext) { .into_iter() .map(|item| { ( - item.text, + item.text.to_string(), item.range.start.to_point(snapshot)..item.range.end.to_point(snapshot), ) }) diff --git a/crates/language/src/outline.rs b/crates/language/src/outline.rs index 78f084736e..5ff5c028ff 100644 --- a/crates/language/src/outline.rs +++ b/crates/language/src/outline.rs @@ -1,15 +1,18 @@ use crate::{BufferSnapshot, Point, ToPoint, ToTreeSitterPoint}; -use fuzzy::{StringMatch, StringMatchCandidate}; -use gpui::{BackgroundExecutor, HighlightStyle}; +use fuzzy_nucleo::{Case, LengthPenalty, StringMatch, StringMatchCandidate}; +use gpui::{BackgroundExecutor, HighlightStyle, SharedString}; use std::ops::Range; /// An outline of all the symbols contained in a buffer. #[derive(Debug)] pub struct Outline { pub items: Vec>, + /// Candidates contain the full path of each item, used for matching. candidates: Vec, - pub path_candidates: Vec, - path_candidate_prefixes: Vec, + /// leaf_offsets stores the byte offset within that full path where the + /// item's own text starts. Anything before this offset is ancestor + /// path text used purely as match context. + leaf_offsets: Vec, } #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] @@ -17,7 +20,7 @@ pub struct OutlineItem { pub depth: usize, pub range: Range, pub source_range_for_text: Range, - pub text: String, + pub text: SharedString, pub highlight_ranges: Vec<(Range, HighlightStyle)>, pub name_ranges: Vec>, pub body_range: Option>, @@ -25,7 +28,42 @@ pub struct OutlineItem { } #[derive(Clone, Debug, Eq, PartialEq)] -pub struct SymbolPath(pub String); +pub struct SymbolPath(pub SharedString); + +/// Result of [`Outline::search`]. Real fuzzy matches are `Match`; `Ancestor` +/// rows are synthetic entries pointing at parent items, included so callers can +/// show the full path of each match (Even when the ancestor has been filtered +/// out due to not matching) but treat those synthetic ancestors differently +/// from an entry that actually matched (e.g. they are not eligible for +/// auto-selection). +#[derive(Clone, Debug)] +pub enum OutlineSearchEntry { + Match(StringMatch), + Ancestor { candidate_id: usize }, +} + +impl OutlineSearchEntry { + pub fn candidate_id(&self) -> usize { + match self { + Self::Match(m) => m.candidate_id, + Self::Ancestor { candidate_id } => *candidate_id, + } + } + + pub fn as_match(&self) -> Option<&StringMatch> { + match self { + Self::Match(m) => Some(m), + Self::Ancestor { .. } => None, + } + } + + pub fn into_match(self) -> Option { + match self { + Self::Match(m) => Some(m), + Self::Ancestor { .. } => None, + } + } +} impl OutlineItem { /// Converts to an equivalent outline item, but with parameterized over Points. @@ -108,9 +146,8 @@ impl OutlineItem { impl Outline { pub fn new(items: Vec>) -> Self { - let mut candidates = Vec::new(); - let mut path_candidates = Vec::new(); - let mut path_candidate_prefixes = Vec::new(); + let mut candidates = Vec::with_capacity(items.len()); + let mut leaf_offsets = Vec::with_capacity(items.len()); let mut path_text = String::new(); let mut path_stack = Vec::new(); @@ -122,25 +159,16 @@ impl Outline { if !path_text.is_empty() { path_text.push(' '); } - path_candidate_prefixes.push(path_text.len()); + leaf_offsets.push(path_text.len()); path_text.push_str(&item.text); path_stack.push(path_text.len()); - - let candidate_text = item - .name_ranges - .iter() - .map(|range| &item.text[range.start..range.end]) - .collect::(); - - path_candidates.push(StringMatchCandidate::new(id, &path_text)); - candidates.push(StringMatchCandidate::new(id, &candidate_text)); + candidates.push(StringMatchCandidate::new(id, &path_text)); } Self { - candidates, - path_candidates, - path_candidate_prefixes, items, + candidates, + leaf_offsets, } } @@ -149,7 +177,7 @@ impl Outline { const SIMILARITY_THRESHOLD: f64 = 0.6; let (position, similarity) = self - .path_candidates + .candidates .iter() .enumerate() .map(|(index, candidate)| { @@ -159,7 +187,7 @@ impl Outline { .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap())?; if similarity >= SIMILARITY_THRESHOLD { - self.path_candidates + self.candidates .get(position) .map(|candidate| SymbolPath(candidate.string.clone())) .zip(self.items.get(position)) @@ -168,91 +196,106 @@ impl Outline { } } - /// Find all outline symbols according to a longest subsequence match with the query, ordered descending by match score. - pub async fn search(&self, query: &str, executor: BackgroundExecutor) -> Vec { + /// Find all outline symbols that match with the nucleo fuzzy matcher, ordered by tree position. + /// Each real match is preceded by [`OutlineSearchEntry::Ancestor`] rows carrying parent + /// `candidate_id`s, so callers can render tree context above the match. + pub async fn search( + &self, + query: &str, + executor: BackgroundExecutor, + ) -> Vec { let query = query.trim_start(); - let is_path_query = query.contains(' '); - let smart_case = query.chars().any(|c| c.is_uppercase()); - let mut matches = fuzzy::match_strings( - if is_path_query { - &self.path_candidates - } else { - &self.candidates - }, + if query.is_empty() { + return Vec::new(); + } + let mut matches = fuzzy_nucleo::match_strings_async( + &self.candidates, query, - smart_case, - true, + Case::Smart, + LengthPenalty::On, 100, &Default::default(), - executor.clone(), + executor, ) .await; matches.sort_unstable_by_key(|m| m.candidate_id); - let mut tree_matches = Vec::new(); - - let mut prev_item_ix = 0; - for mut string_match in matches { - let outline_match = &self.items[string_match.candidate_id]; - string_match.string.clone_from(&outline_match.text); - - if is_path_query { - let prefix_len = self.path_candidate_prefixes[string_match.candidate_id]; - string_match - .positions - .retain(|position| *position >= prefix_len); - for position in &mut string_match.positions { - *position -= prefix_len; - } - } else { - let mut name_ranges = outline_match.name_ranges.iter(); - let Some(mut name_range) = name_ranges.next() else { - continue; - }; - let mut preceding_ranges_len = 0; - for position in &mut string_match.positions { - while *position >= preceding_ranges_len + name_range.len() { - preceding_ranges_len += name_range.len(); - name_range = name_ranges.next().unwrap(); - } - *position = name_range.start + (*position - preceding_ranges_len); - } + // Single-atom queries (no whitespace) require *all* matched chars to + // land in the leaf — typing "drop" should only surface leaves that + // actually contain "drop", not items whose ancestor path happens to. + // We can rely on that behavior because nucleo prefers matches at the + // end of the haystack, so the leafiest part of the candidate. + // + // Multi-atom queries (whitespace-separated) use the ancestor path + // for scoping. Rows whose entire match landed in an ancestor are + // kept as context, with empty positions and zero score, so + // descendants of a matched container surface alongside it. The + // picker's score-based auto-select skips them so they never steal + // focus from a row with real highlights. + let single_atom = !query.contains(char::is_whitespace); + matches.retain_mut(|string_match| { + let leaf_offset = self.leaf_offsets[string_match.candidate_id]; + let total = string_match.positions.len(); + string_match + .positions + .retain(|position| *position >= leaf_offset); + let kept = string_match.positions.len(); + if single_atom && kept != total { + return false; } - - let insertion_ix = tree_matches.len(); - let mut cur_depth = outline_match.depth; - for (ix, item) in self.items[prev_item_ix..string_match.candidate_id] - .iter() - .enumerate() - .rev() - { - if cur_depth == 0 { - break; - } - - let candidate_index = ix + prev_item_ix; - if item.depth == cur_depth - 1 { - tree_matches.insert( - insertion_ix, - StringMatch { - candidate_id: candidate_index, - score: Default::default(), - positions: Default::default(), - string: Default::default(), - }, - ); - cur_depth -= 1; - } + if kept == 0 { + string_match.score = 0.0; } + for position in &mut string_match.positions { + *position -= leaf_offset; + } + string_match + .string + .clone_from(&self.items[string_match.candidate_id].text); + true + }); - prev_item_ix = string_match.candidate_id + 1; - tree_matches.push(string_match); - } - - tree_matches + expand_tree(|i| self.items[i].depth, matches) } } +/// Interleaves synthetic [`OutlineSearchEntry::Ancestor`] rows before each match so callers +/// can render the parent chain as tree context above the match. +/// +/// `matches` must be sorted ascending by `candidate_id` (which is what +/// [`Outline::search`] produces), this is so that we preserve the tree +/// structure of the outline. `depth_at` returns the tree depth for the item at +/// a given candidate index. Ancestors that already appear earlier in the output +/// either as their own match or as an ancestor of an earlier match, are not +/// duplicated. +fn expand_tree( + depth_at: impl Fn(usize) -> usize, + matches: Vec, +) -> Vec { + debug_assert!(matches.is_sorted_by_key(|m| m.candidate_id)); + let mut out = Vec::with_capacity(matches.len()); + let mut prev_item_ix = 0; + for string_match in matches { + let insertion_ix = out.len(); + let mut cur_depth = depth_at(string_match.candidate_id); + for ix in (prev_item_ix..string_match.candidate_id).rev() { + if cur_depth == 0 { + break; + } + if depth_at(ix) == cur_depth - 1 { + out.insert( + insertion_ix, + OutlineSearchEntry::Ancestor { candidate_id: ix }, + ); + cur_depth -= 1; + } + } + prev_item_ix = string_match.candidate_id + 1; + out.push(OutlineSearchEntry::Match(string_match)); + } + out +} + #[cfg(test)] mod tests { use super::*; @@ -273,7 +316,7 @@ mod tests { depth: 0, range: range.clone(), source_range_for_text: range, - text: "completion".to_string(), + text: "completion".into(), highlight_ranges: Vec::new(), name_ranges: Vec::new(), body_range: None, @@ -290,7 +333,7 @@ mod tests { depth: 0, range: Point::new(0, 0)..Point::new(5, 0), source_range_for_text: Point::new(0, 0)..Point::new(0, 9), - text: "class Foo".to_string(), + text: "class Foo".into(), highlight_ranges: vec![], name_ranges: vec![6..9], body_range: None, @@ -300,21 +343,27 @@ mod tests { depth: 0, range: Point::new(2, 0)..Point::new(2, 7), source_range_for_text: Point::new(0, 0)..Point::new(0, 7), - text: "private".to_string(), + text: "private".into(), highlight_ranges: vec![], name_ranges: vec![], body_range: None, annotation_range: None, }, ]); + assert!( + outline.search("", cx.executor()).await.is_empty(), + "empty queries return no matches; the picker handles 'show all' itself", + ); assert_eq!( outline - .search(" ", cx.executor()) + .search("foo", cx.executor()) .await .into_iter() - .map(|mat| mat.string) - .collect::>(), - vec!["class Foo".to_string()] + .filter_map(OutlineSearchEntry::into_match) + .map(|m| m.string) + .collect::>(), + vec![SharedString::from("class Foo")], + "'private' (empty name_ranges) is correctly excluded; only the matching 'class Foo' is returned", ); } @@ -325,7 +374,7 @@ mod tests { depth: 0, range: Point::new(0, 0)..Point::new(5, 0), source_range_for_text: Point::new(0, 0)..Point::new(0, 10), - text: "fn process".to_string(), + text: "fn process".into(), highlight_ranges: vec![], name_ranges: vec![3..10], body_range: None, @@ -335,7 +384,7 @@ mod tests { depth: 0, range: Point::new(7, 0)..Point::new(12, 0), source_range_for_text: Point::new(0, 0)..Point::new(0, 20), - text: "struct DataProcessor".to_string(), + text: "struct DataProcessor".into(), highlight_ranges: vec![], name_ranges: vec![7..20], body_range: None, diff --git a/crates/outline/Cargo.toml b/crates/outline/Cargo.toml index 84d596b761..8b49b5a287 100644 --- a/crates/outline/Cargo.toml +++ b/crates/outline/Cargo.toml @@ -14,10 +14,9 @@ doctest = false [dependencies] editor.workspace = true -fuzzy.workspace = true +fuzzy_nucleo.workspace = true gpui.workspace = true language.workspace = true -ordered-float.workspace = true picker.workspace = true settings.workspace = true theme.workspace = true diff --git a/crates/outline/src/outline.rs b/crates/outline/src/outline.rs index 1f88c12a2b..c3a5cc4804 100644 --- a/crates/outline/src/outline.rs +++ b/crates/outline/src/outline.rs @@ -4,14 +4,13 @@ use std::{cmp, sync::Arc}; use editor::scroll::ScrollOffset; use editor::{Anchor, AnchorRangeExt, Editor, scroll::Autoscroll}; use editor::{MultiBufferOffset, RowHighlightOptions, SelectionEffects}; -use fuzzy::StringMatch; +use fuzzy_nucleo::StringMatch; use gpui::{ App, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, HighlightStyle, ParentElement, Point, Render, Styled, StyledText, Task, TextStyle, WeakEntity, Window, div, rems, }; -use language::{Outline, OutlineItem}; -use ordered_float::OrderedFloat; +use language::{Outline, OutlineItem, OutlineSearchEntry}; use picker::{Picker, PickerDelegate}; use settings::Settings; use theme::ActiveTheme; @@ -192,7 +191,7 @@ struct OutlineViewDelegate { outline: Arc>, selected_match_index: usize, prev_scroll_position: Option>, - matches: Vec, + matches: Vec, } enum OutlineRowHighlights {} @@ -239,7 +238,7 @@ impl OutlineViewDelegate { self.selected_match_index = ix; if navigate { - let outline_item = &self.outline.items[selected_match.candidate_id]; + let outline_item = &self.outline.items[selected_match.candidate_id()]; self.active_editor.update(cx, |active_editor, cx| { active_editor.clear_row_highlights::(); @@ -304,11 +303,13 @@ impl PickerDelegate for OutlineViewDelegate { .items .iter() .enumerate() - .map(|(index, _)| StringMatch { - candidate_id: index, - score: Default::default(), - positions: Default::default(), - string: Default::default(), + .map(|(index, _)| { + OutlineSearchEntry::Match(StringMatch { + candidate_id: index, + score: Default::default(), + positions: Default::default(), + string: Default::default(), + }) }) .collect() } else { @@ -333,8 +334,8 @@ impl PickerDelegate for OutlineViewDelegate { .matches .iter() .enumerate() - .filter_map(|(ix, m)| { - let item = &this.delegate.outline.items[m.candidate_id]; + .filter_map(|(ix, entry)| { + let item = &this.delegate.outline.items[entry.candidate_id()]; let range = item.range.to_offset(&buffer); range.contains(&cursor_offset).then_some((ix, item.depth)) }) @@ -346,11 +347,13 @@ impl PickerDelegate for OutlineViewDelegate { .matches .iter() .enumerate() - .max_by(|(ix_a, a), (ix_b, b)| { - OrderedFloat(a.score) - .cmp(&OrderedFloat(b.score)) - .then(ix_b.cmp(ix_a)) + .filter_map(|(ix, entry)| { + entry + .as_match() + .filter(|m| !m.positions.is_empty()) + .map(|m| (ix, m.score)) }) + .max_by(|(ix_a, a), (ix_b, b)| a.total_cmp(b).then(ix_b.cmp(ix_a))) .map(|(ix, _)| ix) .unwrap_or(0) }; @@ -403,8 +406,9 @@ impl PickerDelegate for OutlineViewDelegate { _: &mut Window, cx: &mut Context>, ) -> Option { - let mat = self.matches.get(ix)?; - let outline_item = self.outline.items.get(mat.candidate_id)?; + let entry = self.matches.get(ix)?; + let outline_item = self.outline.items.get(entry.candidate_id())?; + let ranges = entry.as_match().into_iter().flat_map(|m| m.ranges()); Some( ListItem::new(ix) @@ -415,7 +419,7 @@ impl PickerDelegate for OutlineViewDelegate { div() .text_ui(cx) .pl(rems(outline_item.depth as f32)) - .child(render_item(outline_item, mat.ranges(), cx)), + .child(render_item(outline_item, ranges, cx)), ), ) } @@ -664,7 +668,7 @@ mod tests { .update(cx, |outline_view, cx| { let delegate = &outline_view.delegate; let selected_candidate_id = - delegate.matches[delegate.selected_match_index].candidate_id; + delegate.matches[delegate.selected_match_index].candidate_id(); let (buffer, cursor_offset) = delegate.active_editor.update(cx, |editor, cx| { let buffer = editor.buffer().read(cx).snapshot(cx); let cursor_offset = editor @@ -704,7 +708,7 @@ mod tests { cx.run_until_parked(); let selected_candidate_id = outline_view.read_with(cx, |outline_view, _| { let delegate = &outline_view.delegate; - delegate.matches[delegate.selected_match_index].candidate_id + delegate.matches[delegate.selected_match_index].candidate_id() }); assert_eq!( selected_candidate_id, 0, @@ -848,11 +852,12 @@ mod tests { let scored_ids = delegate .matches .iter() + .filter_map(|entry| entry.as_match()) .filter(|m| m.score > 0.0) .map(|m| m.candidate_id) .collect::>(); ( - selected_match.candidate_id, + selected_match.candidate_id(), *scored_ids.first().unwrap(), *scored_ids.last().unwrap(), scored_ids.len(), @@ -934,14 +939,14 @@ mod tests { fn outline_names( outline_view: &Entity>, cx: &mut VisualTestContext, - ) -> Vec { + ) -> Vec { outline_view.read_with(cx, |outline_view, _| { let items = &outline_view.delegate.outline.items; outline_view .delegate .matches .iter() - .map(|hit| items[hit.candidate_id].text.clone()) + .map(|hit| items[hit.candidate_id()].text.clone()) .collect::>() }) } diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index a3203e3958..6bd05b807d 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -2567,7 +2567,7 @@ impl OutlinePanel { depth, annotation_range: None, range: search_data.context_range.clone(), - text: search_data.context_text.clone(), + text: search_data.context_text.clone().into(), source_range_for_text: search_data.context_range.clone(), highlight_ranges: search_data .highlights_data @@ -8121,7 +8121,7 @@ outline: struct Foo <==== selected // Helper function to move the cursor to the first column of a given row // and return the selected outline entry's text. let move_cursor_and_get_selection = - |row: u32, cx: &mut VisualTestContext| -> Option { + |row: u32, cx: &mut VisualTestContext| -> Option { cx.update(|window, cx| { editor.update(cx, |editor, cx| { editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { diff --git a/crates/project/src/lsp_store/document_symbols.rs b/crates/project/src/lsp_store/document_symbols.rs index e0b16182a9..e993865d4f 100644 --- a/crates/project/src/lsp_store/document_symbols.rs +++ b/crates/project/src/lsp_store/document_symbols.rs @@ -271,7 +271,7 @@ fn flatten_document_symbols( depth, range, source_range_for_text, - text, + text: text.into(), highlight_ranges: Vec::new(), name_ranges, body_range: None,