use std::sync::Arc;
use crate::base::FileId;
use crate::hir::{HirSymbol, RefKind, ResolveResult, Resolver, SymbolIndex, SymbolKind, TypeRef};
#[derive(Clone, Debug)]
pub struct GotoResult {
pub targets: Vec<GotoTarget>,
}
impl GotoResult {
pub fn empty() -> Self {
Self {
targets: Vec::new(),
}
}
pub fn single(target: GotoTarget) -> Self {
Self {
targets: vec![target],
}
}
pub fn multiple(targets: Vec<GotoTarget>) -> Self {
Self { targets }
}
pub fn is_empty(&self) -> bool {
self.targets.is_empty()
}
}
#[derive(Clone, Debug)]
pub struct GotoTarget {
pub file: FileId,
pub start_line: u32,
pub start_col: u32,
pub end_line: u32,
pub end_col: u32,
pub kind: SymbolKind,
pub name: Arc<str>,
}
impl From<&HirSymbol> for GotoTarget {
fn from(symbol: &HirSymbol) -> Self {
Self {
file: symbol.file,
start_line: symbol.start_line,
start_col: symbol.start_col,
end_line: symbol.end_line,
end_col: symbol.end_col,
kind: symbol.kind,
name: symbol.name.clone(),
}
}
}
pub fn goto_definition(index: &SymbolIndex, file: FileId, line: u32, col: u32) -> GotoResult {
if let Some((target_name, type_ref, source_symbol)) =
find_type_ref_at_position(index, file, line, col)
{
let scope = extract_scope(&source_symbol.qualified_name);
let resolver = Resolver::new(index).with_scope(scope);
let resolve_result = if type_ref.kind == RefKind::Expression {
resolver.resolve(&target_name)
} else {
resolver.resolve_type(&target_name)
};
match resolve_result {
ResolveResult::Found(def) => {
return GotoResult::single(GotoTarget::from(&def));
}
ResolveResult::Ambiguous(defs) => {
let targets = defs.iter().map(GotoTarget::from).collect();
return GotoResult::multiple(targets);
}
ResolveResult::NotFound => {
if let Some(def) = index.lookup_definition(&target_name) {
return GotoResult::single(GotoTarget::from(def));
}
}
}
}
let symbol = match find_symbol_at_position(index, file, line, col) {
Some(s) => s,
None => return GotoResult::empty(),
};
if symbol.kind.is_definition() {
return GotoResult::single(GotoTarget::from(symbol));
}
if !symbol.supertypes.is_empty() {
let type_name = &symbol.supertypes[0];
let scope = extract_scope(&symbol.qualified_name);
let resolver = Resolver::new(index).with_scope(scope);
match resolver.resolve_type(type_name) {
ResolveResult::Found(def) => {
return GotoResult::single(GotoTarget::from(&def));
}
ResolveResult::Ambiguous(defs) => {
let targets = defs.iter().map(GotoTarget::from).collect();
return GotoResult::multiple(targets);
}
ResolveResult::NotFound => {}
}
}
if let Some(def) = index.lookup_definition(&symbol.qualified_name) {
return GotoResult::single(GotoTarget::from(def));
}
GotoResult::empty()
}
pub fn goto_type_definition(index: &SymbolIndex, file: FileId, line: u32, col: u32) -> GotoResult {
if let Some((target_name, _type_ref, source_symbol)) =
find_type_ref_at_position(index, file, line, col)
{
let scope = extract_scope(&source_symbol.qualified_name);
let resolver = Resolver::new(index).with_scope(scope);
match resolver.resolve_type(&target_name) {
ResolveResult::Found(def) => {
return GotoResult::single(GotoTarget::from(&def));
}
ResolveResult::Ambiguous(defs) => {
let targets = defs.iter().map(GotoTarget::from).collect();
return GotoResult::multiple(targets);
}
ResolveResult::NotFound => {
if let Some(def) = index.lookup_definition(&target_name) {
return GotoResult::single(GotoTarget::from(def));
}
}
}
}
let symbol = match find_symbol_at_position(index, file, line, col) {
Some(s) => s,
None => return GotoResult::empty(),
};
if !symbol.supertypes.is_empty() {
let type_name = &symbol.supertypes[0];
let scope = extract_scope(&symbol.qualified_name);
let resolver = Resolver::new(index).with_scope(scope);
match resolver.resolve_type(type_name) {
ResolveResult::Found(def) => {
return GotoResult::single(GotoTarget::from(&def));
}
ResolveResult::Ambiguous(defs) => {
let targets = defs.iter().map(GotoTarget::from).collect();
return GotoResult::multiple(targets);
}
ResolveResult::NotFound => {
if let Some(def) = index.lookup_definition(type_name) {
return GotoResult::single(GotoTarget::from(def));
}
}
}
}
for type_ref_kind in &symbol.type_refs {
for tr in type_ref_kind.as_refs() {
if tr.kind == RefKind::TypedBy || tr.kind == RefKind::Specializes {
let scope = extract_scope(&symbol.qualified_name);
let resolver = Resolver::new(index).with_scope(scope);
match resolver.resolve_type(&tr.target) {
ResolveResult::Found(def) => {
return GotoResult::single(GotoTarget::from(&def));
}
ResolveResult::Ambiguous(defs) => {
let targets = defs.iter().map(GotoTarget::from).collect();
return GotoResult::multiple(targets);
}
ResolveResult::NotFound => {
if let Some(def) = index.lookup_definition(&tr.target) {
return GotoResult::single(GotoTarget::from(def));
}
}
}
}
}
}
GotoResult::empty()
}
fn find_type_ref_at_position(
index: &SymbolIndex,
file: FileId,
line: u32,
col: u32,
) -> Option<(Arc<str>, &TypeRef, &HirSymbol)> {
let symbols = index.symbols_in_file(file);
for symbol in symbols {
for type_ref_kind in &symbol.type_refs {
if type_ref_kind.contains(line, col) {
if let Some((_, tr)) = type_ref_kind.part_at(line, col) {
return Some((tr.target.clone(), tr, symbol));
}
}
}
}
None
}
fn find_symbol_at_position(
index: &SymbolIndex,
file: FileId,
line: u32,
col: u32,
) -> Option<&HirSymbol> {
let symbols = index.symbols_in_file(file);
let mut best: Option<&HirSymbol> = None;
for symbol in symbols {
if contains_position(symbol, line, col) {
match best {
None => best = Some(symbol),
Some(current) => {
if symbol_size(symbol) < symbol_size(current) {
best = Some(symbol);
}
}
}
}
}
best
}
fn contains_position(symbol: &HirSymbol, line: u32, col: u32) -> bool {
let after_start =
line > symbol.start_line || (line == symbol.start_line && col >= symbol.start_col);
let before_end = line < symbol.end_line || (line == symbol.end_line && col <= symbol.end_col);
after_start && before_end
}
fn symbol_size(symbol: &HirSymbol) -> u32 {
let line_diff = symbol.end_line.saturating_sub(symbol.start_line);
let col_diff = symbol.end_col.saturating_sub(symbol.start_col);
line_diff * 1000 + col_diff
}
fn extract_scope(qualified_name: &str) -> String {
if let Some(pos) = qualified_name.rfind("::") {
qualified_name[..pos].to_string()
} else {
String::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::hir::new_element_id;
fn make_symbol(
name: &str,
qualified: &str,
kind: SymbolKind,
file: u32,
line: u32,
) -> HirSymbol {
HirSymbol {
name: Arc::from(name),
short_name: None,
qualified_name: Arc::from(qualified),
element_id: new_element_id(),
kind,
file: FileId::new(file),
start_line: line,
start_col: 0,
end_line: line,
end_col: 10,
short_name_start_line: None,
short_name_start_col: None,
short_name_end_line: None,
short_name_end_col: None,
doc: None,
supertypes: Vec::new(),
relationships: Vec::new(),
type_refs: Vec::new(),
is_public: false,
view_data: None,
metadata_annotations: Vec::new(),
is_abstract: false,
is_variation: false,
is_readonly: false,
is_derived: false,
is_parallel: false,
is_individual: false,
is_end: false,
is_default: false,
is_ordered: false,
is_nonunique: false,
is_portion: false,
direction: None,
multiplicity: None,
}
}
#[test]
fn test_goto_definition_direct() {
let mut index = SymbolIndex::new();
let def = make_symbol("Car", "Vehicle::Car", SymbolKind::PartDefinition, 0, 5);
index.add_file(FileId::new(0), vec![def]);
let result = goto_definition(&index, FileId::new(0), 5, 5);
assert!(!result.is_empty());
assert_eq!(result.targets.len(), 1);
assert_eq!(result.targets[0].name.as_ref(), "Car");
}
#[test]
fn test_goto_definition_from_usage() {
let mut index = SymbolIndex::new();
let def = make_symbol("Engine", "Engine", SymbolKind::PartDefinition, 0, 1);
let mut usage = make_symbol("engine", "Car::engine", SymbolKind::PartUsage, 0, 10);
usage.supertypes = vec![Arc::from("Engine")];
index.add_file(FileId::new(0), vec![def, usage]);
let result = goto_definition(&index, FileId::new(0), 10, 5);
assert!(!result.is_empty());
assert_eq!(result.targets[0].name.as_ref(), "Engine");
assert_eq!(result.targets[0].start_line, 1); }
#[test]
fn test_goto_definition_from_type_ref() {
use crate::hir::{RefKind, TypeRef, TypeRefKind};
let mut index = SymbolIndex::new();
let def = make_symbol("Engine", "Engine", SymbolKind::PartDefinition, 0, 1);
let mut usage = make_symbol("engine", "Car::engine", SymbolKind::PartUsage, 0, 10);
usage.supertypes = vec![Arc::from("Engine")];
usage.type_refs = vec![TypeRefKind::Simple(TypeRef::new(
"Engine",
RefKind::TypedBy,
10,
15,
10,
21,
))];
index.add_file(FileId::new(0), vec![def, usage]);
let result = goto_definition(&index, FileId::new(0), 10, 17);
assert!(!result.is_empty());
assert_eq!(result.targets[0].name.as_ref(), "Engine");
assert_eq!(result.targets[0].start_line, 1); }
#[test]
fn test_goto_definition_not_found() {
let index = SymbolIndex::new();
let result = goto_definition(&index, FileId::new(0), 0, 0);
assert!(result.is_empty());
}
}