mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-29 13:25:09 +00:00
4729: Hover actions r=matklad a=vsrs This PR adds a `hoverActions` LSP extension and a `Go to Implementations` action as an example:  4748: Add an `ImportMap` and use it to resolve item paths in `find_path` r=matklad a=jonas-schievink Removes the "go faster" queries I added in https://github.com/rust-analyzer/rust-analyzer/pull/4501 and https://github.com/rust-analyzer/rust-analyzer/pull/4506. I've checked this PR on the rustc code base and the assists are still fast. This should fix https://github.com/rust-analyzer/rust-analyzer/issues/4515. Note that this does introduce a change in behavior: We now always refer to items defined in external crates using paths through the external crate. Previously we could also use a local path (if for example the extern crate was reexported locally), as seen in the changed test. If that is undesired I can fix that, but the test didn't say why the previous behavior would be preferable. Co-authored-by: vsrs <vit@conrlab.com> Co-authored-by: Jonas Schievink <jonasschievink@gmail.com> Co-authored-by: Jonas Schievink <jonas.schievink@ferrous-systems.com>
This commit is contained in:
commit
4029628f15
22 changed files with 899 additions and 169 deletions
|
@ -11,8 +11,8 @@ use ra_syntax::{ast, Parse, SourceFile, TextRange, TextSize};
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
cancellation::Canceled,
|
cancellation::Canceled,
|
||||||
input::{
|
input::{
|
||||||
CrateGraph, CrateId, CrateName, Dependency, Edition, Env, ExternSource, ExternSourceId,
|
CrateData, CrateGraph, CrateId, CrateName, Dependency, Edition, Env, ExternSource,
|
||||||
FileId, ProcMacroId, SourceRoot, SourceRootId,
|
ExternSourceId, FileId, ProcMacroId, SourceRoot, SourceRootId,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
pub use relative_path::{RelativePath, RelativePathBuf};
|
pub use relative_path::{RelativePath, RelativePathBuf};
|
||||||
|
|
|
@ -3,11 +3,11 @@
|
||||||
pub use hir_def::db::{
|
pub use hir_def::db::{
|
||||||
AttrsQuery, BodyQuery, BodyWithSourceMapQuery, ConstDataQuery, CrateDefMapQueryQuery,
|
AttrsQuery, BodyQuery, BodyWithSourceMapQuery, ConstDataQuery, CrateDefMapQueryQuery,
|
||||||
CrateLangItemsQuery, DefDatabase, DefDatabaseStorage, DocumentationQuery, EnumDataQuery,
|
CrateLangItemsQuery, DefDatabase, DefDatabaseStorage, DocumentationQuery, EnumDataQuery,
|
||||||
ExprScopesQuery, FunctionDataQuery, GenericParamsQuery, ImplDataQuery, InternConstQuery,
|
ExprScopesQuery, FunctionDataQuery, GenericParamsQuery, ImplDataQuery, ImportMapQuery,
|
||||||
InternDatabase, InternDatabaseStorage, InternEnumQuery, InternFunctionQuery, InternImplQuery,
|
InternConstQuery, InternDatabase, InternDatabaseStorage, InternEnumQuery, InternFunctionQuery,
|
||||||
InternStaticQuery, InternStructQuery, InternTraitQuery, InternTypeAliasQuery, InternUnionQuery,
|
InternImplQuery, InternStaticQuery, InternStructQuery, InternTraitQuery, InternTypeAliasQuery,
|
||||||
LangItemQuery, ModuleLangItemsQuery, RawItemsQuery, StaticDataQuery, StructDataQuery,
|
InternUnionQuery, LangItemQuery, ModuleLangItemsQuery, RawItemsQuery, StaticDataQuery,
|
||||||
TraitDataQuery, TypeAliasDataQuery, UnionDataQuery,
|
StructDataQuery, TraitDataQuery, TypeAliasDataQuery, UnionDataQuery,
|
||||||
};
|
};
|
||||||
pub use hir_expand::db::{
|
pub use hir_expand::db::{
|
||||||
AstDatabase, AstDatabaseStorage, AstIdMapQuery, InternEagerExpansionQuery, InternMacroQuery,
|
AstDatabase, AstDatabaseStorage, AstIdMapQuery, InternEagerExpansionQuery, InternMacroQuery,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
//! Defines database & queries for name resolution.
|
//! Defines database & queries for name resolution.
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use hir_expand::{db::AstDatabase, name::Name, HirFileId};
|
use hir_expand::{db::AstDatabase, HirFileId};
|
||||||
use ra_db::{salsa, CrateId, SourceDatabase, Upcast};
|
use ra_db::{salsa, CrateId, SourceDatabase, Upcast};
|
||||||
use ra_prof::profile;
|
use ra_prof::profile;
|
||||||
use ra_syntax::SmolStr;
|
use ra_syntax::SmolStr;
|
||||||
|
@ -12,13 +12,10 @@ use crate::{
|
||||||
body::{scope::ExprScopes, Body, BodySourceMap},
|
body::{scope::ExprScopes, Body, BodySourceMap},
|
||||||
data::{ConstData, FunctionData, ImplData, StaticData, TraitData, TypeAliasData},
|
data::{ConstData, FunctionData, ImplData, StaticData, TraitData, TypeAliasData},
|
||||||
docs::Documentation,
|
docs::Documentation,
|
||||||
find_path,
|
|
||||||
generics::GenericParams,
|
generics::GenericParams,
|
||||||
item_scope::ItemInNs,
|
import_map::ImportMap,
|
||||||
lang_item::{LangItemTarget, LangItems},
|
lang_item::{LangItemTarget, LangItems},
|
||||||
nameres::{raw::RawItems, CrateDefMap},
|
nameres::{raw::RawItems, CrateDefMap},
|
||||||
path::ModPath,
|
|
||||||
visibility::Visibility,
|
|
||||||
AttrDefId, ConstId, ConstLoc, DefWithBodyId, EnumId, EnumLoc, FunctionId, FunctionLoc,
|
AttrDefId, ConstId, ConstLoc, DefWithBodyId, EnumId, EnumLoc, FunctionId, FunctionLoc,
|
||||||
GenericDefId, ImplId, ImplLoc, ModuleId, StaticId, StaticLoc, StructId, StructLoc, TraitId,
|
GenericDefId, ImplId, ImplLoc, ModuleId, StaticId, StaticLoc, StructId, StructLoc, TraitId,
|
||||||
TraitLoc, TypeAliasId, TypeAliasLoc, UnionId, UnionLoc,
|
TraitLoc, TypeAliasId, TypeAliasLoc, UnionId, UnionLoc,
|
||||||
|
@ -113,15 +110,8 @@ pub trait DefDatabase: InternDatabase + AstDatabase + Upcast<dyn AstDatabase> {
|
||||||
#[salsa::invoke(Documentation::documentation_query)]
|
#[salsa::invoke(Documentation::documentation_query)]
|
||||||
fn documentation(&self, def: AttrDefId) -> Option<Documentation>;
|
fn documentation(&self, def: AttrDefId) -> Option<Documentation>;
|
||||||
|
|
||||||
#[salsa::invoke(find_path::importable_locations_of_query)]
|
#[salsa::invoke(ImportMap::import_map_query)]
|
||||||
fn importable_locations_of(
|
fn import_map(&self, krate: CrateId) -> Arc<ImportMap>;
|
||||||
&self,
|
|
||||||
item: ItemInNs,
|
|
||||||
krate: CrateId,
|
|
||||||
) -> Arc<[(ModuleId, Name, Visibility)]>;
|
|
||||||
|
|
||||||
#[salsa::invoke(find_path::find_path_inner_query)]
|
|
||||||
fn find_path_inner(&self, item: ItemInNs, from: ModuleId, max_len: usize) -> Option<ModPath>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn crate_def_map_wait(db: &impl DefDatabase, krate: CrateId) -> Arc<CrateDefMap> {
|
fn crate_def_map_wait(db: &impl DefDatabase, krate: CrateId) -> Arc<CrateDefMap> {
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
//! An algorithm to find a path to refer to a certain item.
|
//! An algorithm to find a path to refer to a certain item.
|
||||||
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use hir_expand::name::{known, AsName, Name};
|
use hir_expand::name::{known, AsName, Name};
|
||||||
use ra_prof::profile;
|
use ra_prof::profile;
|
||||||
|
use rustc_hash::FxHashSet;
|
||||||
use test_utils::mark;
|
use test_utils::mark;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -11,7 +10,7 @@ use crate::{
|
||||||
item_scope::ItemInNs,
|
item_scope::ItemInNs,
|
||||||
path::{ModPath, PathKind},
|
path::{ModPath, PathKind},
|
||||||
visibility::Visibility,
|
visibility::Visibility,
|
||||||
CrateId, ModuleDefId, ModuleId,
|
ModuleDefId, ModuleId,
|
||||||
};
|
};
|
||||||
|
|
||||||
// FIXME: handle local items
|
// FIXME: handle local items
|
||||||
|
@ -20,7 +19,7 @@ use crate::{
|
||||||
/// *from where* you're referring to the item, hence the `from` parameter.
|
/// *from where* you're referring to the item, hence the `from` parameter.
|
||||||
pub fn find_path(db: &dyn DefDatabase, item: ItemInNs, from: ModuleId) -> Option<ModPath> {
|
pub fn find_path(db: &dyn DefDatabase, item: ItemInNs, from: ModuleId) -> Option<ModPath> {
|
||||||
let _p = profile("find_path");
|
let _p = profile("find_path");
|
||||||
db.find_path_inner(item, from, MAX_PATH_LEN)
|
find_path_inner(db, item, from, MAX_PATH_LEN)
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAX_PATH_LEN: usize = 15;
|
const MAX_PATH_LEN: usize = 15;
|
||||||
|
@ -36,20 +35,9 @@ impl ModPath {
|
||||||
let first_segment = self.segments.first();
|
let first_segment = self.segments.first();
|
||||||
first_segment == Some(&known::alloc) || first_segment == Some(&known::core)
|
first_segment == Some(&known::alloc) || first_segment == Some(&known::core)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn len(&self) -> usize {
|
|
||||||
self.segments.len()
|
|
||||||
+ match self.kind {
|
|
||||||
PathKind::Plain => 0,
|
|
||||||
PathKind::Super(i) => i as usize,
|
|
||||||
PathKind::Crate => 1,
|
|
||||||
PathKind::Abs => 0,
|
|
||||||
PathKind::DollarCrate(_) => 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn find_path_inner_query(
|
fn find_path_inner(
|
||||||
db: &dyn DefDatabase,
|
db: &dyn DefDatabase,
|
||||||
item: ItemInNs,
|
item: ItemInNs,
|
||||||
from: ModuleId,
|
from: ModuleId,
|
||||||
|
@ -133,21 +121,25 @@ pub(crate) fn find_path_inner_query(
|
||||||
}
|
}
|
||||||
|
|
||||||
// - otherwise, look for modules containing (reexporting) it and import it from one of those
|
// - otherwise, look for modules containing (reexporting) it and import it from one of those
|
||||||
|
|
||||||
let crate_root = ModuleId { local_id: def_map.root, krate: from.krate };
|
let crate_root = ModuleId { local_id: def_map.root, krate: from.krate };
|
||||||
let crate_attrs = db.attrs(crate_root.into());
|
let crate_attrs = db.attrs(crate_root.into());
|
||||||
let prefer_no_std = crate_attrs.by_key("no_std").exists();
|
let prefer_no_std = crate_attrs.by_key("no_std").exists();
|
||||||
let importable_locations = find_importable_locations(db, item, from);
|
|
||||||
let mut best_path = None;
|
let mut best_path = None;
|
||||||
let mut best_path_len = max_len;
|
let mut best_path_len = max_len;
|
||||||
for (module_id, name) in importable_locations {
|
|
||||||
let mut path = match db.find_path_inner(
|
if item.krate(db) == Some(from.krate) {
|
||||||
|
// Item was defined in the same crate that wants to import it. It cannot be found in any
|
||||||
|
// dependency in this case.
|
||||||
|
|
||||||
|
let local_imports = find_local_import_locations(db, item, from);
|
||||||
|
for (module_id, name) in local_imports {
|
||||||
|
if let Some(mut path) = find_path_inner(
|
||||||
|
db,
|
||||||
ItemInNs::Types(ModuleDefId::ModuleId(module_id)),
|
ItemInNs::Types(ModuleDefId::ModuleId(module_id)),
|
||||||
from,
|
from,
|
||||||
best_path_len - 1,
|
best_path_len - 1,
|
||||||
) {
|
) {
|
||||||
None => continue,
|
|
||||||
Some(path) => path,
|
|
||||||
};
|
|
||||||
path.segments.push(name);
|
path.segments.push(name);
|
||||||
|
|
||||||
let new_path = if let Some(best_path) = best_path {
|
let new_path = if let Some(best_path) = best_path {
|
||||||
|
@ -158,6 +150,32 @@ pub(crate) fn find_path_inner_query(
|
||||||
best_path_len = new_path.len();
|
best_path_len = new_path.len();
|
||||||
best_path = Some(new_path);
|
best_path = Some(new_path);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Item was defined in some upstream crate. This means that it must be exported from one,
|
||||||
|
// too (unless we can't name it at all). It could *also* be (re)exported by the same crate
|
||||||
|
// that wants to import it here, but we always prefer to use the external path here.
|
||||||
|
|
||||||
|
let crate_graph = db.crate_graph();
|
||||||
|
let extern_paths = crate_graph[from.krate].dependencies.iter().filter_map(|dep| {
|
||||||
|
let import_map = db.import_map(dep.crate_id);
|
||||||
|
import_map.path_of(item).map(|modpath| {
|
||||||
|
let mut modpath = modpath.clone();
|
||||||
|
modpath.segments.insert(0, dep.as_name());
|
||||||
|
modpath
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
for path in extern_paths {
|
||||||
|
let new_path = if let Some(best_path) = best_path {
|
||||||
|
select_best_path(best_path, path, prefer_no_std)
|
||||||
|
} else {
|
||||||
|
path
|
||||||
|
};
|
||||||
|
best_path = Some(new_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
best_path
|
best_path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,48 +203,55 @@ fn select_best_path(old_path: ModPath, new_path: ModPath, prefer_no_std: bool) -
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_importable_locations(
|
/// Finds locations in `from.krate` from which `item` can be imported by `from`.
|
||||||
|
fn find_local_import_locations(
|
||||||
db: &dyn DefDatabase,
|
db: &dyn DefDatabase,
|
||||||
item: ItemInNs,
|
item: ItemInNs,
|
||||||
from: ModuleId,
|
from: ModuleId,
|
||||||
) -> Vec<(ModuleId, Name)> {
|
) -> Vec<(ModuleId, Name)> {
|
||||||
let crate_graph = db.crate_graph();
|
let _p = profile("find_local_import_locations");
|
||||||
let mut result = Vec::new();
|
|
||||||
// We only look in the crate from which we are importing, and the direct
|
// `from` can import anything below `from` with visibility of at least `from`, and anything
|
||||||
// dependencies. We cannot refer to names from transitive dependencies
|
// above `from` with any visibility. That means we do not need to descend into private siblings
|
||||||
// directly (only through reexports in direct dependencies).
|
// of `from` (and similar).
|
||||||
for krate in Some(from.krate)
|
|
||||||
.into_iter()
|
let def_map = db.crate_def_map(from.krate);
|
||||||
.chain(crate_graph[from.krate].dependencies.iter().map(|dep| dep.crate_id))
|
|
||||||
{
|
// Compute the initial worklist. We start with all direct child modules of `from` as well as all
|
||||||
result.extend(
|
// of its (recursive) parent modules.
|
||||||
db.importable_locations_of(item, krate)
|
let data = &def_map.modules[from.local_id];
|
||||||
.iter()
|
let mut worklist = data
|
||||||
.filter(|(_, _, vis)| vis.is_visible_from(db, from))
|
.children
|
||||||
.map(|(m, n, _)| (*m, n.clone())),
|
.values()
|
||||||
);
|
.map(|child| ModuleId { krate: from.krate, local_id: *child })
|
||||||
}
|
.collect::<Vec<_>>();
|
||||||
result
|
let mut parent = data.parent;
|
||||||
|
while let Some(p) = parent {
|
||||||
|
worklist.push(ModuleId { krate: from.krate, local_id: p });
|
||||||
|
parent = def_map.modules[p].parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Collects all locations from which we might import the item in a particular
|
let mut seen: FxHashSet<_> = FxHashSet::default();
|
||||||
/// crate. These include the original definition of the item, and any
|
|
||||||
/// non-private `use`s.
|
let mut locations = Vec::new();
|
||||||
///
|
while let Some(module) = worklist.pop() {
|
||||||
/// Note that the crate doesn't need to be the one in which the item is defined;
|
if !seen.insert(module) {
|
||||||
/// it might be re-exported in other crates.
|
continue; // already processed this module
|
||||||
pub(crate) fn importable_locations_of_query(
|
}
|
||||||
db: &dyn DefDatabase,
|
|
||||||
item: ItemInNs,
|
let ext_def_map;
|
||||||
krate: CrateId,
|
let data = if module.krate == from.krate {
|
||||||
) -> Arc<[(ModuleId, Name, Visibility)]> {
|
&def_map[module.local_id]
|
||||||
let _p = profile("importable_locations_of_query");
|
} else {
|
||||||
let def_map = db.crate_def_map(krate);
|
// The crate might reexport a module defined in another crate.
|
||||||
let mut result = Vec::new();
|
ext_def_map = db.crate_def_map(module.krate);
|
||||||
for (local_id, data) in def_map.modules.iter() {
|
&ext_def_map[module.local_id]
|
||||||
|
};
|
||||||
|
|
||||||
if let Some((name, vis)) = data.scope.name_of(item) {
|
if let Some((name, vis)) = data.scope.name_of(item) {
|
||||||
|
if vis.is_visible_from(db, from) {
|
||||||
let is_private = if let Visibility::Module(private_to) = vis {
|
let is_private = if let Visibility::Module(private_to) = vis {
|
||||||
private_to.local_id == local_id
|
private_to.local_id == module.local_id
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
|
@ -235,19 +260,29 @@ pub(crate) fn importable_locations_of_query(
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
if is_private && !is_original_def {
|
|
||||||
// Ignore private imports. these could be used if we are
|
// Ignore private imports. these could be used if we are
|
||||||
// in a submodule of this module, but that's usually not
|
// in a submodule of this module, but that's usually not
|
||||||
// what the user wants; and if this module can import
|
// what the user wants; and if this module can import
|
||||||
// the item and we're a submodule of it, so can we.
|
// the item and we're a submodule of it, so can we.
|
||||||
// Also this keeps the cached data smaller.
|
// Also this keeps the cached data smaller.
|
||||||
continue;
|
if !is_private || is_original_def {
|
||||||
|
locations.push((module, name.clone()));
|
||||||
}
|
}
|
||||||
result.push((ModuleId { krate, local_id }, name.clone(), vis));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Arc::from(result)
|
// Descend into all modules visible from `from`.
|
||||||
|
for (_, per_ns) in data.scope.entries() {
|
||||||
|
if let Some((ModuleDefId::ModuleId(module), vis)) = per_ns.take_types_vis() {
|
||||||
|
if vis.is_visible_from(db, from) {
|
||||||
|
worklist.push(module);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
locations
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -385,6 +420,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn different_crate_renamed() {
|
fn different_crate_renamed() {
|
||||||
|
// Even if a local path exists, if the item is defined externally, prefer an external path.
|
||||||
let code = r#"
|
let code = r#"
|
||||||
//- /main.rs crate:main deps:std
|
//- /main.rs crate:main deps:std
|
||||||
extern crate std as std_renamed;
|
extern crate std as std_renamed;
|
||||||
|
@ -392,7 +428,7 @@ mod tests {
|
||||||
//- /std.rs crate:std
|
//- /std.rs crate:std
|
||||||
pub struct S;
|
pub struct S;
|
||||||
"#;
|
"#;
|
||||||
check_found_path(code, "std_renamed::S");
|
check_found_path(code, "std::S");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
331
crates/ra_hir_def/src/import_map.rs
Normal file
331
crates/ra_hir_def/src/import_map.rs
Normal file
|
@ -0,0 +1,331 @@
|
||||||
|
//! A map of all publicly exported items in a crate.
|
||||||
|
|
||||||
|
use std::{collections::hash_map::Entry, fmt, sync::Arc};
|
||||||
|
|
||||||
|
use ra_db::CrateId;
|
||||||
|
use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
db::DefDatabase,
|
||||||
|
item_scope::ItemInNs,
|
||||||
|
path::{ModPath, PathKind},
|
||||||
|
visibility::Visibility,
|
||||||
|
ModuleDefId, ModuleId,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A map from publicly exported items to the path needed to import/name them from a downstream
|
||||||
|
/// crate.
|
||||||
|
///
|
||||||
|
/// Reexports of items are taken into account, ie. if something is exported under multiple
|
||||||
|
/// names, the one with the shortest import path will be used.
|
||||||
|
///
|
||||||
|
/// Note that all paths are relative to the containing crate's root, so the crate name still needs
|
||||||
|
/// to be prepended to the `ModPath` before the path is valid.
|
||||||
|
#[derive(Eq, PartialEq)]
|
||||||
|
pub struct ImportMap {
|
||||||
|
map: FxHashMap<ItemInNs, ModPath>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ImportMap {
|
||||||
|
pub fn import_map_query(db: &dyn DefDatabase, krate: CrateId) -> Arc<Self> {
|
||||||
|
let _p = ra_prof::profile("import_map_query");
|
||||||
|
let def_map = db.crate_def_map(krate);
|
||||||
|
let mut import_map = FxHashMap::with_capacity_and_hasher(64, Default::default());
|
||||||
|
|
||||||
|
// We look only into modules that are public(ly reexported), starting with the crate root.
|
||||||
|
let empty = ModPath { kind: PathKind::Plain, segments: vec![] };
|
||||||
|
let root = ModuleId { krate, local_id: def_map.root };
|
||||||
|
let mut worklist = vec![(root, empty)];
|
||||||
|
while let Some((module, mod_path)) = worklist.pop() {
|
||||||
|
let ext_def_map;
|
||||||
|
let mod_data = if module.krate == krate {
|
||||||
|
&def_map[module.local_id]
|
||||||
|
} else {
|
||||||
|
// The crate might reexport a module defined in another crate.
|
||||||
|
ext_def_map = db.crate_def_map(module.krate);
|
||||||
|
&ext_def_map[module.local_id]
|
||||||
|
};
|
||||||
|
|
||||||
|
let visible_items = mod_data.scope.entries().filter_map(|(name, per_ns)| {
|
||||||
|
let per_ns = per_ns.filter_visibility(|vis| vis == Visibility::Public);
|
||||||
|
if per_ns.is_none() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some((name, per_ns))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (name, per_ns) in visible_items {
|
||||||
|
let mk_path = || {
|
||||||
|
let mut path = mod_path.clone();
|
||||||
|
path.segments.push(name.clone());
|
||||||
|
path
|
||||||
|
};
|
||||||
|
|
||||||
|
for item in per_ns.iter_items() {
|
||||||
|
let path = mk_path();
|
||||||
|
match import_map.entry(item) {
|
||||||
|
Entry::Vacant(entry) => {
|
||||||
|
entry.insert(path);
|
||||||
|
}
|
||||||
|
Entry::Occupied(mut entry) => {
|
||||||
|
// If the new path is shorter, prefer that one.
|
||||||
|
if path.len() < entry.get().len() {
|
||||||
|
*entry.get_mut() = path;
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we've just added a path to a module, descend into it. We might traverse
|
||||||
|
// modules multiple times, but only if the new path to it is shorter than the
|
||||||
|
// first (else we `continue` above).
|
||||||
|
if let Some(ModuleDefId::ModuleId(mod_id)) = item.as_module_def_id() {
|
||||||
|
worklist.push((mod_id, mk_path()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Arc::new(Self { map: import_map })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the `ModPath` needed to import/mention `item`, relative to this crate's root.
|
||||||
|
pub fn path_of(&self, item: ItemInNs) -> Option<&ModPath> {
|
||||||
|
self.map.get(&item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for ImportMap {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let mut importable_paths: Vec<_> = self
|
||||||
|
.map
|
||||||
|
.iter()
|
||||||
|
.map(|(item, modpath)| {
|
||||||
|
let ns = match item {
|
||||||
|
ItemInNs::Types(_) => "t",
|
||||||
|
ItemInNs::Values(_) => "v",
|
||||||
|
ItemInNs::Macros(_) => "m",
|
||||||
|
};
|
||||||
|
format!("- {} ({})", modpath, ns)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
importable_paths.sort();
|
||||||
|
f.write_str(&importable_paths.join("\n"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::test_db::TestDB;
|
||||||
|
use insta::assert_snapshot;
|
||||||
|
use ra_db::fixture::WithFixture;
|
||||||
|
use ra_db::SourceDatabase;
|
||||||
|
|
||||||
|
fn import_map(ra_fixture: &str) -> String {
|
||||||
|
let db = TestDB::with_files(ra_fixture);
|
||||||
|
let crate_graph = db.crate_graph();
|
||||||
|
|
||||||
|
let import_maps: Vec<_> = crate_graph
|
||||||
|
.iter()
|
||||||
|
.filter_map(|krate| {
|
||||||
|
let cdata = &crate_graph[krate];
|
||||||
|
let name = cdata.display_name.as_ref()?;
|
||||||
|
|
||||||
|
let map = db.import_map(krate);
|
||||||
|
|
||||||
|
Some(format!("{}:\n{:?}", name, map))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
import_maps.join("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn smoke() {
|
||||||
|
let map = import_map(
|
||||||
|
r"
|
||||||
|
//- /main.rs crate:main deps:lib
|
||||||
|
|
||||||
|
mod private {
|
||||||
|
pub use lib::Pub;
|
||||||
|
pub struct InPrivateModule;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod publ1 {
|
||||||
|
use lib::Pub;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod real_pub {
|
||||||
|
pub use lib::Pub;
|
||||||
|
}
|
||||||
|
pub mod real_pu2 { // same path length as above
|
||||||
|
pub use lib::Pub;
|
||||||
|
}
|
||||||
|
|
||||||
|
//- /lib.rs crate:lib
|
||||||
|
pub struct Pub {}
|
||||||
|
pub struct Pub2; // t + v
|
||||||
|
struct Priv;
|
||||||
|
",
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_snapshot!(map, @r###"
|
||||||
|
main:
|
||||||
|
- publ1 (t)
|
||||||
|
- real_pu2 (t)
|
||||||
|
- real_pub (t)
|
||||||
|
- real_pub::Pub (t)
|
||||||
|
lib:
|
||||||
|
- Pub (t)
|
||||||
|
- Pub2 (t)
|
||||||
|
- Pub2 (v)
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn prefers_shortest_path() {
|
||||||
|
let map = import_map(
|
||||||
|
r"
|
||||||
|
//- /main.rs crate:main
|
||||||
|
|
||||||
|
pub mod sub {
|
||||||
|
pub mod subsub {
|
||||||
|
pub struct Def {}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub use super::sub::subsub::Def;
|
||||||
|
}
|
||||||
|
",
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_snapshot!(map, @r###"
|
||||||
|
main:
|
||||||
|
- sub (t)
|
||||||
|
- sub::Def (t)
|
||||||
|
- sub::subsub (t)
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn type_reexport_cross_crate() {
|
||||||
|
// Reexports need to be visible from a crate, even if the original crate exports the item
|
||||||
|
// at a shorter path.
|
||||||
|
let map = import_map(
|
||||||
|
r"
|
||||||
|
//- /main.rs crate:main deps:lib
|
||||||
|
pub mod m {
|
||||||
|
pub use lib::S;
|
||||||
|
}
|
||||||
|
//- /lib.rs crate:lib
|
||||||
|
pub struct S;
|
||||||
|
",
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_snapshot!(map, @r###"
|
||||||
|
main:
|
||||||
|
- m (t)
|
||||||
|
- m::S (t)
|
||||||
|
- m::S (v)
|
||||||
|
lib:
|
||||||
|
- S (t)
|
||||||
|
- S (v)
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn macro_reexport() {
|
||||||
|
let map = import_map(
|
||||||
|
r"
|
||||||
|
//- /main.rs crate:main deps:lib
|
||||||
|
pub mod m {
|
||||||
|
pub use lib::pub_macro;
|
||||||
|
}
|
||||||
|
//- /lib.rs crate:lib
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! pub_macro {
|
||||||
|
() => {};
|
||||||
|
}
|
||||||
|
",
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_snapshot!(map, @r###"
|
||||||
|
main:
|
||||||
|
- m (t)
|
||||||
|
- m::pub_macro (m)
|
||||||
|
lib:
|
||||||
|
- pub_macro (m)
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn module_reexport() {
|
||||||
|
// Reexporting modules from a dependency adds all contents to the import map.
|
||||||
|
let map = import_map(
|
||||||
|
r"
|
||||||
|
//- /main.rs crate:main deps:lib
|
||||||
|
pub use lib::module as reexported_module;
|
||||||
|
//- /lib.rs crate:lib
|
||||||
|
pub mod module {
|
||||||
|
pub struct S;
|
||||||
|
}
|
||||||
|
",
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_snapshot!(map, @r###"
|
||||||
|
main:
|
||||||
|
- reexported_module (t)
|
||||||
|
- reexported_module::S (t)
|
||||||
|
- reexported_module::S (v)
|
||||||
|
lib:
|
||||||
|
- module (t)
|
||||||
|
- module::S (t)
|
||||||
|
- module::S (v)
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cyclic_module_reexport() {
|
||||||
|
// A cyclic reexport does not hang.
|
||||||
|
let map = import_map(
|
||||||
|
r"
|
||||||
|
//- /lib.rs crate:lib
|
||||||
|
pub mod module {
|
||||||
|
pub struct S;
|
||||||
|
pub use super::sub::*;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod sub {
|
||||||
|
pub use super::module;
|
||||||
|
}
|
||||||
|
",
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_snapshot!(map, @r###"
|
||||||
|
lib:
|
||||||
|
- module (t)
|
||||||
|
- module::S (t)
|
||||||
|
- module::S (v)
|
||||||
|
- sub (t)
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn private_macro() {
|
||||||
|
let map = import_map(
|
||||||
|
r"
|
||||||
|
//- /lib.rs crate:lib
|
||||||
|
macro_rules! private_macro {
|
||||||
|
() => {};
|
||||||
|
}
|
||||||
|
",
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_snapshot!(map, @r###"
|
||||||
|
lib:
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,11 +3,12 @@
|
||||||
|
|
||||||
use hir_expand::name::Name;
|
use hir_expand::name::Name;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
use ra_db::CrateId;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
per_ns::PerNs, visibility::Visibility, AdtId, BuiltinType, ImplId, MacroDefId, ModuleDefId,
|
db::DefDatabase, per_ns::PerNs, visibility::Visibility, AdtId, BuiltinType, HasModule, ImplId,
|
||||||
TraitId,
|
Lookup, MacroDefId, ModuleDefId, TraitId,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Default, PartialEq, Eq)]
|
#[derive(Debug, Default, PartialEq, Eq)]
|
||||||
|
@ -203,4 +204,22 @@ impl ItemInNs {
|
||||||
ItemInNs::Macros(_) => None,
|
ItemInNs::Macros(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the crate defining this item (or `None` if `self` is built-in).
|
||||||
|
pub fn krate(&self, db: &dyn DefDatabase) -> Option<CrateId> {
|
||||||
|
Some(match self {
|
||||||
|
ItemInNs::Types(did) | ItemInNs::Values(did) => match did {
|
||||||
|
ModuleDefId::ModuleId(id) => id.krate,
|
||||||
|
ModuleDefId::FunctionId(id) => id.lookup(db).module(db).krate,
|
||||||
|
ModuleDefId::AdtId(id) => id.module(db).krate,
|
||||||
|
ModuleDefId::EnumVariantId(id) => id.parent.lookup(db).container.module(db).krate,
|
||||||
|
ModuleDefId::ConstId(id) => id.lookup(db).container.module(db).krate,
|
||||||
|
ModuleDefId::StaticId(id) => id.lookup(db).container.module(db).krate,
|
||||||
|
ModuleDefId::TraitId(id) => id.lookup(db).container.module(db).krate,
|
||||||
|
ModuleDefId::TypeAliasId(id) => id.lookup(db).module(db).krate,
|
||||||
|
ModuleDefId::BuiltinType(_) => return None,
|
||||||
|
},
|
||||||
|
ItemInNs::Macros(id) => return id.krate,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,6 +43,7 @@ pub mod child_by_source;
|
||||||
|
|
||||||
pub mod visibility;
|
pub mod visibility;
|
||||||
pub mod find_path;
|
pub mod find_path;
|
||||||
|
pub mod import_map;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test_db;
|
mod test_db;
|
||||||
|
|
|
@ -76,6 +76,19 @@ impl ModPath {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the number of segments in the path (counting special segments like `$crate` and
|
||||||
|
/// `super`).
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.segments.len()
|
||||||
|
+ match self.kind {
|
||||||
|
PathKind::Plain => 0,
|
||||||
|
PathKind::Super(i) => i as usize,
|
||||||
|
PathKind::Crate => 1,
|
||||||
|
PathKind::Abs => 0,
|
||||||
|
PathKind::DollarCrate(_) => 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_ident(&self) -> bool {
|
pub fn is_ident(&self) -> bool {
|
||||||
self.kind == PathKind::Plain && self.segments.len() == 1
|
self.kind == PathKind::Plain && self.segments.len() == 1
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
use hir_expand::MacroDefId;
|
use hir_expand::MacroDefId;
|
||||||
|
|
||||||
use crate::{visibility::Visibility, ModuleDefId};
|
use crate::{item_scope::ItemInNs, visibility::Visibility, ModuleDefId};
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
pub struct PerNs {
|
pub struct PerNs {
|
||||||
|
@ -84,4 +84,12 @@ impl PerNs {
|
||||||
macros: self.macros.or(other.macros),
|
macros: self.macros.or(other.macros),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn iter_items(self) -> impl Iterator<Item = ItemInNs> {
|
||||||
|
self.types
|
||||||
|
.map(|it| ItemInNs::Types(it.0))
|
||||||
|
.into_iter()
|
||||||
|
.chain(self.values.map(|it| ItemInNs::Values(it.0)).into_iter())
|
||||||
|
.chain(self.macros.map(|it| ItemInNs::Macros(it.0)).into_iter())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,14 +13,43 @@ use ra_ide_db::{
|
||||||
use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset};
|
use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel},
|
display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel, ToNav},
|
||||||
FilePosition, RangeInfo,
|
FilePosition, NavigationTarget, RangeInfo,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct HoverConfig {
|
||||||
|
pub implementations: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for HoverConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self { implementations: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HoverConfig {
|
||||||
|
pub const NO_ACTIONS: Self = Self { implementations: false };
|
||||||
|
|
||||||
|
pub fn any(&self) -> bool {
|
||||||
|
self.implementations
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn none(&self) -> bool {
|
||||||
|
!self.any()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum HoverAction {
|
||||||
|
Implementaion(FilePosition),
|
||||||
|
}
|
||||||
|
|
||||||
/// Contains the results when hovering over an item
|
/// Contains the results when hovering over an item
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct HoverResult {
|
pub struct HoverResult {
|
||||||
results: Vec<String>,
|
results: Vec<String>,
|
||||||
|
actions: Vec<HoverAction>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HoverResult {
|
impl HoverResult {
|
||||||
|
@ -48,10 +77,20 @@ impl HoverResult {
|
||||||
&self.results
|
&self.results
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn actions(&self) -> &[HoverAction] {
|
||||||
|
&self.actions
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_action(&mut self, action: HoverAction) {
|
||||||
|
self.actions.push(action);
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the results converted into markup
|
/// Returns the results converted into markup
|
||||||
/// for displaying in a UI
|
/// for displaying in a UI
|
||||||
|
///
|
||||||
|
/// Does not process actions!
|
||||||
pub fn to_markup(&self) -> String {
|
pub fn to_markup(&self) -> String {
|
||||||
self.results.join("\n\n---\n")
|
self.results.join("\n\n___\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,6 +121,10 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn
|
||||||
res.extend(hover_text_from_name_kind(db, name_kind));
|
res.extend(hover_text_from_name_kind(db, name_kind));
|
||||||
|
|
||||||
if !res.is_empty() {
|
if !res.is_empty() {
|
||||||
|
if let Some(action) = show_implementations_action(db, name_kind) {
|
||||||
|
res.push_action(action);
|
||||||
|
}
|
||||||
|
|
||||||
return Some(RangeInfo::new(range, res));
|
return Some(RangeInfo::new(range, res));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -112,6 +155,26 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn
|
||||||
Some(RangeInfo::new(range, res))
|
Some(RangeInfo::new(range, res))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn show_implementations_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
|
||||||
|
fn to_action(nav_target: NavigationTarget) -> HoverAction {
|
||||||
|
HoverAction::Implementaion(FilePosition {
|
||||||
|
file_id: nav_target.file_id(),
|
||||||
|
offset: nav_target.range().start(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
match def {
|
||||||
|
Definition::ModuleDef(it) => match it {
|
||||||
|
ModuleDef::Adt(Adt::Struct(it)) => Some(to_action(it.to_nav(db))),
|
||||||
|
ModuleDef::Adt(Adt::Union(it)) => Some(to_action(it.to_nav(db))),
|
||||||
|
ModuleDef::Adt(Adt::Enum(it)) => Some(to_action(it.to_nav(db))),
|
||||||
|
ModuleDef::Trait(it) => Some(to_action(it.to_nav(db))),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn hover_text(
|
fn hover_text(
|
||||||
docs: Option<String>,
|
docs: Option<String>,
|
||||||
desc: Option<String>,
|
desc: Option<String>,
|
||||||
|
@ -228,6 +291,8 @@ fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
use ra_db::FileLoader;
|
use ra_db::FileLoader;
|
||||||
use ra_syntax::TextRange;
|
use ra_syntax::TextRange;
|
||||||
|
|
||||||
|
@ -241,7 +306,14 @@ mod tests {
|
||||||
s.map(trim_markup)
|
s.map(trim_markup)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_hover_result(fixture: &str, expected: &[&str]) -> String {
|
fn assert_impl_action(action: &HoverAction, position: u32) {
|
||||||
|
let offset = match action {
|
||||||
|
HoverAction::Implementaion(pos) => pos.offset,
|
||||||
|
};
|
||||||
|
assert_eq!(offset, position.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_hover_result(fixture: &str, expected: &[&str]) -> (String, Vec<HoverAction>) {
|
||||||
let (analysis, position) = analysis_and_position(fixture);
|
let (analysis, position) = analysis_and_position(fixture);
|
||||||
let hover = analysis.hover(position).unwrap().unwrap();
|
let hover = analysis.hover(position).unwrap().unwrap();
|
||||||
let mut results = Vec::from(hover.info.results());
|
let mut results = Vec::from(hover.info.results());
|
||||||
|
@ -256,7 +328,7 @@ mod tests {
|
||||||
assert_eq!(hover.info.len(), expected.len());
|
assert_eq!(hover.info.len(), expected.len());
|
||||||
|
|
||||||
let content = analysis.db.file_text(position.file_id);
|
let content = analysis.db.file_text(position.file_id);
|
||||||
content[hover.range].to_string()
|
(content[hover.range].to_string(), hover.info.actions().to_vec())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_hover_no_result(fixture: &str) {
|
fn check_hover_no_result(fixture: &str) {
|
||||||
|
@ -746,7 +818,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_hover_through_macro() {
|
fn test_hover_through_macro() {
|
||||||
let hover_on = check_hover_result(
|
let (hover_on, _) = check_hover_result(
|
||||||
"
|
"
|
||||||
//- /lib.rs
|
//- /lib.rs
|
||||||
macro_rules! id {
|
macro_rules! id {
|
||||||
|
@ -767,7 +839,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_hover_through_expr_in_macro() {
|
fn test_hover_through_expr_in_macro() {
|
||||||
let hover_on = check_hover_result(
|
let (hover_on, _) = check_hover_result(
|
||||||
"
|
"
|
||||||
//- /lib.rs
|
//- /lib.rs
|
||||||
macro_rules! id {
|
macro_rules! id {
|
||||||
|
@ -785,7 +857,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_hover_through_expr_in_macro_recursive() {
|
fn test_hover_through_expr_in_macro_recursive() {
|
||||||
let hover_on = check_hover_result(
|
let (hover_on, _) = check_hover_result(
|
||||||
"
|
"
|
||||||
//- /lib.rs
|
//- /lib.rs
|
||||||
macro_rules! id_deep {
|
macro_rules! id_deep {
|
||||||
|
@ -806,7 +878,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_hover_through_func_in_macro_recursive() {
|
fn test_hover_through_func_in_macro_recursive() {
|
||||||
let hover_on = check_hover_result(
|
let (hover_on, _) = check_hover_result(
|
||||||
"
|
"
|
||||||
//- /lib.rs
|
//- /lib.rs
|
||||||
macro_rules! id_deep {
|
macro_rules! id_deep {
|
||||||
|
@ -830,7 +902,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_hover_through_literal_string_in_macro() {
|
fn test_hover_through_literal_string_in_macro() {
|
||||||
let hover_on = check_hover_result(
|
let (hover_on, _) = check_hover_result(
|
||||||
r#"
|
r#"
|
||||||
//- /lib.rs
|
//- /lib.rs
|
||||||
macro_rules! arr {
|
macro_rules! arr {
|
||||||
|
@ -849,7 +921,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_hover_through_assert_macro() {
|
fn test_hover_through_assert_macro() {
|
||||||
let hover_on = check_hover_result(
|
let (hover_on, _) = check_hover_result(
|
||||||
r#"
|
r#"
|
||||||
//- /lib.rs
|
//- /lib.rs
|
||||||
#[rustc_builtin_macro]
|
#[rustc_builtin_macro]
|
||||||
|
@ -925,13 +997,14 @@ fn func(foo: i32) { if true { <|>foo; }; }
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_hover_trait_show_qualifiers() {
|
fn test_hover_trait_show_qualifiers() {
|
||||||
check_hover_result(
|
let (_, actions) = check_hover_result(
|
||||||
"
|
"
|
||||||
//- /lib.rs
|
//- /lib.rs
|
||||||
unsafe trait foo<|>() {}
|
unsafe trait foo<|>() {}
|
||||||
",
|
",
|
||||||
&["unsafe trait foo"],
|
&["unsafe trait foo"],
|
||||||
);
|
);
|
||||||
|
assert_impl_action(&actions[0], 13);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1052,4 +1125,55 @@ fn func(foo: i32) { if true { <|>foo; }; }
|
||||||
&["Bar\n```\n\n```rust\nfn foo(&self)\n```\n___\n\nDo the foo"],
|
&["Bar\n```\n\n```rust\nfn foo(&self)\n```\n___\n\nDo the foo"],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hover_trait_has_impl_action() {
|
||||||
|
let (_, actions) = check_hover_result(
|
||||||
|
"
|
||||||
|
//- /lib.rs
|
||||||
|
trait foo<|>() {}
|
||||||
|
",
|
||||||
|
&["trait foo"],
|
||||||
|
);
|
||||||
|
assert_impl_action(&actions[0], 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hover_struct_has_impl_action() {
|
||||||
|
let (_, actions) = check_hover_result(
|
||||||
|
"
|
||||||
|
//- /lib.rs
|
||||||
|
struct foo<|>() {}
|
||||||
|
",
|
||||||
|
&["struct foo"],
|
||||||
|
);
|
||||||
|
assert_impl_action(&actions[0], 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hover_union_has_impl_action() {
|
||||||
|
let (_, actions) = check_hover_result(
|
||||||
|
"
|
||||||
|
//- /lib.rs
|
||||||
|
union foo<|>() {}
|
||||||
|
",
|
||||||
|
&["union foo"],
|
||||||
|
);
|
||||||
|
assert_impl_action(&actions[0], 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hover_enum_has_impl_action() {
|
||||||
|
let (_, actions) = check_hover_result(
|
||||||
|
"
|
||||||
|
//- /lib.rs
|
||||||
|
enum foo<|>() {
|
||||||
|
A,
|
||||||
|
B
|
||||||
|
}
|
||||||
|
",
|
||||||
|
&["enum foo"],
|
||||||
|
);
|
||||||
|
assert_impl_action(&actions[0], 5);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,7 +66,7 @@ pub use crate::{
|
||||||
display::{file_structure, FunctionSignature, NavigationTarget, StructureNode},
|
display::{file_structure, FunctionSignature, NavigationTarget, StructureNode},
|
||||||
expand_macro::ExpandedMacro,
|
expand_macro::ExpandedMacro,
|
||||||
folding_ranges::{Fold, FoldKind},
|
folding_ranges::{Fold, FoldKind},
|
||||||
hover::HoverResult,
|
hover::{HoverAction, HoverConfig, HoverResult},
|
||||||
inlay_hints::{InlayHint, InlayHintsConfig, InlayKind},
|
inlay_hints::{InlayHint, InlayHintsConfig, InlayKind},
|
||||||
references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult},
|
references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult},
|
||||||
runnables::{Runnable, RunnableKind, TestId},
|
runnables::{Runnable, RunnableKind, TestId},
|
||||||
|
|
|
@ -334,6 +334,7 @@ impl RootDatabase {
|
||||||
hir::db::CrateLangItemsQuery
|
hir::db::CrateLangItemsQuery
|
||||||
hir::db::LangItemQuery
|
hir::db::LangItemQuery
|
||||||
hir::db::DocumentationQuery
|
hir::db::DocumentationQuery
|
||||||
|
hir::db::ImportMapQuery
|
||||||
|
|
||||||
// InternDatabase
|
// InternDatabase
|
||||||
hir::db::InternFunctionQuery
|
hir::db::InternFunctionQuery
|
||||||
|
|
|
@ -18,7 +18,7 @@ use ra_syntax::{
|
||||||
use crate::RootDatabase;
|
use crate::RootDatabase;
|
||||||
|
|
||||||
// FIXME: a more precise name would probably be `Symbol`?
|
// FIXME: a more precise name would probably be `Symbol`?
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
pub enum Definition {
|
pub enum Definition {
|
||||||
Macro(MacroDef),
|
Macro(MacroDef),
|
||||||
Field(Field),
|
Field(Field),
|
||||||
|
|
|
@ -11,7 +11,7 @@ use std::{ffi::OsString, path::PathBuf};
|
||||||
|
|
||||||
use lsp_types::ClientCapabilities;
|
use lsp_types::ClientCapabilities;
|
||||||
use ra_flycheck::FlycheckConfig;
|
use ra_flycheck::FlycheckConfig;
|
||||||
use ra_ide::{AssistConfig, CompletionConfig, InlayHintsConfig};
|
use ra_ide::{AssistConfig, CompletionConfig, HoverConfig, InlayHintsConfig};
|
||||||
use ra_project_model::{CargoConfig, JsonProject, ProjectManifest};
|
use ra_project_model::{CargoConfig, JsonProject, ProjectManifest};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
@ -34,6 +34,7 @@ pub struct Config {
|
||||||
pub assist: AssistConfig,
|
pub assist: AssistConfig,
|
||||||
pub call_info_full: bool,
|
pub call_info_full: bool,
|
||||||
pub lens: LensConfig,
|
pub lens: LensConfig,
|
||||||
|
pub hover: HoverConfig,
|
||||||
|
|
||||||
pub with_sysroot: bool,
|
pub with_sysroot: bool,
|
||||||
pub linked_projects: Vec<LinkedProject>,
|
pub linked_projects: Vec<LinkedProject>,
|
||||||
|
@ -124,6 +125,7 @@ pub struct ClientCapsConfig {
|
||||||
pub work_done_progress: bool,
|
pub work_done_progress: bool,
|
||||||
pub code_action_group: bool,
|
pub code_action_group: bool,
|
||||||
pub resolve_code_action: bool,
|
pub resolve_code_action: bool,
|
||||||
|
pub hover_actions: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
|
@ -162,6 +164,7 @@ impl Default for Config {
|
||||||
assist: AssistConfig::default(),
|
assist: AssistConfig::default(),
|
||||||
call_info_full: true,
|
call_info_full: true,
|
||||||
lens: LensConfig::default(),
|
lens: LensConfig::default(),
|
||||||
|
hover: HoverConfig::default(),
|
||||||
linked_projects: Vec::new(),
|
linked_projects: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -278,6 +281,14 @@ impl Config {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut use_hover_actions = false;
|
||||||
|
set(value, "/hoverActions/enable", &mut use_hover_actions);
|
||||||
|
if use_hover_actions {
|
||||||
|
set(value, "/hoverActions/implementations", &mut self.hover.implementations);
|
||||||
|
} else {
|
||||||
|
self.hover = HoverConfig::NO_ACTIONS;
|
||||||
|
}
|
||||||
|
|
||||||
log::info!("Config::update() = {:#?}", self);
|
log::info!("Config::update() = {:#?}", self);
|
||||||
|
|
||||||
fn get<'a, T: Deserialize<'a>>(value: &'a serde_json::Value, pointer: &str) -> Option<T> {
|
fn get<'a, T: Deserialize<'a>>(value: &'a serde_json::Value, pointer: &str) -> Option<T> {
|
||||||
|
@ -331,17 +342,15 @@ impl Config {
|
||||||
|
|
||||||
self.assist.allow_snippets(false);
|
self.assist.allow_snippets(false);
|
||||||
if let Some(experimental) = &caps.experimental {
|
if let Some(experimental) = &caps.experimental {
|
||||||
let snippet_text_edit =
|
let get_bool =
|
||||||
experimental.get("snippetTextEdit").and_then(|it| it.as_bool()) == Some(true);
|
|index: &str| experimental.get(index).and_then(|it| it.as_bool()) == Some(true);
|
||||||
|
|
||||||
|
let snippet_text_edit = get_bool("snippetTextEdit");
|
||||||
self.assist.allow_snippets(snippet_text_edit);
|
self.assist.allow_snippets(snippet_text_edit);
|
||||||
|
|
||||||
let code_action_group =
|
self.client_caps.code_action_group = get_bool("codeActionGroup");
|
||||||
experimental.get("codeActionGroup").and_then(|it| it.as_bool()) == Some(true);
|
self.client_caps.resolve_code_action = get_bool("resolveCodeAction");
|
||||||
self.client_caps.code_action_group = code_action_group;
|
self.client_caps.hover_actions = get_bool("hoverActions");
|
||||||
|
|
||||||
let resolve_code_action =
|
|
||||||
experimental.get("resolveCodeAction").and_then(|it| it.as_bool()) == Some(true);
|
|
||||||
self.client_caps.resolve_code_action = resolve_code_action;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -260,3 +260,35 @@ pub struct SnippetTextEdit {
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub insert_text_format: Option<lsp_types::InsertTextFormat>,
|
pub insert_text_format: Option<lsp_types::InsertTextFormat>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum HoverRequest {}
|
||||||
|
|
||||||
|
impl Request for HoverRequest {
|
||||||
|
type Params = lsp_types::HoverParams;
|
||||||
|
type Result = Option<Hover>;
|
||||||
|
const METHOD: &'static str = "textDocument/hover";
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
|
||||||
|
pub struct Hover {
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub hover: lsp_types::Hover,
|
||||||
|
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||||
|
pub actions: Vec<CommandLinkGroup>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||||
|
pub struct CommandLinkGroup {
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub title: Option<String>,
|
||||||
|
pub commands: Vec<CommandLink>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// LSP v3.15 Command does not have a `tooltip` field, vscode supports one.
|
||||||
|
#[derive(Debug, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||||
|
pub struct CommandLink {
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub command: lsp_types::Command,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub tooltip: Option<String>,
|
||||||
|
}
|
||||||
|
|
|
@ -510,6 +510,7 @@ fn on_request(
|
||||||
.on::<lsp_ext::InlayHints>(handlers::handle_inlay_hints)?
|
.on::<lsp_ext::InlayHints>(handlers::handle_inlay_hints)?
|
||||||
.on::<lsp_ext::CodeActionRequest>(handlers::handle_code_action)?
|
.on::<lsp_ext::CodeActionRequest>(handlers::handle_code_action)?
|
||||||
.on::<lsp_ext::ResolveCodeActionRequest>(handlers::handle_resolve_code_action)?
|
.on::<lsp_ext::ResolveCodeActionRequest>(handlers::handle_resolve_code_action)?
|
||||||
|
.on::<lsp_ext::HoverRequest>(handlers::handle_hover)?
|
||||||
.on::<lsp_types::request::OnTypeFormatting>(handlers::handle_on_type_formatting)?
|
.on::<lsp_types::request::OnTypeFormatting>(handlers::handle_on_type_formatting)?
|
||||||
.on::<lsp_types::request::DocumentSymbolRequest>(handlers::handle_document_symbol)?
|
.on::<lsp_types::request::DocumentSymbolRequest>(handlers::handle_document_symbol)?
|
||||||
.on::<lsp_types::request::WorkspaceSymbol>(handlers::handle_workspace_symbol)?
|
.on::<lsp_types::request::WorkspaceSymbol>(handlers::handle_workspace_symbol)?
|
||||||
|
@ -521,7 +522,6 @@ fn on_request(
|
||||||
.on::<lsp_types::request::CodeLensResolve>(handlers::handle_code_lens_resolve)?
|
.on::<lsp_types::request::CodeLensResolve>(handlers::handle_code_lens_resolve)?
|
||||||
.on::<lsp_types::request::FoldingRangeRequest>(handlers::handle_folding_range)?
|
.on::<lsp_types::request::FoldingRangeRequest>(handlers::handle_folding_range)?
|
||||||
.on::<lsp_types::request::SignatureHelpRequest>(handlers::handle_signature_help)?
|
.on::<lsp_types::request::SignatureHelpRequest>(handlers::handle_signature_help)?
|
||||||
.on::<lsp_types::request::HoverRequest>(handlers::handle_hover)?
|
|
||||||
.on::<lsp_types::request::PrepareRenameRequest>(handlers::handle_prepare_rename)?
|
.on::<lsp_types::request::PrepareRenameRequest>(handlers::handle_prepare_rename)?
|
||||||
.on::<lsp_types::request::Rename>(handlers::handle_rename)?
|
.on::<lsp_types::request::Rename>(handlers::handle_rename)?
|
||||||
.on::<lsp_types::request::References>(handlers::handle_references)?
|
.on::<lsp_types::request::References>(handlers::handle_references)?
|
||||||
|
|
|
@ -12,13 +12,14 @@ use lsp_types::{
|
||||||
CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams, CallHierarchyItem,
|
CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams, CallHierarchyItem,
|
||||||
CallHierarchyOutgoingCall, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams,
|
CallHierarchyOutgoingCall, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams,
|
||||||
CodeLens, Command, CompletionItem, Diagnostic, DocumentFormattingParams, DocumentHighlight,
|
CodeLens, Command, CompletionItem, Diagnostic, DocumentFormattingParams, DocumentHighlight,
|
||||||
DocumentSymbol, FoldingRange, FoldingRangeParams, Hover, HoverContents, Location,
|
DocumentSymbol, FoldingRange, FoldingRangeParams, HoverContents, Location, MarkupContent,
|
||||||
MarkupContent, MarkupKind, Position, PrepareRenameResponse, Range, RenameParams,
|
MarkupKind, Position, PrepareRenameResponse, Range, RenameParams, SemanticTokensParams,
|
||||||
SemanticTokensParams, SemanticTokensRangeParams, SemanticTokensRangeResult,
|
SemanticTokensRangeParams, SemanticTokensRangeResult, SemanticTokensResult, SymbolInformation,
|
||||||
SemanticTokensResult, SymbolInformation, TextDocumentIdentifier, Url, WorkspaceEdit,
|
TextDocumentIdentifier, Url, WorkspaceEdit,
|
||||||
};
|
};
|
||||||
use ra_ide::{
|
use ra_ide::{
|
||||||
FileId, FilePosition, FileRange, Query, RangeInfo, RunnableKind, SearchScope, TextEdit,
|
FileId, FilePosition, FileRange, HoverAction, Query, RangeInfo, RunnableKind, SearchScope,
|
||||||
|
TextEdit,
|
||||||
};
|
};
|
||||||
use ra_prof::profile;
|
use ra_prof::profile;
|
||||||
use ra_project_model::TargetKind;
|
use ra_project_model::TargetKind;
|
||||||
|
@ -537,7 +538,7 @@ pub fn handle_signature_help(
|
||||||
pub fn handle_hover(
|
pub fn handle_hover(
|
||||||
snap: GlobalStateSnapshot,
|
snap: GlobalStateSnapshot,
|
||||||
params: lsp_types::HoverParams,
|
params: lsp_types::HoverParams,
|
||||||
) -> Result<Option<Hover>> {
|
) -> Result<Option<lsp_ext::Hover>> {
|
||||||
let _p = profile("handle_hover");
|
let _p = profile("handle_hover");
|
||||||
let position = from_proto::file_position(&snap, params.text_document_position_params)?;
|
let position = from_proto::file_position(&snap, params.text_document_position_params)?;
|
||||||
let info = match snap.analysis().hover(position)? {
|
let info = match snap.analysis().hover(position)? {
|
||||||
|
@ -546,14 +547,18 @@ pub fn handle_hover(
|
||||||
};
|
};
|
||||||
let line_index = snap.analysis.file_line_index(position.file_id)?;
|
let line_index = snap.analysis.file_line_index(position.file_id)?;
|
||||||
let range = to_proto::range(&line_index, info.range);
|
let range = to_proto::range(&line_index, info.range);
|
||||||
let res = Hover {
|
let hover = lsp_ext::Hover {
|
||||||
|
hover: lsp_types::Hover {
|
||||||
contents: HoverContents::Markup(MarkupContent {
|
contents: HoverContents::Markup(MarkupContent {
|
||||||
kind: MarkupKind::Markdown,
|
kind: MarkupKind::Markdown,
|
||||||
value: crate::markdown::format_docs(&info.info.to_markup()),
|
value: crate::markdown::format_docs(&info.info.to_markup()),
|
||||||
}),
|
}),
|
||||||
range: Some(range),
|
range: Some(range),
|
||||||
|
},
|
||||||
|
actions: prepare_hover_actions(&snap, info.info.actions()),
|
||||||
};
|
};
|
||||||
Ok(Some(res))
|
|
||||||
|
Ok(Some(hover))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_prepare_rename(
|
pub fn handle_prepare_rename(
|
||||||
|
@ -924,24 +929,13 @@ pub fn handle_code_lens_resolve(
|
||||||
_ => vec![],
|
_ => vec![],
|
||||||
};
|
};
|
||||||
|
|
||||||
let title = if locations.len() == 1 {
|
let title = implementation_title(locations.len());
|
||||||
"1 implementation".into()
|
let cmd = show_references_command(
|
||||||
} else {
|
|
||||||
format!("{} implementations", locations.len())
|
|
||||||
};
|
|
||||||
|
|
||||||
// We cannot use the 'editor.action.showReferences' command directly
|
|
||||||
// because that command requires vscode types which we convert in the handler
|
|
||||||
// on the client side.
|
|
||||||
let cmd = Command {
|
|
||||||
title,
|
title,
|
||||||
command: "rust-analyzer.showReferences".into(),
|
&lens_params.text_document_position_params.text_document.uri,
|
||||||
arguments: Some(vec![
|
code_lens.range.start,
|
||||||
to_value(&lens_params.text_document_position_params.text_document.uri).unwrap(),
|
locations,
|
||||||
to_value(code_lens.range.start).unwrap(),
|
);
|
||||||
to_value(locations).unwrap(),
|
|
||||||
]),
|
|
||||||
};
|
|
||||||
Ok(CodeLens { range: code_lens.range, command: Some(cmd), data: None })
|
Ok(CodeLens { range: code_lens.range, command: Some(cmd), data: None })
|
||||||
}
|
}
|
||||||
None => Ok(CodeLens {
|
None => Ok(CodeLens {
|
||||||
|
@ -1145,3 +1139,78 @@ pub fn handle_semantic_tokens_range(
|
||||||
let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights);
|
let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights);
|
||||||
Ok(Some(semantic_tokens.into()))
|
Ok(Some(semantic_tokens.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn implementation_title(count: usize) -> String {
|
||||||
|
if count == 1 {
|
||||||
|
"1 implementation".into()
|
||||||
|
} else {
|
||||||
|
format!("{} implementations", count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_references_command(
|
||||||
|
title: String,
|
||||||
|
uri: &lsp_types::Url,
|
||||||
|
position: lsp_types::Position,
|
||||||
|
locations: Vec<lsp_types::Location>,
|
||||||
|
) -> Command {
|
||||||
|
// We cannot use the 'editor.action.showReferences' command directly
|
||||||
|
// because that command requires vscode types which we convert in the handler
|
||||||
|
// on the client side.
|
||||||
|
|
||||||
|
Command {
|
||||||
|
title,
|
||||||
|
command: "rust-analyzer.showReferences".into(),
|
||||||
|
arguments: Some(vec![
|
||||||
|
to_value(uri).unwrap(),
|
||||||
|
to_value(position).unwrap(),
|
||||||
|
to_value(locations).unwrap(),
|
||||||
|
]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_command_link(command: Command, tooltip: String) -> lsp_ext::CommandLink {
|
||||||
|
lsp_ext::CommandLink { tooltip: Some(tooltip), command }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_impl_command_link(
|
||||||
|
snap: &GlobalStateSnapshot,
|
||||||
|
position: &FilePosition,
|
||||||
|
) -> Option<lsp_ext::CommandLinkGroup> {
|
||||||
|
if snap.config.hover.implementations {
|
||||||
|
if let Some(nav_data) = snap.analysis().goto_implementation(*position).unwrap_or(None) {
|
||||||
|
let uri = to_proto::url(snap, position.file_id).ok()?;
|
||||||
|
let line_index = snap.analysis().file_line_index(position.file_id).ok()?;
|
||||||
|
let position = to_proto::position(&line_index, position.offset);
|
||||||
|
let locations: Vec<_> = nav_data
|
||||||
|
.info
|
||||||
|
.iter()
|
||||||
|
.filter_map(|it| to_proto::location(snap, it.file_range()).ok())
|
||||||
|
.collect();
|
||||||
|
let title = implementation_title(locations.len());
|
||||||
|
let command = show_references_command(title, &uri, position, locations);
|
||||||
|
|
||||||
|
return Some(lsp_ext::CommandLinkGroup {
|
||||||
|
commands: vec![to_command_link(command, "Go to implementations".into())],
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare_hover_actions(
|
||||||
|
snap: &GlobalStateSnapshot,
|
||||||
|
actions: &[HoverAction],
|
||||||
|
) -> Vec<lsp_ext::CommandLinkGroup> {
|
||||||
|
if snap.config.hover.none() || !snap.config.client_caps.hover_actions {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
actions
|
||||||
|
.iter()
|
||||||
|
.filter_map(|it| match it {
|
||||||
|
HoverAction::Implementaion(position) => show_impl_command_link(snap, position),
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
|
@ -467,3 +467,41 @@ interface InlayHint {
|
||||||
label: string,
|
label: string,
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Hover Actions
|
||||||
|
|
||||||
|
**Client Capability:** `{ "hoverActions": boolean }`
|
||||||
|
|
||||||
|
If this capability is set, `Hover` request returned from the server might contain an additional field, `actions`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface Hover {
|
||||||
|
...
|
||||||
|
actions?: CommandLinkGroup[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CommandLink extends Command {
|
||||||
|
/**
|
||||||
|
* A tooltip for the command, when represented in the UI.
|
||||||
|
*/
|
||||||
|
tooltip?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CommandLinkGroup {
|
||||||
|
title?: string;
|
||||||
|
commands: CommandLink[];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Such actions on the client side are appended to a hover bottom as command links:
|
||||||
|
```
|
||||||
|
+-----------------------------+
|
||||||
|
| Hover content |
|
||||||
|
| |
|
||||||
|
+-----------------------------+
|
||||||
|
| _Action1_ | _Action2_ | <- first group, no TITLE
|
||||||
|
+-----------------------------+
|
||||||
|
| TITLE _Action1_ | _Action2_ | <- second group
|
||||||
|
+-----------------------------+
|
||||||
|
...
|
||||||
|
```
|
|
@ -462,17 +462,27 @@
|
||||||
"default": true
|
"default": true
|
||||||
},
|
},
|
||||||
"rust-analyzer.lens.run": {
|
"rust-analyzer.lens.run": {
|
||||||
"markdownDescription": "Whether to show Run lens. Only applies when `#rust-analyzer.lens.enable#` is set.",
|
"markdownDescription": "Whether to show `Run` lens. Only applies when `#rust-analyzer.lens.enable#` is set.",
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"default": true
|
"default": true
|
||||||
},
|
},
|
||||||
"rust-analyzer.lens.debug": {
|
"rust-analyzer.lens.debug": {
|
||||||
"markdownDescription": "Whether to show Debug lens. Only applies when `#rust-analyzer.lens.enable#` is set.",
|
"markdownDescription": "Whether to show `Debug` lens. Only applies when `#rust-analyzer.lens.enable#` is set.",
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"default": true
|
"default": true
|
||||||
},
|
},
|
||||||
"rust-analyzer.lens.implementations": {
|
"rust-analyzer.lens.implementations": {
|
||||||
"markdownDescription": "Whether to show Implementations lens. Only applies when `#rust-analyzer.lens.enable#` is set.",
|
"markdownDescription": "Whether to show `Implementations` lens. Only applies when `#rust-analyzer.lens.enable#` is set.",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"rust-analyzer.hoverActions.enable": {
|
||||||
|
"description": "Whether to show HoverActions in Rust files.",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"rust-analyzer.hoverActions.implementations": {
|
||||||
|
"markdownDescription": "Whether to show `Implementations` action. Only applies when `#rust-analyzer.hoverActions.enable#` is set.",
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"default": true
|
"default": true
|
||||||
},
|
},
|
||||||
|
|
|
@ -7,6 +7,20 @@ import { CallHierarchyFeature } from 'vscode-languageclient/lib/callHierarchy.pr
|
||||||
import { SemanticTokensFeature, DocumentSemanticsTokensSignature } from 'vscode-languageclient/lib/semanticTokens.proposed';
|
import { SemanticTokensFeature, DocumentSemanticsTokensSignature } from 'vscode-languageclient/lib/semanticTokens.proposed';
|
||||||
import { assert } from './util';
|
import { assert } from './util';
|
||||||
|
|
||||||
|
function renderCommand(cmd: ra.CommandLink) {
|
||||||
|
return `[${cmd.title}](command:${cmd.command}?${encodeURIComponent(JSON.stringify(cmd.arguments))} '${cmd.tooltip!}')`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderHoverActions(actions: ra.CommandLinkGroup[]): vscode.MarkdownString {
|
||||||
|
const text = actions.map(group =>
|
||||||
|
(group.title ? (group.title + " ") : "") + group.commands.map(renderCommand).join(' | ')
|
||||||
|
).join('___');
|
||||||
|
|
||||||
|
const result = new vscode.MarkdownString(text);
|
||||||
|
result.isTrusted = true;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
export function createClient(serverPath: string, cwd: string): lc.LanguageClient {
|
export function createClient(serverPath: string, cwd: string): lc.LanguageClient {
|
||||||
// '.' Is the fallback if no folder is open
|
// '.' Is the fallback if no folder is open
|
||||||
// TODO?: Workspace folders support Uri's (eg: file://test.txt).
|
// TODO?: Workspace folders support Uri's (eg: file://test.txt).
|
||||||
|
@ -35,6 +49,23 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient
|
||||||
if (res === undefined) throw new Error('busy');
|
if (res === undefined) throw new Error('busy');
|
||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
|
async provideHover(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken, _next: lc.ProvideHoverSignature) {
|
||||||
|
return client.sendRequest(lc.HoverRequest.type, client.code2ProtocolConverter.asTextDocumentPositionParams(document, position), token).then(
|
||||||
|
(result) => {
|
||||||
|
const hover = client.protocol2CodeConverter.asHover(result);
|
||||||
|
if (hover) {
|
||||||
|
const actions = (<any>result).actions;
|
||||||
|
if (actions) {
|
||||||
|
hover.contents.push(renderHoverActions(actions));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hover;
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
client.logFailedRequest(lc.HoverRequest.type, error);
|
||||||
|
return Promise.resolve(null);
|
||||||
|
});
|
||||||
|
},
|
||||||
// Using custom handling of CodeActions where each code action is resloved lazily
|
// Using custom handling of CodeActions where each code action is resloved lazily
|
||||||
// That's why we are not waiting for any command or edits
|
// That's why we are not waiting for any command or edits
|
||||||
async provideCodeActions(document: vscode.TextDocument, range: vscode.Range, context: vscode.CodeActionContext, token: vscode.CancellationToken, _next: lc.ProvideCodeActionsSignature) {
|
async provideCodeActions(document: vscode.TextDocument, range: vscode.Range, context: vscode.CodeActionContext, token: vscode.CancellationToken, _next: lc.ProvideCodeActionsSignature) {
|
||||||
|
@ -129,6 +160,7 @@ class ExperimentalFeatures implements lc.StaticFeature {
|
||||||
caps.snippetTextEdit = true;
|
caps.snippetTextEdit = true;
|
||||||
caps.codeActionGroup = true;
|
caps.codeActionGroup = true;
|
||||||
caps.resolveCodeAction = true;
|
caps.resolveCodeAction = true;
|
||||||
|
caps.hoverActions = true;
|
||||||
capabilities.experimental = caps;
|
capabilities.experimental = caps;
|
||||||
}
|
}
|
||||||
initialize(_capabilities: lc.ServerCapabilities<any>, _documentSelector: lc.DocumentSelector | undefined): void {
|
initialize(_capabilities: lc.ServerCapabilities<any>, _documentSelector: lc.DocumentSelector | undefined): void {
|
||||||
|
|
|
@ -16,10 +16,8 @@ export class Config {
|
||||||
"files",
|
"files",
|
||||||
"highlighting",
|
"highlighting",
|
||||||
"updates.channel",
|
"updates.channel",
|
||||||
"lens.enable",
|
"lens", // works as lens.*
|
||||||
"lens.run",
|
"hoverActions", // works as hoverActions.*
|
||||||
"lens.debug",
|
|
||||||
"lens.implementations",
|
|
||||||
]
|
]
|
||||||
.map(opt => `${this.rootSection}.${opt}`);
|
.map(opt => `${this.rootSection}.${opt}`);
|
||||||
|
|
||||||
|
@ -132,4 +130,11 @@ export class Config {
|
||||||
implementations: this.get<boolean>("lens.implementations"),
|
implementations: this.get<boolean>("lens.implementations"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get hoverActions() {
|
||||||
|
return {
|
||||||
|
enable: this.get<boolean>("hoverActions.enable"),
|
||||||
|
implementations: this.get<boolean>("hoverActions.implementations"),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,3 +90,15 @@ export interface SsrParams {
|
||||||
parseOnly: boolean;
|
parseOnly: boolean;
|
||||||
}
|
}
|
||||||
export const ssr = new lc.RequestType<SsrParams, lc.WorkspaceEdit, void>('experimental/ssr');
|
export const ssr = new lc.RequestType<SsrParams, lc.WorkspaceEdit, void>('experimental/ssr');
|
||||||
|
|
||||||
|
export interface CommandLink extends lc.Command {
|
||||||
|
/**
|
||||||
|
* A tooltip for the command, when represented in the UI.
|
||||||
|
*/
|
||||||
|
tooltip?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CommandLinkGroup {
|
||||||
|
title?: string;
|
||||||
|
commands: CommandLink[];
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue