use anyhow::Context;
use anyhow::Result;
use anyhow::anyhow;
use lsp_types::Location;
use url::Url;
use wdl_ast::AstNode;
use wdl_ast::SyntaxKind;
use wdl_ast::TreeToken;
use crate::SourcePosition;
use crate::SourcePositionEncoding;
use crate::graph::DocumentGraph;
use crate::handlers;
use crate::handlers::common::location_from_span;
use crate::handlers::common::position;
use crate::handlers::common::position_to_offset;
#[derive(Debug)]
struct TargetDefinition {
name: String,
location: Location,
}
pub fn find_all_references(
graph: &DocumentGraph,
document_uri: Url,
position: SourcePosition,
encoding: SourcePositionEncoding,
include_declaration: bool,
) -> Result<Vec<Location>> {
let definition_location = handlers::goto_definition(graph, document_uri, position, encoding)
.context("failed to resolve symbol definition")?
.ok_or_else(|| {
anyhow!(
"no definition location found for symbol at position: {}:{}",
position.line,
position.character
)
})?;
let doc_index = graph
.get_index(&definition_location.uri)
.ok_or_else(|| anyhow!("definition document not in graph"))?;
let node = graph.get(doc_index);
let document = node
.document()
.ok_or_else(|| anyhow!("definition document not analyzed"))?;
let lines = node
.parse_state()
.lines()
.ok_or_else(|| anyhow!("missing line index for target"))?;
let offset = position_to_offset(
lines,
SourcePosition::new(
definition_location.range.start.line,
definition_location.range.start.character,
),
encoding,
)
.context("failed to convert position to offset")?;
let token = document
.root()
.inner()
.token_at_offset(offset)
.find(|t| t.kind() == SyntaxKind::Ident)
.ok_or_else(|| anyhow!("could not find target token at definition site"))?;
let target = TargetDefinition {
name: token.text().to_string(),
location: definition_location.clone(),
};
let search_scope: Vec<_> = graph.transitive_dependents(doc_index).collect();
let mut locations = Vec::new();
for doc_index in search_scope {
collect_references_from_document(graph, doc_index, &target, encoding, &mut locations)
.with_context(|| {
format!("failed to collect references from document at index {doc_index:?}")
})?;
}
if !include_declaration {
locations.retain(|loc| *loc != target.location);
}
Ok(locations)
}
fn collect_references_from_document(
graph: &DocumentGraph,
doc_index: petgraph::graph::NodeIndex,
target: &TargetDefinition,
encoding: SourcePositionEncoding,
locations: &mut Vec<Location>,
) -> Result<()> {
let node = graph.get(doc_index);
let document = match node.document() {
Some(doc) => doc,
None => return Ok(()),
};
let lines = match node.parse_state().lines() {
Some(lines) => lines,
None => return Ok(()),
};
let root = document.root().inner().clone();
for token in root
.descendants_with_tokens()
.filter_map(|el| el.into_token())
{
if token.kind() == SyntaxKind::Ident && token.text() == target.name {
let token_pos = position(lines, token.text_range().start())
.context("failed to convert token position")?;
let source_pos = SourcePosition::new(token_pos.line, token_pos.character);
let resolved_location = handlers::goto_definition(
graph,
document.uri().as_ref().clone(),
source_pos,
encoding,
)
.context("failed to resolve token definition")?;
if let Some(location) = resolved_location
&& location == target.location
{
let reference_location = location_from_span(document.uri(), token.span(), lines)
.context("failed to create reference location")?;
locations.push(reference_location);
}
}
}
Ok(())
}