mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-06 16:08:05 +00:00
97 lines
3.4 KiB
Rust
97 lines
3.4 KiB
Rust
//! Identifier occurrences by name (find-usages filtering, text-based rename preview).
|
|
|
|
use crate::kinds::in_comment_or_string;
|
|
use crate::types::IdentifierOccurrence;
|
|
use crate::{Ctx, StructureResult};
|
|
use tree_sitter::TreeCursor;
|
|
|
|
/// Leaf kinds that never name anything.
|
|
fn is_non_name_leaf(kind: &str) -> bool {
|
|
["comment", "string", "literal", "number", "integer", "float", "char", "escape", "content", "fragment", "text"]
|
|
.iter()
|
|
.any(|k| kind.contains(k))
|
|
}
|
|
|
|
/// Ancestors of the cursor's node, parent first (cursor moves are O(1),
|
|
/// unlike `Node::parent`).
|
|
fn ancestors<'t>(cursor: &TreeCursor<'t>) -> impl Iterator<Item = tree_sitter::Node<'t>> {
|
|
let mut up = cursor.clone();
|
|
std::iter::from_fn(move || up.goto_parent().then(|| up.node()))
|
|
}
|
|
|
|
/// Moves `cursor` (forward only, occurrences come in document order) to the
|
|
/// smallest node containing byte `s`; returns false when `s` is in no node.
|
|
fn seek(cursor: &mut TreeCursor, s: usize) -> bool {
|
|
// Climb / step right until the current node reaches past `s`.
|
|
loop {
|
|
let n = cursor.node();
|
|
if n.end_byte() > s {
|
|
if n.start_byte() > s {
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
if cursor.goto_next_sibling() {
|
|
continue;
|
|
}
|
|
if !cursor.goto_parent() {
|
|
return false;
|
|
}
|
|
}
|
|
// Descend to the innermost child containing `s`.
|
|
while cursor.goto_first_child() {
|
|
while cursor.node().end_byte() <= s {
|
|
if !cursor.goto_next_sibling() {
|
|
break;
|
|
}
|
|
}
|
|
let n = cursor.node();
|
|
if !(n.start_byte() <= s && s < n.end_byte()) {
|
|
cursor.goto_parent();
|
|
break;
|
|
}
|
|
}
|
|
true
|
|
}
|
|
|
|
pub(crate) fn identifiers_named(cx: &Ctx, name: &str) -> StructureResult<Vec<IdentifierOccurrence>> {
|
|
let text = cx.text();
|
|
let mut cursor = cx.tree.root_node().walk();
|
|
let mut out = Vec::new();
|
|
for (i, start) in memchr::memmem::find_iter(text.as_bytes(), name.as_bytes()).enumerate() {
|
|
if i % 1024 == 1023 {
|
|
cx.check()?;
|
|
}
|
|
let end = start + name.len();
|
|
// Two word characters are never split between tokens: skip `counter` for `count`.
|
|
let word = |b: Option<&u8>| b.is_some_and(|b| b.is_ascii_alphanumeric() || *b == b'_');
|
|
let bytes = text.as_bytes();
|
|
if (name.as_bytes()[0].is_ascii_alphanumeric() && word(start.checked_sub(1).and_then(|i| bytes.get(i))))
|
|
|| (name.as_bytes()[name.len() - 1].is_ascii_alphanumeric() && word(bytes.get(end)))
|
|
{
|
|
continue;
|
|
}
|
|
if !seek(&mut cursor, start) {
|
|
continue;
|
|
}
|
|
let node = cursor.node();
|
|
if node.start_byte() != start || node.end_byte() != end || node.child_count() != 0 {
|
|
continue;
|
|
}
|
|
let kind = node.kind();
|
|
let word_like = kind.contains("identifier") || node.is_named();
|
|
if !word_like || is_non_name_leaf(kind) || node.is_missing() || in_comment_or_string(&node, ancestors(&cursor))
|
|
{
|
|
continue;
|
|
}
|
|
let mut up = ancestors(&cursor);
|
|
let (parent, grandparent) = (up.next(), up.next());
|
|
out.push(IdentifierOccurrence {
|
|
range: cx.lines.range(start, end),
|
|
kind: kind.to_owned(),
|
|
qualified: cx.kinds.is_qualified_in(&node, parent, grandparent),
|
|
});
|
|
}
|
|
Ok(out)
|
|
}
|