use collections::HashMap;use language::BufferSnapshot;use std::ops::Range;use util::RangeExt; use crate::{ declaration::Identifier, excerpt::{EditPredictionExcerpt, EditPredictionExcerptText},}; #[derive(Debug, Clone)]pub struct Reference { pub identifier: Identifier, pub range: Range<usize>, pub region: ReferenceRegion,} #[derive(Copy, Clone, Debug, Eq, PartialEq)]pub enum ReferenceRegion { Breadcrumb, Nearby,} pub fn references_in_excerpt( excerpt: &EditPredictionExcerpt, excerpt_text: &EditPredictionExcerptText, snapshot: &BufferSnapshot,) -> HashMap<Identifier, Vec<Reference>> { let mut references = references_in_range( excerpt.range.clone(), excerpt_text.body.as_str(), ReferenceRegion::Nearby, snapshot, ); for ((_, range), text) in excerpt .parent_declarations .iter() .zip(excerpt_text.parent_signatures.iter()) { references.extend(references_in_range( range.clone(), text.as_str(), ReferenceRegion::Breadcrumb, snapshot, )); } let mut identifier_to_references: HashMap<Identifier, Vec<Reference>> = HashMap::default(); for reference in references { identifier_to_references .entry(reference.identifier.clone()) .or_insert_with(Vec::new) .push(reference); } identifier_to_references} /// Finds all nodes which have a "variable" match from the highlights query within the offset range.pub fn references_in_range( range: Range<usize>, range_text: &str, reference_region: ReferenceRegion, buffer: &BufferSnapshot,) -> Vec<Reference> { let mut matches = buffer .syntax .matches(range.clone(), &buffer.text, |grammar| { grammar .highlights_config .as_ref() .map(|config| &config.query) }); let mut references = Vec::new(); let mut last_added_range = None; while let Some(mat) = matches.peek() { let config = matches.grammars()[mat.grammar_index] .highlights_config .as_ref(); if let Some(config) = config { for capture in mat.captures { if config.identifier_capture_indices.contains(&capture.index) { let node_range = capture.node.byte_range(); // sometimes multiple highlight queries match - this deduplicates them if Some(node_range.clone()) == last_added_range { continue; } if !range.contains_inclusive(&node_range) { continue; } let identifier_text = &range_text[node_range.start - range.start..node_range.end - range.start]; references.push(Reference { identifier: Identifier { name: identifier_text.into(), language_id: mat.language.id(), }, range: node_range.clone(), region: reference_region, }); last_added_range = Some(node_range); } } } matches.advance(); } references} #[cfg(test)]mod test { use gpui::{TestAppContext, prelude::*}; use indoc::indoc; use language::{BufferSnapshot, Language, LanguageConfig, LanguageMatcher, tree_sitter_rust}; use crate::reference::{ReferenceRegion, references_in_range}; #[gpui::test] fn test_identifier_node_truncated(cx: &mut TestAppContext) { let code = indoc! { r#" fn main() { add(1, 2); } fn add(a: i32, b: i32) -> i32 { a + b } "# }; let buffer = create_buffer(code, cx); let range = 0..35; let references = references_in_range( range.clone(), &code[range], ReferenceRegion::Breadcrumb, &buffer, ); assert_eq!(references.len(), 2); assert_eq!(references[0].identifier.name.as_ref(), "main"); assert_eq!(references[1].identifier.name.as_ref(), "add"); } fn create_buffer(text: &str, cx: &mut TestAppContext) -> BufferSnapshot { let buffer = cx.new(|cx| language::Buffer::local(text, cx).with_language(rust_lang().into(), cx)); buffer.read_with(cx, |buffer, _| buffer.snapshot()) } fn rust_lang() -> Language { Language::new( LanguageConfig { name: "Rust".into(), matcher: LanguageMatcher { path_suffixes: vec!["rs".to_string()], ..Default::default() }, ..Default::default() }, Some(tree_sitter_rust::LANGUAGE.into()), ) .with_highlights_query(include_str!("../../languages/src/rust/highlights.scm")) .unwrap() .with_outline_query(include_str!("../../languages/src/rust/outline.scm")) .unwrap() }}
use collections::HashMap;
use language::BufferSnapshot;
use std::ops::Range;
use util::RangeExt;
use crate::{
declaration::Identifier,
excerpt::{EditPredictionExcerpt, EditPredictionExcerptText},
};
#[derive(Debug, Clone)]
pub struct Reference {
pub identifier: Identifier,
pub range: Range<usize>,
pub region: ReferenceRegion,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ReferenceRegion {
Breadcrumb,
Nearby,
}
pub fn references_in_excerpt(
excerpt: &EditPredictionExcerpt,
excerpt_text: &EditPredictionExcerptText,
snapshot: &BufferSnapshot,
) -> HashMap<Identifier, Vec<Reference>> {
let mut references = references_in_range(
excerpt.range.clone(),
excerpt_text.body.as_str(),
ReferenceRegion::Nearby,
snapshot,
);
for ((_, range), text) in excerpt
.parent_declarations
.iter()
.zip(excerpt_text.parent_signatures.iter())
{
references.extend(references_in_range(
range.clone(),
text.as_str(),
ReferenceRegion::Breadcrumb,
snapshot,
));
}
let mut identifier_to_references: HashMap<Identifier, Vec<Reference>> = HashMap::default();
for reference in references {
identifier_to_references
.entry(reference.identifier.clone())
.or_insert_with(Vec::new)
.push(reference);
}
identifier_to_references
}
/// Finds all nodes which have a "variable" match from the highlights query within the offset range.
pub fn references_in_range(
range: Range<usize>,
range_text: &str,
reference_region: ReferenceRegion,
buffer: &BufferSnapshot,
) -> Vec<Reference> {
let mut matches = buffer
.syntax
.matches(range.clone(), &buffer.text, |grammar| {
grammar
.highlights_config
.as_ref()
.map(|config| &config.query)
});
let mut references = Vec::new();
let mut last_added_range = None;
while let Some(mat) = matches.peek() {
let config = matches.grammars()[mat.grammar_index]
.highlights_config
.as_ref();
if let Some(config) = config {
for capture in mat.captures {
if config.identifier_capture_indices.contains(&capture.index) {
let node_range = capture.node.byte_range();
// sometimes multiple highlight queries match - this deduplicates them
if Some(node_range.clone()) == last_added_range {
continue;
}
if !range.contains_inclusive(&node_range) {
continue;
}
let identifier_text =
&range_text[node_range.start - range.start..node_range.end - range.start];
references.push(Reference {
identifier: Identifier {
name: identifier_text.into(),
language_id: mat.language.id(),
},
range: node_range.clone(),
region: reference_region,
});
last_added_range = Some(node_range);
}
}
}
matches.advance();
}
references
}
#[cfg(test)]
mod test {
use gpui::{TestAppContext, prelude::*};
use indoc::indoc;
use language::{BufferSnapshot, Language, LanguageConfig, LanguageMatcher, tree_sitter_rust};
use crate::reference::{ReferenceRegion, references_in_range};
#[gpui::test]
fn test_identifier_node_truncated(cx: &mut TestAppContext) {
let code = indoc! { r#"
fn main() {
add(1, 2);
}
fn add(a: i32, b: i32) -> i32 {
a + b
}
"# };
let buffer = create_buffer(code, cx);
let range = 0..35;
let references = references_in_range(
range.clone(),
&code[range],
ReferenceRegion::Breadcrumb,
&buffer,
);
assert_eq!(references.len(), 2);
assert_eq!(references[0].identifier.name.as_ref(), "main");
assert_eq!(references[1].identifier.name.as_ref(), "add");
}
fn create_buffer(text: &str, cx: &mut TestAppContext) -> BufferSnapshot {
let buffer =
cx.new(|cx| language::Buffer::local(text, cx).with_language(rust_lang().into(), cx));
buffer.read_with(cx, |buffer, _| buffer.snapshot())
}
fn rust_lang() -> Language {
Language::new(
LanguageConfig {
name: "Rust".into(),
matcher: LanguageMatcher {
path_suffixes: vec!["rs".to_string()],
..Default::default()
},
..Default::default()
},
Some(tree_sitter_rust::LANGUAGE.into()),
)
.with_highlights_query(include_str!("../../languages/src/rust/highlights.scm"))
.unwrap()
.with_outline_query(include_str!("../../languages/src/rust/outline.scm"))
.unwrap()
}
}