diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index daecc56650..30020a0d55 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -1712,6 +1712,7 @@ impl MultiBufferSnapshot { ..self.anchor_in_excerpt(excerpt_id.clone(), item.range.end), text: item.text, highlight_ranges: item.highlight_ranges, + name_ranges: item.name_ranges, }) .collect(), )) diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 13e80bdd93..4cf0c8f081 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -1864,11 +1864,15 @@ impl BufferSnapshot { let item_node = mat.nodes_for_capture_index(item_capture_ix).next()?; let range = item_node.start_byte()..item_node.end_byte(); let mut text = String::new(); + let mut name_ranges = Vec::new(); let mut highlight_ranges = Vec::new(); for capture in mat.captures { + let node_is_name; if capture.index == name_capture_ix { + node_is_name = true; } else if capture.index == context_capture_ix { + node_is_name = false; } else { continue; } @@ -1877,6 +1881,18 @@ impl BufferSnapshot { if !text.is_empty() { text.push(' '); } + if node_is_name { + let mut start = text.len(); + let end = start + range.len(); + + // When multiple names are captured, then the matcheable text + // includes the whitespace in between the names. + if !name_ranges.is_empty() { + start -= 1; + } + + name_ranges.push(start..end); + } let mut offset = range.start; chunks.seek(offset); @@ -1911,6 +1927,7 @@ impl BufferSnapshot { range: self.anchor_after(range.start)..self.anchor_before(range.end), text, highlight_ranges, + name_ranges, }) }) .collect::>(); diff --git a/crates/language/src/outline.rs b/crates/language/src/outline.rs index 0da6e72547..07f0196c9f 100644 --- a/crates/language/src/outline.rs +++ b/crates/language/src/outline.rs @@ -16,44 +16,49 @@ pub struct OutlineItem { pub range: Range, pub text: String, pub highlight_ranges: Vec<(Range, HighlightStyle)>, + pub name_ranges: Vec>, } 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 item_text = String::new(); - let mut stack = Vec::new(); + let mut path_text = String::new(); + let mut path_stack = Vec::new(); for (id, item) in items.iter().enumerate() { - if item.depth < stack.len() { - stack.truncate(item.depth); - item_text.truncate(stack.last().copied().unwrap_or(0)); + if item.depth < path_stack.len() { + path_stack.truncate(item.depth); + path_text.truncate(path_stack.last().copied().unwrap_or(0)); } - if !item_text.is_empty() { - item_text.push(' '); + if !path_text.is_empty() { + path_text.push(' '); } - path_candidate_prefixes.push(item_text.len()); - item_text.push_str(&item.text); - stack.push(item_text.len()); + path_candidate_prefixes.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 as usize..range.end as usize]) + .collect::(); path_candidates.push(StringMatchCandidate { id, - string: item_text.clone(), - char_bag: item_text.as_str().into(), + char_bag: path_text.as_str().into(), + string: path_text.clone(), + }); + candidates.push(StringMatchCandidate { + id, + char_bag: candidate_text.as_str().into(), + string: candidate_text, }); } Self { - candidates: items - .iter() - .enumerate() - .map(|(id, item)| StringMatchCandidate { - id, - char_bag: item.text.as_str().into(), - string: item.text.clone(), - }) - .collect(), + candidates, path_candidates, path_candidate_prefixes, items, @@ -93,6 +98,17 @@ impl Outline { for position in &mut string_match.positions { *position -= prefix_len; } + } else { + let mut name_ranges = outline_match.name_ranges.iter(); + let mut name_range = name_ranges.next().unwrap(); + let mut preceding_ranges_len = 0; + for position in &mut string_match.positions { + while *position >= preceding_ranges_len + name_range.len() as usize { + preceding_ranges_len += name_range.len(); + name_range = name_ranges.next().unwrap(); + } + *position = name_range.start as usize + (*position - preceding_ranges_len); + } } let insertion_ix = tree_matches.len(); diff --git a/crates/language/src/tests.rs b/crates/language/src/tests.rs index 849c60ca5f..e2ee035c86 100644 --- a/crates/language/src/tests.rs +++ b/crates/language/src/tests.rs @@ -365,29 +365,34 @@ async fn test_outline(mut cx: gpui::TestAppContext) { ] ); + // Without space, we only match on names 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 Eq for Person", vec![9, 16, 17]), // matches part of the context - ("impl Drop for Person", vec![11, 18, 19]), // matches in two disjoint names + ("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 + ] + ); + + assert_eq!( + search(&outline, "dp p", &cx).await, + &[ + ("impl Drop for Person", vec![5, 8, 9, 14]), + ("fn drop", vec![]), ] ); - assert_eq!( - search(&outline, "dp p", &cx).await, - &[("impl Drop for Person", vec![5, 8, 9, 14])] - ); assert_eq!( search(&outline, "dpn", &cx).await, - &[("impl Drop for Person", vec![5, 8, 19])] + &[("impl Drop for Person", vec![5, 14, 19])] ); assert_eq!( - search(&outline, "impl", &cx).await, + search(&outline, "impl ", &cx).await, &[ - ("impl Eq for Person", vec![0, 1, 2, 3]), - ("impl Drop for Person", vec![0, 1, 2, 3]) + ("impl Eq for Person", vec![0, 1, 2, 3, 4]), + ("impl Drop for Person", vec![0, 1, 2, 3, 4]), + ("fn drop", vec![]), ] );