Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 65 additions & 10 deletions crates/ide/src/analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use vfs::FileId;

use crate::{
Cancellable, FilePosition, RangeInfo,
call_hierarchy::{self, CallHierarchyItem, IncomingCall, OutgoingCall},
code_action::{self, CodeAction, CodeActionDiagnostics, CodeActionResolveStrategy},
code_lens::{self, CodeLens, CodeLensConfig, CodeLensKind},
completion::{
Expand All @@ -29,15 +30,19 @@ use crate::{
diagnostics,
document_highlight::{self, DocumentHighlight, DocumentHighlightConfig},
document_symbols::{self, DocumentSymbol},
facts::{
SemanticFacts,
edit::{EditPlan, EditRequest},
},
folding_ranges::{self, Fold, FoldingConfig},
formatting::{self, FmtConfig},
goto_declaration, goto_definition,
hover::{self, HoverConfig},
inlay_hint::{self, InlayHint, InlayHintConfig},
markup::Markup,
navigation_target::NavTarget,
references::{self, References, ReferencesConfig},
rename::{self, RenameConfig, RenameResult},
references::{References, ReferencesConfig},
rename::{RecursiveRenameInfo, RenameCollisionInfo, RenameConfig, RenameResult},
selection_ranges,
semantic_index::{self, ModuleCallEdge},
semantic_tokens::{self, SemaToken, SemaTokenConfig},
Expand Down Expand Up @@ -182,7 +187,32 @@ impl Analysis {
position: FilePosition,
config: ReferencesConfig,
) -> Cancellable<Option<Vec<References>>> {
self.with_db(|db| references::references(db, position, config))
self.with_db(|db| {
crate::facts::SemanticFacts::new(db).relations().references(position, config)
})
}

pub fn prepare_call_hierarchy(
&self,
position: FilePosition,
) -> Cancellable<Option<Vec<CallHierarchyItem>>> {
self.with_db(|db| call_hierarchy::prepare(db, position))
}

pub fn call_hierarchy_incoming(
&self,
item: CallHierarchyItem,
config: ReferencesConfig,
) -> Cancellable<Option<Vec<IncomingCall>>> {
self.with_db(|db| call_hierarchy::incoming(db, item, config))
}

pub fn call_hierarchy_outgoing(
&self,
item: CallHierarchyItem,
config: ReferencesConfig,
) -> Cancellable<Option<Vec<OutgoingCall>>> {
self.with_db(|db| call_hierarchy::outgoing(db, item, config))
}

pub fn module_incoming_calls(
Expand All @@ -206,7 +236,11 @@ impl Analysis {
position: FilePosition,
config: RenameConfig,
) -> Cancellable<RenameResult<TextRange>> {
self.with_db(|db| rename::prepare_rename(db, position, config))
self.with_db(|db| {
SemanticFacts::new(db)
.edit_plan(EditRequest::PrepareRename { position, config })
.map(EditPlan::into_prepare_rename)
})
}

pub fn rename(
Expand All @@ -215,15 +249,23 @@ impl Analysis {
config: RenameConfig,
new_name: &str,
) -> Cancellable<RenameResult<SourceChange>> {
self.with_db(|db| rename::rename(db, position, config, new_name))
self.with_db(|db| {
SemanticFacts::new(db)
.edit_plan(EditRequest::Rename { position, config, new_name })
.map(EditPlan::into_source_change)
})
}

pub fn rename_expansion_info(
&self,
position: FilePosition,
config: RenameConfig,
) -> Cancellable<RenameResult<rename::RecursiveRenameInfo>> {
self.with_db(|db| rename::rename_expansion_info(db, position, config))
) -> Cancellable<RenameResult<RecursiveRenameInfo>> {
self.with_db(|db| {
SemanticFacts::new(db)
.edit_plan(EditRequest::RenameExpansionInfo { position, config })
.map(EditPlan::into_rename_expansion_info)
})
}

pub fn expanded_rename(
Expand All @@ -232,7 +274,11 @@ impl Analysis {
config: RenameConfig,
new_name: &str,
) -> Cancellable<RenameResult<SourceChange>> {
self.with_db(|db| rename::expanded_rename(db, position, config, new_name))
self.with_db(|db| {
SemanticFacts::new(db)
.edit_plan(EditRequest::ExpandedRename { position, config, new_name })
.map(EditPlan::into_source_change)
})
}

pub fn rename_conflict_info(
Expand All @@ -241,8 +287,17 @@ impl Analysis {
config: RenameConfig,
new_name: &str,
recursive: bool,
) -> Cancellable<RenameResult<rename::RenameCollisionInfo>> {
self.with_db(|db| rename::rename_conflict_info(db, position, config, new_name, recursive))
) -> Cancellable<RenameResult<RenameCollisionInfo>> {
self.with_db(|db| {
SemanticFacts::new(db)
.edit_plan(EditRequest::RenameConflictInfo {
position,
config,
new_name,
recursive,
})
.map(EditPlan::into_rename_conflict_info)
})
}

pub fn format(
Expand Down
158 changes: 158 additions & 0 deletions crates/ide/src/call_hierarchy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
use itertools::Itertools;

use crate::{
FilePosition, FileRange, SymbolKind,
db::root_db::RootDb,
facts::{
SemanticFacts,
relation::{CallSymbolKey, RelationFacts, RelationKind, RelationQuery},
symbol::{SymbolId, SymbolInfo},
},
references::ReferencesConfig,
};

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CallHierarchyItem {
pub symbol: Option<SymbolId>,
pub name: String,
pub kind: SymbolKind,
pub detail: Option<String>,
pub full_range: FileRange,
pub selection_range: FileRange,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IncomingCall {
pub from: CallHierarchyItem,
pub from_ranges: Vec<FileRange>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OutgoingCall {
pub to: CallHierarchyItem,
pub from_ranges: Vec<FileRange>,
}

pub(crate) fn prepare(db: &RootDb, position: FilePosition) -> Option<Vec<CallHierarchyItem>> {
let facts = SemanticFacts::new(db);
let relations = facts.relations();
let items = relations
.definition_symbols(position)?
.into_iter()
.filter_map(|symbol| call_hierarchy_item_for_symbol(&relations, symbol))
.unique_by(call_hierarchy_item_key)
.collect_vec();

(!items.is_empty()).then_some(items)
}

pub(crate) fn incoming(
db: &RootDb,
target: CallHierarchyItem,
config: ReferencesConfig,
) -> Option<Vec<IncomingCall>> {
let facts = SemanticFacts::new(db);
let relations = facts.relations();
let target = symbol_for_item(&relations, &target)?;
let relation_set = relations.relations(RelationQuery::Incoming {
target: target.id,
kind: RelationKind::Instantiates,
config,
});
let mut groups = Vec::<(SymbolId, Vec<FileRange>)>::new();
for relation in relation_set.relations {
push_call_range(&mut groups, relation.source, relation.range);
}

let calls: Vec<_> = groups
.into_iter()
.filter_map(|(source, from_ranges)| {
let from = call_hierarchy_item_for_symbol(&relations, relations.symbol(source)?)?;
Some(IncomingCall { from, from_ranges })
})
.collect();
(!calls.is_empty()).then_some(calls)
}

pub(crate) fn outgoing(
db: &RootDb,
caller: CallHierarchyItem,
config: ReferencesConfig,
) -> Option<Vec<OutgoingCall>> {
let facts = SemanticFacts::new(db);
let relations = facts.relations();
let caller = symbol_for_item(&relations, &caller)?;
let relation_set = relations.relations(RelationQuery::Outgoing {
source: caller.id,
kind: RelationKind::Instantiates,
config,
});
let mut groups = Vec::<(SymbolId, Vec<FileRange>)>::new();
for relation in relation_set.relations {
push_call_range(&mut groups, relation.target, relation.range);
}

let calls: Vec<_> = groups
.into_iter()
.filter_map(|(target, from_ranges)| {
let to = call_hierarchy_item_for_symbol(&relations, relations.symbol(target)?)?;
Some(OutgoingCall { to, from_ranges })
})
.collect();
(!calls.is_empty()).then_some(calls)
}

fn symbol_for_item(relations: &RelationFacts<'_>, item: &CallHierarchyItem) -> Option<SymbolInfo> {
if let Some(symbol) = item.symbol {
return relations.symbol(symbol);
}
relations.module_symbol_for_item(CallSymbolKey {
full_range: item.full_range,
selection_range: item.selection_range,
})
}

fn call_hierarchy_item_for_symbol(
relations: &RelationFacts<'_>,
symbol: SymbolInfo,
) -> Option<CallHierarchyItem> {
let kind = symbol.kind;
if !is_call_hierarchy_kind(kind) {
return None;
}

let full_range = symbol.definition_range?;
let selection_range = symbol.selection_range.unwrap_or(full_range);
let name = symbol.name.map(|name| name.to_string()).unwrap_or_else(|| "<anonymous>".to_owned());
let detail = symbol
.container
.and_then(|container| relations.symbol(container))
.and_then(|container| container.name.map(|name| name.to_string()));
Some(CallHierarchyItem {
symbol: Some(symbol.id),
name,
kind,
detail,
full_range,
selection_range,
})
}

fn is_call_hierarchy_kind(kind: SymbolKind) -> bool {
matches!(kind, SymbolKind::Module)
}

fn push_call_range(groups: &mut Vec<(SymbolId, Vec<FileRange>)>, item: SymbolId, range: FileRange) {
if let Some((_, ranges)) = groups.iter_mut().find(|(existing, _)| *existing == item) {
if !ranges.contains(&range) {
ranges.push(range);
}
return;
}

groups.push((item, vec![range]));
}

fn call_hierarchy_item_key(item: &CallHierarchyItem) -> (String, FileRange, FileRange) {
(item.name.clone(), item.full_range, item.selection_range)
}
Loading
Loading