use std::sync::Arc;
use php_ast::Stmt;
use tower_lsp_server::ls_types::{Location, Position, Range, Uri};
use super::walk::collect_var_refs_in_scope;
use crate::document::ast::{ParsedDoc, SourceView};
use crate::text::word_at_position;
use crate::types::resolve::{Container, Declaration, resolve_declaration};
pub fn goto_definition(
uri: &Uri,
source: &str,
doc: &ParsedDoc,
other_docs: &[(Uri, Arc<ParsedDoc>)],
position: Position,
) -> Option<Location> {
let word = word_at_position(source, position)?;
let sv = doc.view();
if word.starts_with('$') {
let bare = word.trim_start_matches('$');
let byte_off = sv.byte_of_position(position) as usize;
let mut spans = Vec::new();
collect_var_refs_in_scope(&doc.program().stmts, bare, byte_off, &mut spans);
if let Some((span, _)) = spans.into_iter().min_by_key(|(s, _)| s.start) {
let src_at_start = source.get(span.start as usize..).unwrap_or("");
let is_promoted = src_at_start.starts_with("private ")
|| src_at_start.starts_with("public ")
|| src_at_start.starts_with("protected ")
|| src_at_start.starts_with("readonly ");
let range = if is_promoted {
Range {
start: sv.position_of(span.start),
end: sv.position_of(span.end),
}
} else {
let name_with_sigil = format!("${bare}");
let precise_start =
crate::document::ast::str_offset_in_range(source, span, &name_with_sigil)
.unwrap_or(span.start);
Range {
start: sv.position_of(precise_start),
end: sv.position_of(precise_start + name_with_sigil.len() as u32),
}
};
return Some(Location {
uri: uri.clone(),
range,
});
}
}
if let Some(range) = resolve_definition_range(sv, &doc.program().stmts, &word) {
return Some(Location {
uri: uri.clone(),
range,
});
}
for (other_uri, other_doc) in other_docs {
let other_sv = other_doc.view();
if let Some(range) = resolve_definition_range(other_sv, &other_doc.program().stmts, &word) {
return Some(Location {
uri: other_uri.clone(),
range,
});
}
}
None
}
pub fn find_declaration_range(doc: &ParsedDoc, name: &str) -> Option<Range> {
let sv = doc.view();
resolve_definition_range(sv, &doc.program().stmts, name)
}
fn resolve_definition_range(
sv: SourceView<'_>,
stmts: &[Stmt<'_, '_>],
word: &str,
) -> Option<Range> {
let decl = resolve_declaration(stmts, word, &|d| {
!matches!(
d,
Declaration::ClassConst {
container: Container::Enum,
..
}
)
})?;
Some(definition_name_range(sv, &decl))
}
fn definition_name_range(sv: SourceView<'_>, decl: &Declaration<'_>) -> Range {
sv.name_range_in_span(decl.name(), decl.span())
}