use cairo_lang_diagnostics::ToOption;
use cairo_lang_filesystem::ids::FileId;
use cairo_lang_filesystem::span::{TextPosition, TextSpan};
use cairo_lang_parser::db::ParserGroup;
use cairo_lang_syntax::node::ast::TerminalIdentifier;
use cairo_lang_syntax::node::{SyntaxNode, Terminal};
use cairo_language_common::CommonGroup;
use salsa::Database;
pub trait LsSyntaxGroup: Database {
fn widest_node_within_span<'db>(
&'db self,
file: FileId<'db>,
span: TextSpan,
) -> Option<SyntaxNode<'db>> {
widest_node_within_span(self.as_dyn_database(), file, span)
}
fn widest_node_within_span_without_trivia<'db>(
&'db self,
file: FileId<'db>,
span: TextSpan,
) -> Option<SyntaxNode<'db>> {
widest_node_within_ex(self.as_dyn_database(), file, span, |db, node| {
node.span_without_trivia(db)
})
}
fn find_syntax_node_at_position<'db>(
&'db self,
file: FileId<'db>,
position: TextPosition,
) -> Option<SyntaxNode<'db>> {
find_syntax_node_at_position(self.as_dyn_database(), file, position)
}
fn find_identifier_at_position<'db>(
&'db self,
file: FileId<'db>,
position: TextPosition,
) -> Option<TerminalIdentifier<'db>> {
find_identifier_at_position(self.as_dyn_database(), file, position)
}
}
impl<T: Database + ?Sized> LsSyntaxGroup for T {}
#[cairo_lang_proc_macros::tracked]
fn widest_node_within_span<'db>(
db: &'db dyn Database,
file: FileId<'db>,
span: TextSpan,
) -> Option<SyntaxNode<'db>> {
widest_node_within_ex(db, file, span, |db, node| node.span(db))
}
fn widest_node_within_ex<'db>(
db: &'db dyn Database,
file: FileId<'db>,
span: TextSpan,
obtain_span: fn(&'db dyn Database, &SyntaxNode<'db>) -> TextSpan,
) -> Option<SyntaxNode<'db>> {
let precise_node = db.find_syntax_node_at_offset(file, span.start)?;
let nodes: Vec<_> = precise_node
.ancestors_with_self(db)
.take_while(|new_node| span.contains(obtain_span(db, new_node)))
.collect();
let last_node = nodes.last().cloned()?;
let last_node_span = obtain_span(db, &last_node);
nodes.into_iter().rev().take_while(|node| obtain_span(db, node) == last_node_span).last()
}
#[cairo_lang_proc_macros::tracked]
fn find_syntax_node_at_position<'db>(
db: &'db dyn Database,
file: FileId<'db>,
position: TextPosition,
) -> Option<SyntaxNode<'db>> {
Some(db.file_syntax(file).to_option()?.lookup_position(db, position))
}
fn find_identifier_at_position<'db>(
db: &'db dyn Database,
file: FileId<'db>,
position: TextPosition,
) -> Option<TerminalIdentifier<'db>> {
let find = |position: TextPosition| {
let node = db.find_syntax_node_at_position(file, position)?;
TerminalIdentifier::cast_token(db, node)
};
find(position).or_else(|| {
let col = position.col.checked_sub(1)?;
find(TextPosition { col, ..position })
})
}