mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-11-15 01:17:27 +00:00
Group references by FileId
This commit is contained in:
parent
52fa926f00
commit
fbdb32adfc
10 changed files with 322 additions and 254 deletions
|
@ -2,12 +2,16 @@ use std::iter;
|
|||
|
||||
use either::Either;
|
||||
use hir::{AsName, Module, ModuleDef, Name, Variant};
|
||||
use ide_db::helpers::{
|
||||
insert_use::{insert_use, ImportScope},
|
||||
mod_path_to_ast,
|
||||
use ide_db::{
|
||||
defs::Definition,
|
||||
helpers::{
|
||||
insert_use::{insert_use, ImportScope},
|
||||
mod_path_to_ast,
|
||||
},
|
||||
search::{FileReference, FileReferences},
|
||||
RootDatabase,
|
||||
};
|
||||
use ide_db::{defs::Definition, search::Reference, RootDatabase};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use rustc_hash::FxHashSet;
|
||||
use syntax::{
|
||||
algo::{find_node_at_offset, SyntaxRewriter},
|
||||
ast::{self, edit::IndentLevel, make, AstNode, NameOwner, VisibilityOwner},
|
||||
|
@ -58,29 +62,29 @@ pub(crate) fn extract_struct_from_enum_variant(
|
|||
let mut visited_modules_set = FxHashSet::default();
|
||||
let current_module = enum_hir.module(ctx.db());
|
||||
visited_modules_set.insert(current_module);
|
||||
let mut rewriters = FxHashMap::default();
|
||||
for reference in usages {
|
||||
let rewriter = rewriters
|
||||
.entry(reference.file_range.file_id)
|
||||
.or_insert_with(SyntaxRewriter::default);
|
||||
let source_file = ctx.sema.parse(reference.file_range.file_id);
|
||||
update_reference(
|
||||
ctx,
|
||||
rewriter,
|
||||
reference,
|
||||
&source_file,
|
||||
&enum_module_def,
|
||||
&variant_hir_name,
|
||||
&mut visited_modules_set,
|
||||
);
|
||||
}
|
||||
let mut rewriter =
|
||||
rewriters.remove(&ctx.frange.file_id).unwrap_or_else(SyntaxRewriter::default);
|
||||
for (file_id, rewriter) in rewriters {
|
||||
let mut def_rewriter = None;
|
||||
for FileReferences { file_id, references: refs } in usages {
|
||||
let mut rewriter = SyntaxRewriter::default();
|
||||
let source_file = ctx.sema.parse(file_id);
|
||||
for reference in refs {
|
||||
update_reference(
|
||||
ctx,
|
||||
&mut rewriter,
|
||||
reference,
|
||||
&source_file,
|
||||
&enum_module_def,
|
||||
&variant_hir_name,
|
||||
&mut visited_modules_set,
|
||||
);
|
||||
}
|
||||
if file_id == ctx.frange.file_id {
|
||||
def_rewriter = Some(rewriter);
|
||||
continue;
|
||||
}
|
||||
builder.edit_file(file_id);
|
||||
builder.rewrite(rewriter);
|
||||
}
|
||||
builder.edit_file(ctx.frange.file_id);
|
||||
let mut rewriter = def_rewriter.unwrap_or_default();
|
||||
update_variant(&mut rewriter, &variant);
|
||||
extract_struct_def(
|
||||
&mut rewriter,
|
||||
|
@ -90,6 +94,7 @@ pub(crate) fn extract_struct_from_enum_variant(
|
|||
&variant.parent_enum().syntax().clone().into(),
|
||||
enum_ast.visibility(),
|
||||
);
|
||||
builder.edit_file(ctx.frange.file_id);
|
||||
builder.rewrite(rewriter);
|
||||
},
|
||||
)
|
||||
|
@ -205,13 +210,13 @@ fn update_variant(rewriter: &mut SyntaxRewriter, variant: &ast::Variant) -> Opti
|
|||
fn update_reference(
|
||||
ctx: &AssistContext,
|
||||
rewriter: &mut SyntaxRewriter,
|
||||
reference: Reference,
|
||||
reference: FileReference,
|
||||
source_file: &SourceFile,
|
||||
enum_module_def: &ModuleDef,
|
||||
variant_hir_name: &Name,
|
||||
visited_modules_set: &mut FxHashSet<Module>,
|
||||
) -> Option<()> {
|
||||
let offset = reference.file_range.range.start();
|
||||
let offset = reference.range.start();
|
||||
let (segment, expr) = if let Some(path_expr) =
|
||||
find_node_at_offset::<ast::PathExpr>(source_file.syntax(), offset)
|
||||
{
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
use ide_db::{defs::Definition, search::ReferenceKind};
|
||||
use ide_db::{
|
||||
defs::Definition,
|
||||
search::{FileReference, ReferenceKind},
|
||||
};
|
||||
use syntax::{
|
||||
ast::{self, AstNode, AstToken},
|
||||
TextRange,
|
||||
|
@ -63,48 +66,44 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> O
|
|||
let_stmt.syntax().text_range()
|
||||
};
|
||||
|
||||
let mut wrap_in_parens = vec![true; refs.len()];
|
||||
let wrap_in_parens = refs
|
||||
.iter()
|
||||
.flat_map(|refs| &refs.references)
|
||||
.map(|&FileReference { range, .. }| {
|
||||
let usage_node =
|
||||
ctx.covering_node_for_range(range).ancestors().find_map(ast::PathExpr::cast)?;
|
||||
let usage_parent_option = usage_node.syntax().parent().and_then(ast::Expr::cast);
|
||||
let usage_parent = match usage_parent_option {
|
||||
Some(u) => u,
|
||||
None => return Ok(false),
|
||||
};
|
||||
|
||||
for (i, desc) in refs.iter().enumerate() {
|
||||
let usage_node = ctx
|
||||
.covering_node_for_range(desc.file_range.range)
|
||||
.ancestors()
|
||||
.find_map(ast::PathExpr::cast)?;
|
||||
let usage_parent_option = usage_node.syntax().parent().and_then(ast::Expr::cast);
|
||||
let usage_parent = match usage_parent_option {
|
||||
Some(u) => u,
|
||||
None => {
|
||||
wrap_in_parens[i] = false;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
wrap_in_parens[i] = match (&initializer_expr, usage_parent) {
|
||||
(ast::Expr::CallExpr(_), _)
|
||||
| (ast::Expr::IndexExpr(_), _)
|
||||
| (ast::Expr::MethodCallExpr(_), _)
|
||||
| (ast::Expr::FieldExpr(_), _)
|
||||
| (ast::Expr::TryExpr(_), _)
|
||||
| (ast::Expr::RefExpr(_), _)
|
||||
| (ast::Expr::Literal(_), _)
|
||||
| (ast::Expr::TupleExpr(_), _)
|
||||
| (ast::Expr::ArrayExpr(_), _)
|
||||
| (ast::Expr::ParenExpr(_), _)
|
||||
| (ast::Expr::PathExpr(_), _)
|
||||
| (ast::Expr::BlockExpr(_), _)
|
||||
| (ast::Expr::EffectExpr(_), _)
|
||||
| (_, ast::Expr::CallExpr(_))
|
||||
| (_, ast::Expr::TupleExpr(_))
|
||||
| (_, ast::Expr::ArrayExpr(_))
|
||||
| (_, ast::Expr::ParenExpr(_))
|
||||
| (_, ast::Expr::ForExpr(_))
|
||||
| (_, ast::Expr::WhileExpr(_))
|
||||
| (_, ast::Expr::BreakExpr(_))
|
||||
| (_, ast::Expr::ReturnExpr(_))
|
||||
| (_, ast::Expr::MatchExpr(_)) => false,
|
||||
_ => true,
|
||||
};
|
||||
}
|
||||
Ok(!matches!((&initializer_expr, usage_parent),
|
||||
(ast::Expr::CallExpr(_), _)
|
||||
| (ast::Expr::IndexExpr(_), _)
|
||||
| (ast::Expr::MethodCallExpr(_), _)
|
||||
| (ast::Expr::FieldExpr(_), _)
|
||||
| (ast::Expr::TryExpr(_), _)
|
||||
| (ast::Expr::RefExpr(_), _)
|
||||
| (ast::Expr::Literal(_), _)
|
||||
| (ast::Expr::TupleExpr(_), _)
|
||||
| (ast::Expr::ArrayExpr(_), _)
|
||||
| (ast::Expr::ParenExpr(_), _)
|
||||
| (ast::Expr::PathExpr(_), _)
|
||||
| (ast::Expr::BlockExpr(_), _)
|
||||
| (ast::Expr::EffectExpr(_), _)
|
||||
| (_, ast::Expr::CallExpr(_))
|
||||
| (_, ast::Expr::TupleExpr(_))
|
||||
| (_, ast::Expr::ArrayExpr(_))
|
||||
| (_, ast::Expr::ParenExpr(_))
|
||||
| (_, ast::Expr::ForExpr(_))
|
||||
| (_, ast::Expr::WhileExpr(_))
|
||||
| (_, ast::Expr::BreakExpr(_))
|
||||
| (_, ast::Expr::ReturnExpr(_))
|
||||
| (_, ast::Expr::MatchExpr(_))
|
||||
))
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
let init_str = initializer_expr.syntax().text().to_string();
|
||||
let init_in_paren = format!("({})", &init_str);
|
||||
|
@ -116,15 +115,17 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> O
|
|||
target,
|
||||
move |builder| {
|
||||
builder.delete(delete_range);
|
||||
for (desc, should_wrap) in refs.iter().zip(wrap_in_parens) {
|
||||
for (reference, should_wrap) in
|
||||
refs.iter().flat_map(|refs| &refs.references).zip(wrap_in_parens)
|
||||
{
|
||||
let replacement =
|
||||
if should_wrap { init_in_paren.clone() } else { init_str.clone() };
|
||||
match desc.kind {
|
||||
match reference.kind {
|
||||
ReferenceKind::FieldShorthandForLocal => {
|
||||
mark::hit!(inline_field_shorthand);
|
||||
builder.insert(desc.file_range.range.end(), format!(": {}", replacement))
|
||||
builder.insert(reference.range.end(), format!(": {}", replacement))
|
||||
}
|
||||
_ => builder.replace(desc.file_range.range, replacement),
|
||||
_ => builder.replace(reference.range, replacement),
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
use ide_db::{defs::Definition, search::Reference};
|
||||
use ide_db::{
|
||||
defs::Definition,
|
||||
search::{FileReference, FileReferences},
|
||||
};
|
||||
use syntax::{
|
||||
algo::find_node_at_range,
|
||||
ast::{self, ArgListOwner},
|
||||
AstNode, SyntaxKind, SyntaxNode, TextRange, T,
|
||||
AstNode, SourceFile, SyntaxKind, SyntaxNode, TextRange, T,
|
||||
};
|
||||
use test_utils::mark;
|
||||
use SyntaxKind::WHITESPACE;
|
||||
|
@ -58,32 +61,40 @@ pub(crate) fn remove_unused_param(acc: &mut Assists, ctx: &AssistContext) -> Opt
|
|||
param.syntax().text_range(),
|
||||
|builder| {
|
||||
builder.delete(range_to_remove(param.syntax()));
|
||||
for usage in fn_def.usages(&ctx.sema).all() {
|
||||
process_usage(ctx, builder, usage, param_position);
|
||||
for usages in fn_def.usages(&ctx.sema).all() {
|
||||
process_usages(ctx, builder, usages, param_position);
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn process_usage(
|
||||
fn process_usages(
|
||||
ctx: &AssistContext,
|
||||
builder: &mut AssistBuilder,
|
||||
usage: Reference,
|
||||
usages: FileReferences,
|
||||
arg_to_remove: usize,
|
||||
) -> Option<()> {
|
||||
let source_file = ctx.sema.parse(usage.file_range.file_id);
|
||||
let call_expr: ast::CallExpr =
|
||||
find_node_at_range(source_file.syntax(), usage.file_range.range)?;
|
||||
) {
|
||||
let source_file = ctx.sema.parse(usages.file_id);
|
||||
builder.edit_file(usages.file_id);
|
||||
for usage in usages.references {
|
||||
if let Some(text_range) = process_usage(&source_file, usage, arg_to_remove) {
|
||||
builder.delete(text_range);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process_usage(
|
||||
source_file: &SourceFile,
|
||||
FileReference { range, .. }: FileReference,
|
||||
arg_to_remove: usize,
|
||||
) -> Option<TextRange> {
|
||||
let call_expr: ast::CallExpr = find_node_at_range(source_file.syntax(), range)?;
|
||||
let call_expr_range = call_expr.expr()?.syntax().text_range();
|
||||
if !call_expr_range.contains_range(usage.file_range.range) {
|
||||
if !call_expr_range.contains_range(range) {
|
||||
return None;
|
||||
}
|
||||
let arg = call_expr.arg_list()?.args().nth(arg_to_remove)?;
|
||||
|
||||
builder.edit_file(usage.file_range.file_id);
|
||||
builder.delete(range_to_remove(arg.syntax()));
|
||||
|
||||
Some(())
|
||||
Some(range_to_remove(arg.syntax()))
|
||||
}
|
||||
|
||||
fn range_to_remove(node: &SyntaxNode) -> TextRange {
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
use indexmap::IndexMap;
|
||||
|
||||
use hir::Semantics;
|
||||
use ide_db::call_info::FnCallNode;
|
||||
use ide_db::RootDatabase;
|
||||
use ide_db::{call_info::FnCallNode, search::FileReferences};
|
||||
use syntax::{ast, AstNode, TextRange};
|
||||
|
||||
use crate::{
|
||||
|
@ -47,22 +47,23 @@ pub(crate) fn incoming_calls(db: &RootDatabase, position: FilePosition) -> Optio
|
|||
|
||||
let mut calls = CallLocations::default();
|
||||
|
||||
for reference in refs.info.references() {
|
||||
let file_id = reference.file_range.file_id;
|
||||
for &FileReferences { file_id, ref references } in refs.info.references() {
|
||||
let file = sema.parse(file_id);
|
||||
let file = file.syntax();
|
||||
let token = file.token_at_offset(reference.file_range.range.start()).next()?;
|
||||
let token = sema.descend_into_macros(token);
|
||||
let syntax = token.parent();
|
||||
for reference in references {
|
||||
let token = file.token_at_offset(reference.range.start()).next()?;
|
||||
let token = sema.descend_into_macros(token);
|
||||
let syntax = token.parent();
|
||||
|
||||
// This target is the containing function
|
||||
if let Some(nav) = syntax.ancestors().find_map(|node| {
|
||||
let fn_ = ast::Fn::cast(node)?;
|
||||
let def = sema.to_def(&fn_)?;
|
||||
def.try_to_nav(sema.db)
|
||||
}) {
|
||||
let relative_range = reference.file_range.range;
|
||||
calls.add(&nav, relative_range);
|
||||
// This target is the containing function
|
||||
if let Some(nav) = syntax.ancestors().find_map(|node| {
|
||||
let fn_ = ast::Fn::cast(node)?;
|
||||
let def = sema.to_def(&fn_)?;
|
||||
def.try_to_nav(sema.db)
|
||||
}) {
|
||||
let relative_range = reference.range;
|
||||
calls.add(&nav, relative_range);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -92,7 +92,7 @@ pub use ide_db::base_db::{
|
|||
};
|
||||
pub use ide_db::{
|
||||
call_info::CallInfo,
|
||||
search::{Reference, ReferenceAccess, ReferenceKind},
|
||||
search::{FileReference, ReferenceAccess, ReferenceKind},
|
||||
};
|
||||
pub use ide_db::{
|
||||
label::Label,
|
||||
|
|
|
@ -14,8 +14,7 @@ pub(crate) mod rename;
|
|||
use hir::Semantics;
|
||||
use ide_db::{
|
||||
defs::{Definition, NameClass, NameRefClass},
|
||||
search::Reference,
|
||||
search::{ReferenceAccess, ReferenceKind, SearchScope},
|
||||
search::{FileReference, FileReferences, ReferenceAccess, ReferenceKind, SearchScope},
|
||||
RootDatabase,
|
||||
};
|
||||
use syntax::{
|
||||
|
@ -29,7 +28,7 @@ use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeI
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct ReferenceSearchResult {
|
||||
declaration: Declaration,
|
||||
references: Vec<Reference>,
|
||||
references: Vec<FileReferences>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -48,7 +47,7 @@ impl ReferenceSearchResult {
|
|||
&self.declaration.nav
|
||||
}
|
||||
|
||||
pub fn references(&self) -> &[Reference] {
|
||||
pub fn references(&self) -> &[FileReferences] {
|
||||
&self.references
|
||||
}
|
||||
|
||||
|
@ -63,20 +62,22 @@ impl ReferenceSearchResult {
|
|||
// allow turning ReferenceSearchResult into an iterator
|
||||
// over References
|
||||
impl IntoIterator for ReferenceSearchResult {
|
||||
type Item = Reference;
|
||||
type IntoIter = std::vec::IntoIter<Reference>;
|
||||
type Item = FileReferences;
|
||||
type IntoIter = std::vec::IntoIter<FileReferences>;
|
||||
|
||||
fn into_iter(mut self) -> Self::IntoIter {
|
||||
let mut v = Vec::with_capacity(self.len());
|
||||
v.push(Reference {
|
||||
file_range: FileRange {
|
||||
file_id: self.declaration.nav.file_id,
|
||||
range: self.declaration.nav.focus_or_full_range(),
|
||||
},
|
||||
v.append(&mut self.references);
|
||||
let decl_ref = FileReference {
|
||||
range: self.declaration.nav.focus_or_full_range(),
|
||||
kind: self.declaration.kind,
|
||||
access: self.declaration.access,
|
||||
});
|
||||
v.append(&mut self.references);
|
||||
};
|
||||
let file_id = self.declaration.nav.file_id;
|
||||
match v.iter_mut().find(|it| it.file_id == file_id) {
|
||||
Some(file_refs) => file_refs.references.push(decl_ref),
|
||||
None => v.push(FileReferences { file_id, references: vec![decl_ref] }),
|
||||
}
|
||||
v.into_iter()
|
||||
}
|
||||
}
|
||||
|
@ -109,13 +110,11 @@ pub(crate) fn find_all_refs(
|
|||
|
||||
let RangeInfo { range, info: def } = find_name(&sema, &syntax, position, opt_name)?;
|
||||
|
||||
let references = def
|
||||
.usages(sema)
|
||||
.set_scope(search_scope)
|
||||
.all()
|
||||
.into_iter()
|
||||
.filter(|r| search_kind == ReferenceKind::Other || search_kind == r.kind)
|
||||
.collect();
|
||||
let mut references = def.usages(sema).set_scope(search_scope).all();
|
||||
references.iter_mut().for_each(|it| {
|
||||
it.references.retain(|r| search_kind == ReferenceKind::Other || search_kind == r.kind)
|
||||
});
|
||||
references.retain(|r| !r.references.is_empty());
|
||||
|
||||
let nav = def.try_to_nav(sema.db)?;
|
||||
let decl_range = nav.focus_or_full_range();
|
||||
|
@ -255,7 +254,8 @@ fn try_find_self_references(
|
|||
syntax: &SyntaxNode,
|
||||
position: FilePosition,
|
||||
) -> Option<RangeInfo<ReferenceSearchResult>> {
|
||||
let self_token = syntax.token_at_offset(position.offset).find(|t| t.kind() == T![self])?;
|
||||
let FilePosition { file_id, offset } = position;
|
||||
let self_token = syntax.token_at_offset(offset).find(|t| t.kind() == T![self])?;
|
||||
let parent = self_token.parent();
|
||||
match_ast! {
|
||||
match parent {
|
||||
|
@ -276,7 +276,7 @@ fn try_find_self_references(
|
|||
|
||||
let declaration = Declaration {
|
||||
nav: NavigationTarget {
|
||||
file_id: position.file_id,
|
||||
file_id,
|
||||
full_range: self_param.syntax().text_range(),
|
||||
focus_range: Some(param_self_token.text_range()),
|
||||
name: param_self_token.text().clone(),
|
||||
|
@ -295,25 +295,29 @@ fn try_find_self_references(
|
|||
let references = function
|
||||
.body()
|
||||
.map(|body| {
|
||||
body.syntax()
|
||||
.descendants()
|
||||
.filter_map(ast::PathExpr::cast)
|
||||
.filter_map(|expr| {
|
||||
let path = expr.path()?;
|
||||
if path.qualifier().is_none() {
|
||||
path.segment()?.self_token()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.map(|token| Reference {
|
||||
file_range: FileRange { file_id: position.file_id, range: token.text_range() },
|
||||
kind: ReferenceKind::SelfKw,
|
||||
access: declaration.access, // FIXME: properly check access kind here instead of copying it from the declaration
|
||||
})
|
||||
.collect()
|
||||
FileReferences {
|
||||
file_id,
|
||||
references: body
|
||||
.syntax()
|
||||
.descendants()
|
||||
.filter_map(ast::PathExpr::cast)
|
||||
.filter_map(|expr| {
|
||||
let path = expr.path()?;
|
||||
if path.qualifier().is_none() {
|
||||
path.segment()?.self_token()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.map(|token| FileReference {
|
||||
range: token.text_range(),
|
||||
kind: ReferenceKind::SelfKw,
|
||||
access: declaration.access, // FIXME: properly check access kind here instead of copying it from the declaration
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
})
|
||||
.unwrap_or_default();
|
||||
.map_or_else(Vec::default, |it| vec![it]);
|
||||
|
||||
Some(RangeInfo::new(
|
||||
param_self_token.text_range(),
|
||||
|
@ -324,7 +328,7 @@ fn try_find_self_references(
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use expect_test::{expect, Expect};
|
||||
use ide_db::base_db::FileId;
|
||||
use ide_db::{base_db::FileId, search::FileReferences};
|
||||
use stdx::format_to;
|
||||
|
||||
use crate::{fixture, SearchScope};
|
||||
|
@ -1018,12 +1022,14 @@ impl Foo {
|
|||
actual += "\n\n";
|
||||
}
|
||||
|
||||
for r in &refs.references {
|
||||
format_to!(actual, "{:?} {:?} {:?}", r.file_range.file_id, r.file_range.range, r.kind);
|
||||
if let Some(access) = r.access {
|
||||
format_to!(actual, " {:?}", access);
|
||||
for FileReferences { file_id, references } in refs.references {
|
||||
for r in references {
|
||||
format_to!(actual, "{:?} {:?} {:?}", file_id, r.range, r.kind);
|
||||
if let Some(access) = r.access {
|
||||
format_to!(actual, " {:?}", access);
|
||||
}
|
||||
actual += "\n";
|
||||
}
|
||||
actual += "\n";
|
||||
}
|
||||
expect.assert_eq(&actual)
|
||||
}
|
||||
|
|
|
@ -6,9 +6,10 @@ use std::{
|
|||
};
|
||||
|
||||
use hir::{Module, ModuleDef, ModuleSource, Semantics};
|
||||
use ide_db::base_db::{AnchoredPathBuf, FileId, FileRange, SourceDatabaseExt};
|
||||
use ide_db::{
|
||||
base_db::{AnchoredPathBuf, FileId, FileRange, SourceDatabaseExt},
|
||||
defs::{Definition, NameClass, NameRefClass},
|
||||
search::FileReferences,
|
||||
RootDatabase,
|
||||
};
|
||||
use syntax::{
|
||||
|
@ -20,8 +21,8 @@ use test_utils::mark;
|
|||
use text_edit::TextEdit;
|
||||
|
||||
use crate::{
|
||||
FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind, ReferenceSearchResult,
|
||||
SourceChange, SourceFileEdit, TextRange, TextSize,
|
||||
FilePosition, FileSystemEdit, RangeInfo, ReferenceKind, ReferenceSearchResult, SourceChange,
|
||||
SourceFileEdit, TextRange, TextSize,
|
||||
};
|
||||
|
||||
type RenameResult<T> = Result<T, RenameError>;
|
||||
|
@ -173,39 +174,45 @@ fn find_all_refs(
|
|||
.ok_or_else(|| format_err!("No references found at position"))
|
||||
}
|
||||
|
||||
fn source_edit_from_reference(
|
||||
fn source_edit_from_references(
|
||||
sema: &Semantics<RootDatabase>,
|
||||
reference: Reference,
|
||||
&FileReferences { file_id, ref references }: &FileReferences,
|
||||
new_name: &str,
|
||||
) -> SourceFileEdit {
|
||||
let mut replacement_text = String::new();
|
||||
let range = match reference.kind {
|
||||
ReferenceKind::FieldShorthandForField => {
|
||||
mark::hit!(test_rename_struct_field_for_shorthand);
|
||||
replacement_text.push_str(new_name);
|
||||
replacement_text.push_str(": ");
|
||||
TextRange::new(reference.file_range.range.start(), reference.file_range.range.start())
|
||||
}
|
||||
ReferenceKind::FieldShorthandForLocal => {
|
||||
mark::hit!(test_rename_local_for_field_shorthand);
|
||||
replacement_text.push_str(": ");
|
||||
replacement_text.push_str(new_name);
|
||||
TextRange::new(reference.file_range.range.end(), reference.file_range.range.end())
|
||||
}
|
||||
ReferenceKind::RecordFieldExprOrPat => {
|
||||
mark::hit!(test_rename_field_expr_pat);
|
||||
replacement_text.push_str(new_name);
|
||||
edit_text_range_for_record_field_expr_or_pat(sema, reference.file_range, new_name)
|
||||
}
|
||||
_ => {
|
||||
replacement_text.push_str(new_name);
|
||||
reference.file_range.range
|
||||
}
|
||||
};
|
||||
SourceFileEdit {
|
||||
file_id: reference.file_range.file_id,
|
||||
edit: TextEdit::replace(range, replacement_text),
|
||||
let mut edit = TextEdit::builder();
|
||||
for reference in references {
|
||||
let mut replacement_text = String::new();
|
||||
let range = match reference.kind {
|
||||
ReferenceKind::FieldShorthandForField => {
|
||||
mark::hit!(test_rename_struct_field_for_shorthand);
|
||||
replacement_text.push_str(new_name);
|
||||
replacement_text.push_str(": ");
|
||||
TextRange::new(reference.range.start(), reference.range.start())
|
||||
}
|
||||
ReferenceKind::FieldShorthandForLocal => {
|
||||
mark::hit!(test_rename_local_for_field_shorthand);
|
||||
replacement_text.push_str(": ");
|
||||
replacement_text.push_str(new_name);
|
||||
TextRange::new(reference.range.end(), reference.range.end())
|
||||
}
|
||||
ReferenceKind::RecordFieldExprOrPat => {
|
||||
mark::hit!(test_rename_field_expr_pat);
|
||||
replacement_text.push_str(new_name);
|
||||
edit_text_range_for_record_field_expr_or_pat(
|
||||
sema,
|
||||
FileRange { file_id, range: reference.range },
|
||||
new_name,
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
replacement_text.push_str(new_name);
|
||||
reference.range
|
||||
}
|
||||
};
|
||||
edit.replace(range, replacement_text);
|
||||
}
|
||||
|
||||
SourceFileEdit { file_id, edit: edit.finish() }
|
||||
}
|
||||
|
||||
fn edit_text_range_for_record_field_expr_or_pat(
|
||||
|
@ -277,9 +284,9 @@ fn rename_mod(
|
|||
|
||||
let RangeInfo { range, info: refs } = find_all_refs(sema, position)?;
|
||||
let ref_edits = refs
|
||||
.references
|
||||
.into_iter()
|
||||
.map(|reference| source_edit_from_reference(sema, reference, new_name));
|
||||
.references()
|
||||
.iter()
|
||||
.map(|reference| source_edit_from_references(sema, reference, new_name));
|
||||
source_file_edits.extend(ref_edits);
|
||||
|
||||
Ok(RangeInfo::new(range, SourceChange::from_edits(source_file_edits, file_system_edits)))
|
||||
|
@ -331,17 +338,10 @@ fn rename_to_self(
|
|||
|
||||
let RangeInfo { range, info: refs } = find_all_refs(sema, position)?;
|
||||
|
||||
let (param_ref, usages): (Vec<Reference>, Vec<Reference>) = refs
|
||||
.into_iter()
|
||||
.partition(|reference| param_range.intersect(reference.file_range.range).is_some());
|
||||
|
||||
if param_ref.is_empty() {
|
||||
bail!("Parameter to rename not found");
|
||||
}
|
||||
|
||||
let mut edits = usages
|
||||
.into_iter()
|
||||
.map(|reference| source_edit_from_reference(sema, reference, "self"))
|
||||
let mut edits = refs
|
||||
.references()
|
||||
.iter()
|
||||
.map(|reference| source_edit_from_references(sema, reference, "self"))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
edits.push(SourceFileEdit {
|
||||
|
@ -467,7 +467,7 @@ fn rename_reference(
|
|||
|
||||
let edit = refs
|
||||
.into_iter()
|
||||
.map(|reference| source_edit_from_reference(sema, reference, new_name))
|
||||
.map(|reference| source_edit_from_references(sema, &reference, new_name))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(RangeInfo::new(range, SourceChange::from(edit)))
|
||||
|
|
|
@ -8,6 +8,7 @@ use std::{convert::TryInto, mem};
|
|||
|
||||
use base_db::{FileId, FileRange, SourceDatabaseExt};
|
||||
use hir::{DefWithBody, HasSource, Module, ModuleSource, Semantics, Visibility};
|
||||
use itertools::Itertools;
|
||||
use once_cell::unsync::Lazy;
|
||||
use rustc_hash::FxHashMap;
|
||||
use syntax::{ast, match_ast, AstNode, TextRange, TextSize};
|
||||
|
@ -19,8 +20,22 @@ use crate::{
|
|||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Reference {
|
||||
pub file_range: FileRange,
|
||||
pub struct FileReferences {
|
||||
pub file_id: FileId,
|
||||
pub references: Vec<FileReference>,
|
||||
}
|
||||
|
||||
impl FileReferences {
|
||||
pub fn file_ranges(&self) -> impl Iterator<Item = FileRange> + '_ {
|
||||
self.references
|
||||
.iter()
|
||||
.map(move |&FileReference { range, .. }| FileRange { file_id: self.file_id, range })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FileReference {
|
||||
pub range: TextRange,
|
||||
pub kind: ReferenceKind,
|
||||
pub access: Option<ReferenceAccess>,
|
||||
}
|
||||
|
@ -252,23 +267,33 @@ impl<'a> FindUsages<'a> {
|
|||
|
||||
pub fn at_least_one(self) -> bool {
|
||||
let mut found = false;
|
||||
self.search(&mut |_reference| {
|
||||
self.search(&mut |_, _| {
|
||||
found = true;
|
||||
true
|
||||
});
|
||||
found
|
||||
}
|
||||
|
||||
pub fn all(self) -> Vec<Reference> {
|
||||
let mut res = Vec::new();
|
||||
self.search(&mut |reference| {
|
||||
res.push(reference);
|
||||
/// The [`FileReferences`] returned always have unique [`FileId`]s.
|
||||
pub fn all(self) -> Vec<FileReferences> {
|
||||
let mut res = <Vec<FileReferences>>::new();
|
||||
self.search(&mut |file_id, reference| {
|
||||
match res.iter_mut().find(|it| it.file_id == file_id) {
|
||||
Some(file_refs) => file_refs.references.push(reference),
|
||||
_ => res.push(FileReferences { file_id, references: vec![reference] }),
|
||||
}
|
||||
false
|
||||
});
|
||||
assert!(res
|
||||
.iter()
|
||||
.map(|refs| refs.file_id)
|
||||
.sorted_unstable()
|
||||
.tuple_windows::<(_, _)>()
|
||||
.all(|(a, b)| a < b));
|
||||
res
|
||||
}
|
||||
|
||||
fn search(self, sink: &mut dyn FnMut(Reference) -> bool) {
|
||||
fn search(self, sink: &mut dyn FnMut(FileId, FileReference) -> bool) {
|
||||
let _p = profile::span("FindUsages:search");
|
||||
let sema = self.sema;
|
||||
|
||||
|
@ -320,16 +345,14 @@ impl<'a> FindUsages<'a> {
|
|||
fn found_lifetime(
|
||||
&self,
|
||||
lifetime: &ast::Lifetime,
|
||||
sink: &mut dyn FnMut(Reference) -> bool,
|
||||
sink: &mut dyn FnMut(FileId, FileReference) -> bool,
|
||||
) -> bool {
|
||||
match NameRefClass::classify_lifetime(self.sema, lifetime) {
|
||||
Some(NameRefClass::Definition(def)) if &def == self.def => {
|
||||
let reference = Reference {
|
||||
file_range: self.sema.original_range(lifetime.syntax()),
|
||||
kind: ReferenceKind::Lifetime,
|
||||
access: None,
|
||||
};
|
||||
sink(reference)
|
||||
let FileRange { file_id, range } = self.sema.original_range(lifetime.syntax());
|
||||
let reference =
|
||||
FileReference { range, kind: ReferenceKind::Lifetime, access: None };
|
||||
sink(file_id, reference)
|
||||
}
|
||||
_ => false, // not a usage
|
||||
}
|
||||
|
@ -338,7 +361,7 @@ impl<'a> FindUsages<'a> {
|
|||
fn found_name_ref(
|
||||
&self,
|
||||
name_ref: &ast::NameRef,
|
||||
sink: &mut dyn FnMut(Reference) -> bool,
|
||||
sink: &mut dyn FnMut(FileId, FileReference) -> bool,
|
||||
) -> bool {
|
||||
match NameRefClass::classify(self.sema, &name_ref) {
|
||||
Some(NameRefClass::Definition(def)) if &def == self.def => {
|
||||
|
@ -352,46 +375,50 @@ impl<'a> FindUsages<'a> {
|
|||
ReferenceKind::Other
|
||||
};
|
||||
|
||||
let reference = Reference {
|
||||
file_range: self.sema.original_range(name_ref.syntax()),
|
||||
kind,
|
||||
access: reference_access(&def, &name_ref),
|
||||
};
|
||||
sink(reference)
|
||||
let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
|
||||
let reference =
|
||||
FileReference { range, kind, access: reference_access(&def, &name_ref) };
|
||||
sink(file_id, reference)
|
||||
}
|
||||
Some(NameRefClass::FieldShorthand { local_ref: local, field_ref: field }) => {
|
||||
let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
|
||||
let reference = match self.def {
|
||||
Definition::Field(_) if &field == self.def => Reference {
|
||||
file_range: self.sema.original_range(name_ref.syntax()),
|
||||
Definition::Field(_) if &field == self.def => FileReference {
|
||||
range,
|
||||
kind: ReferenceKind::FieldShorthandForField,
|
||||
access: reference_access(&field, &name_ref),
|
||||
},
|
||||
Definition::Local(l) if &local == l => Reference {
|
||||
file_range: self.sema.original_range(name_ref.syntax()),
|
||||
Definition::Local(l) if &local == l => FileReference {
|
||||
range,
|
||||
kind: ReferenceKind::FieldShorthandForLocal,
|
||||
access: reference_access(&Definition::Local(local), &name_ref),
|
||||
},
|
||||
_ => return false, // not a usage
|
||||
};
|
||||
sink(reference)
|
||||
sink(file_id, reference)
|
||||
}
|
||||
_ => false, // not a usage
|
||||
}
|
||||
}
|
||||
|
||||
fn found_name(&self, name: &ast::Name, sink: &mut dyn FnMut(Reference) -> bool) -> bool {
|
||||
fn found_name(
|
||||
&self,
|
||||
name: &ast::Name,
|
||||
sink: &mut dyn FnMut(FileId, FileReference) -> bool,
|
||||
) -> bool {
|
||||
match NameClass::classify(self.sema, name) {
|
||||
Some(NameClass::PatFieldShorthand { local_def: _, field_ref }) => {
|
||||
let reference = match self.def {
|
||||
Definition::Field(_) if &field_ref == self.def => Reference {
|
||||
file_range: self.sema.original_range(name.syntax()),
|
||||
kind: ReferenceKind::FieldShorthandForField,
|
||||
// FIXME: mutable patterns should have `Write` access
|
||||
access: Some(ReferenceAccess::Read),
|
||||
},
|
||||
_ => return false, // not a usage
|
||||
if !matches!(self.def, Definition::Field(_) if &field_ref == self.def) {
|
||||
return false;
|
||||
}
|
||||
let FileRange { file_id, range } = self.sema.original_range(name.syntax());
|
||||
let reference = FileReference {
|
||||
range,
|
||||
kind: ReferenceKind::FieldShorthandForField,
|
||||
// FIXME: mutable patterns should have `Write` access
|
||||
access: Some(ReferenceAccess::Read),
|
||||
};
|
||||
sink(reference)
|
||||
sink(file_id, reference)
|
||||
}
|
||||
_ => false, // not a usage
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ use ide::{
|
|||
FileId, FilePosition, FileRange, HoverAction, HoverGotoTypeData, LineIndex, NavigationTarget,
|
||||
Query, RangeInfo, Runnable, RunnableKind, SearchScope, SourceChange, SymbolKind, TextEdit,
|
||||
};
|
||||
use ide_db::search::FileReferences;
|
||||
use itertools::Itertools;
|
||||
use lsp_server::ErrorCode;
|
||||
use lsp_types::{
|
||||
|
@ -812,14 +813,19 @@ pub(crate) fn handle_references(
|
|||
};
|
||||
|
||||
let locations = if params.context.include_declaration {
|
||||
refs.into_iter()
|
||||
.filter_map(|reference| to_proto::location(&snap, reference.file_range).ok())
|
||||
.collect()
|
||||
let mut locations = Vec::default();
|
||||
refs.into_iter().for_each(|it| {
|
||||
locations.extend(
|
||||
it.file_ranges().filter_map(|frange| to_proto::location(&snap, frange).ok()),
|
||||
)
|
||||
});
|
||||
locations
|
||||
} else {
|
||||
// Only iterate over the references if include_declaration was false
|
||||
refs.references()
|
||||
.iter()
|
||||
.filter_map(|reference| to_proto::location(&snap, reference.file_range).ok())
|
||||
.flat_map(FileReferences::file_ranges)
|
||||
.filter_map(|frange| to_proto::location(&snap, frange).ok())
|
||||
.collect()
|
||||
};
|
||||
|
||||
|
@ -1176,7 +1182,8 @@ pub(crate) fn handle_code_lens_resolve(
|
|||
.map(|r| {
|
||||
r.references()
|
||||
.iter()
|
||||
.filter_map(|it| to_proto::location(&snap, it.file_range).ok())
|
||||
.flat_map(FileReferences::file_ranges)
|
||||
.filter_map(|frange| to_proto::location(&snap, frange).ok())
|
||||
.collect_vec()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
@ -1221,12 +1228,18 @@ pub(crate) fn handle_document_highlight(
|
|||
|
||||
let res = refs
|
||||
.into_iter()
|
||||
.filter(|reference| reference.file_range.file_id == position.file_id)
|
||||
.map(|reference| DocumentHighlight {
|
||||
range: to_proto::range(&line_index, reference.file_range.range),
|
||||
kind: reference.access.map(to_proto::document_highlight_kind),
|
||||
.find(|refs| refs.file_id == position.file_id)
|
||||
.map(|file_refs| {
|
||||
file_refs
|
||||
.references
|
||||
.into_iter()
|
||||
.map(|r| DocumentHighlight {
|
||||
range: to_proto::range(&line_index, r.range),
|
||||
kind: r.access.map(to_proto::document_highlight_kind),
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
.unwrap_or_default();
|
||||
Ok(Some(res))
|
||||
}
|
||||
|
||||
|
|
|
@ -5,10 +5,10 @@ use crate::{
|
|||
resolving::{ResolvedPath, ResolvedPattern, ResolvedRule},
|
||||
Match, MatchFinder,
|
||||
};
|
||||
use ide_db::base_db::{FileId, FileRange};
|
||||
use ide_db::{
|
||||
base_db::{FileId, FileRange},
|
||||
defs::Definition,
|
||||
search::{Reference, SearchScope},
|
||||
search::{FileReferences, SearchScope},
|
||||
};
|
||||
use rustc_hash::FxHashSet;
|
||||
use syntax::{ast, AstNode, SyntaxKind, SyntaxNode};
|
||||
|
@ -20,7 +20,7 @@ use test_utils::mark;
|
|||
/// them more than once.
|
||||
#[derive(Default)]
|
||||
pub(crate) struct UsageCache {
|
||||
usages: Vec<(Definition, Vec<Reference>)>,
|
||||
usages: Vec<(Definition, Vec<FileReferences>)>,
|
||||
}
|
||||
|
||||
impl<'db> MatchFinder<'db> {
|
||||
|
@ -58,8 +58,12 @@ impl<'db> MatchFinder<'db> {
|
|||
) {
|
||||
if let Some(resolved_path) = pick_path_for_usages(pattern) {
|
||||
let definition: Definition = resolved_path.resolution.clone().into();
|
||||
for reference in self.find_usages(usage_cache, definition) {
|
||||
if let Some(node_to_match) = self.find_node_to_match(resolved_path, reference) {
|
||||
for file_range in self
|
||||
.find_usages(usage_cache, definition)
|
||||
.iter()
|
||||
.flat_map(FileReferences::file_ranges)
|
||||
{
|
||||
if let Some(node_to_match) = self.find_node_to_match(resolved_path, file_range) {
|
||||
if !is_search_permitted_ancestors(&node_to_match) {
|
||||
mark::hit!(use_declaration_with_braces);
|
||||
continue;
|
||||
|
@ -73,11 +77,11 @@ impl<'db> MatchFinder<'db> {
|
|||
fn find_node_to_match(
|
||||
&self,
|
||||
resolved_path: &ResolvedPath,
|
||||
reference: &Reference,
|
||||
file_range: FileRange,
|
||||
) -> Option<SyntaxNode> {
|
||||
let file = self.sema.parse(reference.file_range.file_id);
|
||||
let file = self.sema.parse(file_range.file_id);
|
||||
let depth = resolved_path.depth as usize;
|
||||
let offset = reference.file_range.range.start();
|
||||
let offset = file_range.range.start();
|
||||
if let Some(path) =
|
||||
self.sema.find_node_at_offset_with_descend::<ast::Path>(file.syntax(), offset)
|
||||
{
|
||||
|
@ -108,7 +112,7 @@ impl<'db> MatchFinder<'db> {
|
|||
&self,
|
||||
usage_cache: &'a mut UsageCache,
|
||||
definition: Definition,
|
||||
) -> &'a [Reference] {
|
||||
) -> &'a [FileReferences] {
|
||||
// Logically if a lookup succeeds we should just return it. Unfortunately returning it would
|
||||
// extend the lifetime of the borrow, then we wouldn't be able to do the insertion on a
|
||||
// cache miss. This is a limitation of NLL and is fixed with Polonius. For now we do two
|
||||
|
@ -250,7 +254,7 @@ fn is_search_permitted(node: &SyntaxNode) -> bool {
|
|||
}
|
||||
|
||||
impl UsageCache {
|
||||
fn find(&mut self, definition: &Definition) -> Option<&[Reference]> {
|
||||
fn find(&mut self, definition: &Definition) -> Option<&[FileReferences]> {
|
||||
// We expect a very small number of cache entries (generally 1), so a linear scan should be
|
||||
// fast enough and avoids the need to implement Hash for Definition.
|
||||
for (d, refs) in &self.usages {
|
||||
|
|
Loading…
Reference in a new issue