mirror of
https://github.com/rust-lang/rust.git
synced 2024-12-01 19:23:50 +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: ![hover_actions_impl](https://user-images.githubusercontent.com/62505555/83335732-6d9de280-a2b7-11ea-8cc3-75253d062fe0.gif) 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
@ -11,8 +11,8 @@ use ra_syntax::{ast, Parse, SourceFile, TextRange, TextSize};
|
||||
pub use crate::{
|
||||
cancellation::Canceled,
|
||||
input::{
|
||||
CrateGraph, CrateId, CrateName, Dependency, Edition, Env, ExternSource, ExternSourceId,
|
||||
FileId, ProcMacroId, SourceRoot, SourceRootId,
|
||||
CrateData, CrateGraph, CrateId, CrateName, Dependency, Edition, Env, ExternSource,
|
||||
ExternSourceId, FileId, ProcMacroId, SourceRoot, SourceRootId,
|
||||
},
|
||||
};
|
||||
pub use relative_path::{RelativePath, RelativePathBuf};
|
||||
|
@ -3,11 +3,11 @@
|
||||
pub use hir_def::db::{
|
||||
AttrsQuery, BodyQuery, BodyWithSourceMapQuery, ConstDataQuery, CrateDefMapQueryQuery,
|
||||
CrateLangItemsQuery, DefDatabase, DefDatabaseStorage, DocumentationQuery, EnumDataQuery,
|
||||
ExprScopesQuery, FunctionDataQuery, GenericParamsQuery, ImplDataQuery, InternConstQuery,
|
||||
InternDatabase, InternDatabaseStorage, InternEnumQuery, InternFunctionQuery, InternImplQuery,
|
||||
InternStaticQuery, InternStructQuery, InternTraitQuery, InternTypeAliasQuery, InternUnionQuery,
|
||||
LangItemQuery, ModuleLangItemsQuery, RawItemsQuery, StaticDataQuery, StructDataQuery,
|
||||
TraitDataQuery, TypeAliasDataQuery, UnionDataQuery,
|
||||
ExprScopesQuery, FunctionDataQuery, GenericParamsQuery, ImplDataQuery, ImportMapQuery,
|
||||
InternConstQuery, InternDatabase, InternDatabaseStorage, InternEnumQuery, InternFunctionQuery,
|
||||
InternImplQuery, InternStaticQuery, InternStructQuery, InternTraitQuery, InternTypeAliasQuery,
|
||||
InternUnionQuery, LangItemQuery, ModuleLangItemsQuery, RawItemsQuery, StaticDataQuery,
|
||||
StructDataQuery, TraitDataQuery, TypeAliasDataQuery, UnionDataQuery,
|
||||
};
|
||||
pub use hir_expand::db::{
|
||||
AstDatabase, AstDatabaseStorage, AstIdMapQuery, InternEagerExpansionQuery, InternMacroQuery,
|
||||
|
@ -1,7 +1,7 @@
|
||||
//! Defines database & queries for name resolution.
|
||||
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_prof::profile;
|
||||
use ra_syntax::SmolStr;
|
||||
@ -12,13 +12,10 @@ use crate::{
|
||||
body::{scope::ExprScopes, Body, BodySourceMap},
|
||||
data::{ConstData, FunctionData, ImplData, StaticData, TraitData, TypeAliasData},
|
||||
docs::Documentation,
|
||||
find_path,
|
||||
generics::GenericParams,
|
||||
item_scope::ItemInNs,
|
||||
import_map::ImportMap,
|
||||
lang_item::{LangItemTarget, LangItems},
|
||||
nameres::{raw::RawItems, CrateDefMap},
|
||||
path::ModPath,
|
||||
visibility::Visibility,
|
||||
AttrDefId, ConstId, ConstLoc, DefWithBodyId, EnumId, EnumLoc, FunctionId, FunctionLoc,
|
||||
GenericDefId, ImplId, ImplLoc, ModuleId, StaticId, StaticLoc, StructId, StructLoc, TraitId,
|
||||
TraitLoc, TypeAliasId, TypeAliasLoc, UnionId, UnionLoc,
|
||||
@ -113,15 +110,8 @@ pub trait DefDatabase: InternDatabase + AstDatabase + Upcast<dyn AstDatabase> {
|
||||
#[salsa::invoke(Documentation::documentation_query)]
|
||||
fn documentation(&self, def: AttrDefId) -> Option<Documentation>;
|
||||
|
||||
#[salsa::invoke(find_path::importable_locations_of_query)]
|
||||
fn importable_locations_of(
|
||||
&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>;
|
||||
#[salsa::invoke(ImportMap::import_map_query)]
|
||||
fn import_map(&self, krate: CrateId) -> Arc<ImportMap>;
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use hir_expand::name::{known, AsName, Name};
|
||||
use ra_prof::profile;
|
||||
use rustc_hash::FxHashSet;
|
||||
use test_utils::mark;
|
||||
|
||||
use crate::{
|
||||
@ -11,7 +10,7 @@ use crate::{
|
||||
item_scope::ItemInNs,
|
||||
path::{ModPath, PathKind},
|
||||
visibility::Visibility,
|
||||
CrateId, ModuleDefId, ModuleId,
|
||||
ModuleDefId, ModuleId,
|
||||
};
|
||||
|
||||
// FIXME: handle local items
|
||||
@ -20,7 +19,7 @@ use crate::{
|
||||
/// *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> {
|
||||
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;
|
||||
@ -36,20 +35,9 @@ impl ModPath {
|
||||
let first_segment = self.segments.first();
|
||||
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,
|
||||
item: ItemInNs,
|
||||
from: ModuleId,
|
||||
@ -133,31 +121,61 @@ pub(crate) fn find_path_inner_query(
|
||||
}
|
||||
|
||||
// - 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_attrs = db.attrs(crate_root.into());
|
||||
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_len = max_len;
|
||||
for (module_id, name) in importable_locations {
|
||||
let mut path = match db.find_path_inner(
|
||||
ItemInNs::Types(ModuleDefId::ModuleId(module_id)),
|
||||
from,
|
||||
best_path_len - 1,
|
||||
) {
|
||||
None => continue,
|
||||
Some(path) => path,
|
||||
};
|
||||
path.segments.push(name);
|
||||
|
||||
let new_path = if let Some(best_path) = best_path {
|
||||
select_best_path(best_path, path, prefer_no_std)
|
||||
} else {
|
||||
path
|
||||
};
|
||||
best_path_len = new_path.len();
|
||||
best_path = Some(new_path);
|
||||
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)),
|
||||
from,
|
||||
best_path_len - 1,
|
||||
) {
|
||||
path.segments.push(name);
|
||||
|
||||
let new_path = if let Some(best_path) = best_path {
|
||||
select_best_path(best_path, path, prefer_no_std)
|
||||
} else {
|
||||
path
|
||||
};
|
||||
best_path_len = new_path.len();
|
||||
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
|
||||
}
|
||||
|
||||
@ -185,69 +203,86 @@ 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,
|
||||
item: ItemInNs,
|
||||
from: ModuleId,
|
||||
) -> Vec<(ModuleId, Name)> {
|
||||
let crate_graph = db.crate_graph();
|
||||
let mut result = Vec::new();
|
||||
// We only look in the crate from which we are importing, and the direct
|
||||
// dependencies. We cannot refer to names from transitive dependencies
|
||||
// directly (only through reexports in direct dependencies).
|
||||
for krate in Some(from.krate)
|
||||
.into_iter()
|
||||
.chain(crate_graph[from.krate].dependencies.iter().map(|dep| dep.crate_id))
|
||||
{
|
||||
result.extend(
|
||||
db.importable_locations_of(item, krate)
|
||||
.iter()
|
||||
.filter(|(_, _, vis)| vis.is_visible_from(db, from))
|
||||
.map(|(m, n, _)| (*m, n.clone())),
|
||||
);
|
||||
}
|
||||
result
|
||||
}
|
||||
let _p = profile("find_local_import_locations");
|
||||
|
||||
// `from` can import anything below `from` with visibility of at least `from`, and anything
|
||||
// above `from` with any visibility. That means we do not need to descend into private siblings
|
||||
// of `from` (and similar).
|
||||
|
||||
let def_map = db.crate_def_map(from.krate);
|
||||
|
||||
// Compute the initial worklist. We start with all direct child modules of `from` as well as all
|
||||
// of its (recursive) parent modules.
|
||||
let data = &def_map.modules[from.local_id];
|
||||
let mut worklist = data
|
||||
.children
|
||||
.values()
|
||||
.map(|child| ModuleId { krate: from.krate, local_id: *child })
|
||||
.collect::<Vec<_>>();
|
||||
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;
|
||||
}
|
||||
|
||||
let mut seen: FxHashSet<_> = FxHashSet::default();
|
||||
|
||||
let mut locations = Vec::new();
|
||||
while let Some(module) = worklist.pop() {
|
||||
if !seen.insert(module) {
|
||||
continue; // already processed this module
|
||||
}
|
||||
|
||||
let ext_def_map;
|
||||
let data = if module.krate == from.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]
|
||||
};
|
||||
|
||||
/// Collects all locations from which we might import the item in a particular
|
||||
/// crate. These include the original definition of the item, and any
|
||||
/// non-private `use`s.
|
||||
///
|
||||
/// Note that the crate doesn't need to be the one in which the item is defined;
|
||||
/// it might be re-exported in other crates.
|
||||
pub(crate) fn importable_locations_of_query(
|
||||
db: &dyn DefDatabase,
|
||||
item: ItemInNs,
|
||||
krate: CrateId,
|
||||
) -> Arc<[(ModuleId, Name, Visibility)]> {
|
||||
let _p = profile("importable_locations_of_query");
|
||||
let def_map = db.crate_def_map(krate);
|
||||
let mut result = Vec::new();
|
||||
for (local_id, data) in def_map.modules.iter() {
|
||||
if let Some((name, vis)) = data.scope.name_of(item) {
|
||||
let is_private = if let Visibility::Module(private_to) = vis {
|
||||
private_to.local_id == local_id
|
||||
} else {
|
||||
false
|
||||
};
|
||||
let is_original_def = if let Some(module_def_id) = item.as_module_def_id() {
|
||||
data.scope.declarations().any(|it| it == module_def_id)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if is_private && !is_original_def {
|
||||
if vis.is_visible_from(db, from) {
|
||||
let is_private = if let Visibility::Module(private_to) = vis {
|
||||
private_to.local_id == module.local_id
|
||||
} else {
|
||||
false
|
||||
};
|
||||
let is_original_def = if let Some(module_def_id) = item.as_module_def_id() {
|
||||
data.scope.declarations().any(|it| it == module_def_id)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
// Ignore private imports. these could be used if we are
|
||||
// in a submodule of this module, but that's usually not
|
||||
// what the user wants; and if this module can import
|
||||
// the item and we're a submodule of it, so can we.
|
||||
// Also this keeps the cached data smaller.
|
||||
continue;
|
||||
if !is_private || is_original_def {
|
||||
locations.push((module, name.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
result.push((ModuleId { krate, local_id }, name.clone(), vis));
|
||||
}
|
||||
}
|
||||
|
||||
Arc::from(result)
|
||||
locations
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -385,6 +420,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn different_crate_renamed() {
|
||||
// Even if a local path exists, if the item is defined externally, prefer an external path.
|
||||
let code = r#"
|
||||
//- /main.rs crate:main deps:std
|
||||
extern crate std as std_renamed;
|
||||
@ -392,7 +428,7 @@ mod tests {
|
||||
//- /std.rs crate:std
|
||||
pub struct S;
|
||||
"#;
|
||||
check_found_path(code, "std_renamed::S");
|
||||
check_found_path(code, "std::S");
|
||||
}
|
||||
|
||||
#[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 once_cell::sync::Lazy;
|
||||
use ra_db::CrateId;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use crate::{
|
||||
per_ns::PerNs, visibility::Visibility, AdtId, BuiltinType, ImplId, MacroDefId, ModuleDefId,
|
||||
TraitId,
|
||||
db::DefDatabase, per_ns::PerNs, visibility::Visibility, AdtId, BuiltinType, HasModule, ImplId,
|
||||
Lookup, MacroDefId, ModuleDefId, TraitId,
|
||||
};
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
@ -203,4 +204,22 @@ impl ItemInNs {
|
||||
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 find_path;
|
||||
pub mod import_map;
|
||||
|
||||
#[cfg(test)]
|
||||
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 {
|
||||
self.kind == PathKind::Plain && self.segments.len() == 1
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
use hir_expand::MacroDefId;
|
||||
|
||||
use crate::{visibility::Visibility, ModuleDefId};
|
||||
use crate::{item_scope::ItemInNs, visibility::Visibility, ModuleDefId};
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub struct PerNs {
|
||||
@ -84,4 +84,12 @@ impl PerNs {
|
||||
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 crate::{
|
||||
display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel},
|
||||
FilePosition, RangeInfo,
|
||||
display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel, ToNav},
|
||||
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
|
||||
#[derive(Debug, Default)]
|
||||
pub struct HoverResult {
|
||||
results: Vec<String>,
|
||||
actions: Vec<HoverAction>,
|
||||
}
|
||||
|
||||
impl HoverResult {
|
||||
@ -48,10 +77,20 @@ impl HoverResult {
|
||||
&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
|
||||
/// for displaying in a UI
|
||||
///
|
||||
/// Does not process actions!
|
||||
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));
|
||||
|
||||
if !res.is_empty() {
|
||||
if let Some(action) = show_implementations_action(db, name_kind) {
|
||||
res.push_action(action);
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
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(
|
||||
docs: Option<String>,
|
||||
desc: Option<String>,
|
||||
@ -228,6 +291,8 @@ fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use ra_db::FileLoader;
|
||||
use ra_syntax::TextRange;
|
||||
|
||||
@ -241,7 +306,14 @@ mod tests {
|
||||
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 hover = analysis.hover(position).unwrap().unwrap();
|
||||
let mut results = Vec::from(hover.info.results());
|
||||
@ -256,7 +328,7 @@ mod tests {
|
||||
assert_eq!(hover.info.len(), expected.len());
|
||||
|
||||
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) {
|
||||
@ -746,7 +818,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
|
||||
|
||||
#[test]
|
||||
fn test_hover_through_macro() {
|
||||
let hover_on = check_hover_result(
|
||||
let (hover_on, _) = check_hover_result(
|
||||
"
|
||||
//- /lib.rs
|
||||
macro_rules! id {
|
||||
@ -767,7 +839,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
|
||||
|
||||
#[test]
|
||||
fn test_hover_through_expr_in_macro() {
|
||||
let hover_on = check_hover_result(
|
||||
let (hover_on, _) = check_hover_result(
|
||||
"
|
||||
//- /lib.rs
|
||||
macro_rules! id {
|
||||
@ -785,7 +857,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
|
||||
|
||||
#[test]
|
||||
fn test_hover_through_expr_in_macro_recursive() {
|
||||
let hover_on = check_hover_result(
|
||||
let (hover_on, _) = check_hover_result(
|
||||
"
|
||||
//- /lib.rs
|
||||
macro_rules! id_deep {
|
||||
@ -806,7 +878,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
|
||||
|
||||
#[test]
|
||||
fn test_hover_through_func_in_macro_recursive() {
|
||||
let hover_on = check_hover_result(
|
||||
let (hover_on, _) = check_hover_result(
|
||||
"
|
||||
//- /lib.rs
|
||||
macro_rules! id_deep {
|
||||
@ -830,7 +902,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
|
||||
|
||||
#[test]
|
||||
fn test_hover_through_literal_string_in_macro() {
|
||||
let hover_on = check_hover_result(
|
||||
let (hover_on, _) = check_hover_result(
|
||||
r#"
|
||||
//- /lib.rs
|
||||
macro_rules! arr {
|
||||
@ -849,7 +921,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
|
||||
|
||||
#[test]
|
||||
fn test_hover_through_assert_macro() {
|
||||
let hover_on = check_hover_result(
|
||||
let (hover_on, _) = check_hover_result(
|
||||
r#"
|
||||
//- /lib.rs
|
||||
#[rustc_builtin_macro]
|
||||
@ -925,13 +997,14 @@ fn func(foo: i32) { if true { <|>foo; }; }
|
||||
|
||||
#[test]
|
||||
fn test_hover_trait_show_qualifiers() {
|
||||
check_hover_result(
|
||||
let (_, actions) = check_hover_result(
|
||||
"
|
||||
//- /lib.rs
|
||||
unsafe trait foo<|>() {}
|
||||
",
|
||||
&["unsafe trait foo"],
|
||||
);
|
||||
assert_impl_action(&actions[0], 13);
|
||||
}
|
||||
|
||||
#[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"],
|
||||
);
|
||||
}
|
||||
|
||||
#[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},
|
||||
expand_macro::ExpandedMacro,
|
||||
folding_ranges::{Fold, FoldKind},
|
||||
hover::HoverResult,
|
||||
hover::{HoverAction, HoverConfig, HoverResult},
|
||||
inlay_hints::{InlayHint, InlayHintsConfig, InlayKind},
|
||||
references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult},
|
||||
runnables::{Runnable, RunnableKind, TestId},
|
||||
|
@ -334,6 +334,7 @@ impl RootDatabase {
|
||||
hir::db::CrateLangItemsQuery
|
||||
hir::db::LangItemQuery
|
||||
hir::db::DocumentationQuery
|
||||
hir::db::ImportMapQuery
|
||||
|
||||
// InternDatabase
|
||||
hir::db::InternFunctionQuery
|
||||
|
@ -18,7 +18,7 @@ use ra_syntax::{
|
||||
use crate::RootDatabase;
|
||||
|
||||
// FIXME: a more precise name would probably be `Symbol`?
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
pub enum Definition {
|
||||
Macro(MacroDef),
|
||||
Field(Field),
|
||||
|
@ -11,7 +11,7 @@ use std::{ffi::OsString, path::PathBuf};
|
||||
|
||||
use lsp_types::ClientCapabilities;
|
||||
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 serde::Deserialize;
|
||||
|
||||
@ -34,6 +34,7 @@ pub struct Config {
|
||||
pub assist: AssistConfig,
|
||||
pub call_info_full: bool,
|
||||
pub lens: LensConfig,
|
||||
pub hover: HoverConfig,
|
||||
|
||||
pub with_sysroot: bool,
|
||||
pub linked_projects: Vec<LinkedProject>,
|
||||
@ -124,6 +125,7 @@ pub struct ClientCapsConfig {
|
||||
pub work_done_progress: bool,
|
||||
pub code_action_group: bool,
|
||||
pub resolve_code_action: bool,
|
||||
pub hover_actions: bool,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
@ -162,6 +164,7 @@ impl Default for Config {
|
||||
assist: AssistConfig::default(),
|
||||
call_info_full: true,
|
||||
lens: LensConfig::default(),
|
||||
hover: HoverConfig::default(),
|
||||
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);
|
||||
|
||||
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);
|
||||
if let Some(experimental) = &caps.experimental {
|
||||
let snippet_text_edit =
|
||||
experimental.get("snippetTextEdit").and_then(|it| it.as_bool()) == Some(true);
|
||||
let get_bool =
|
||||
|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);
|
||||
|
||||
let code_action_group =
|
||||
experimental.get("codeActionGroup").and_then(|it| it.as_bool()) == Some(true);
|
||||
self.client_caps.code_action_group = code_action_group;
|
||||
|
||||
let resolve_code_action =
|
||||
experimental.get("resolveCodeAction").and_then(|it| it.as_bool()) == Some(true);
|
||||
self.client_caps.resolve_code_action = resolve_code_action;
|
||||
self.client_caps.code_action_group = get_bool("codeActionGroup");
|
||||
self.client_caps.resolve_code_action = get_bool("resolveCodeAction");
|
||||
self.client_caps.hover_actions = get_bool("hoverActions");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -260,3 +260,35 @@ pub struct SnippetTextEdit {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
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::CodeActionRequest>(handlers::handle_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::DocumentSymbolRequest>(handlers::handle_document_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::FoldingRangeRequest>(handlers::handle_folding_range)?
|
||||
.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::Rename>(handlers::handle_rename)?
|
||||
.on::<lsp_types::request::References>(handlers::handle_references)?
|
||||
|
@ -12,13 +12,14 @@ use lsp_types::{
|
||||
CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams, CallHierarchyItem,
|
||||
CallHierarchyOutgoingCall, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams,
|
||||
CodeLens, Command, CompletionItem, Diagnostic, DocumentFormattingParams, DocumentHighlight,
|
||||
DocumentSymbol, FoldingRange, FoldingRangeParams, Hover, HoverContents, Location,
|
||||
MarkupContent, MarkupKind, Position, PrepareRenameResponse, Range, RenameParams,
|
||||
SemanticTokensParams, SemanticTokensRangeParams, SemanticTokensRangeResult,
|
||||
SemanticTokensResult, SymbolInformation, TextDocumentIdentifier, Url, WorkspaceEdit,
|
||||
DocumentSymbol, FoldingRange, FoldingRangeParams, HoverContents, Location, MarkupContent,
|
||||
MarkupKind, Position, PrepareRenameResponse, Range, RenameParams, SemanticTokensParams,
|
||||
SemanticTokensRangeParams, SemanticTokensRangeResult, SemanticTokensResult, SymbolInformation,
|
||||
TextDocumentIdentifier, Url, WorkspaceEdit,
|
||||
};
|
||||
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_project_model::TargetKind;
|
||||
@ -537,7 +538,7 @@ pub fn handle_signature_help(
|
||||
pub fn handle_hover(
|
||||
snap: GlobalStateSnapshot,
|
||||
params: lsp_types::HoverParams,
|
||||
) -> Result<Option<Hover>> {
|
||||
) -> Result<Option<lsp_ext::Hover>> {
|
||||
let _p = profile("handle_hover");
|
||||
let position = from_proto::file_position(&snap, params.text_document_position_params)?;
|
||||
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 range = to_proto::range(&line_index, info.range);
|
||||
let res = Hover {
|
||||
contents: HoverContents::Markup(MarkupContent {
|
||||
kind: MarkupKind::Markdown,
|
||||
value: crate::markdown::format_docs(&info.info.to_markup()),
|
||||
}),
|
||||
range: Some(range),
|
||||
let hover = lsp_ext::Hover {
|
||||
hover: lsp_types::Hover {
|
||||
contents: HoverContents::Markup(MarkupContent {
|
||||
kind: MarkupKind::Markdown,
|
||||
value: crate::markdown::format_docs(&info.info.to_markup()),
|
||||
}),
|
||||
range: Some(range),
|
||||
},
|
||||
actions: prepare_hover_actions(&snap, info.info.actions()),
|
||||
};
|
||||
Ok(Some(res))
|
||||
|
||||
Ok(Some(hover))
|
||||
}
|
||||
|
||||
pub fn handle_prepare_rename(
|
||||
@ -924,24 +929,13 @@ pub fn handle_code_lens_resolve(
|
||||
_ => vec![],
|
||||
};
|
||||
|
||||
let title = if locations.len() == 1 {
|
||||
"1 implementation".into()
|
||||
} 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 {
|
||||
let title = implementation_title(locations.len());
|
||||
let cmd = show_references_command(
|
||||
title,
|
||||
command: "rust-analyzer.showReferences".into(),
|
||||
arguments: Some(vec![
|
||||
to_value(&lens_params.text_document_position_params.text_document.uri).unwrap(),
|
||||
to_value(code_lens.range.start).unwrap(),
|
||||
to_value(locations).unwrap(),
|
||||
]),
|
||||
};
|
||||
&lens_params.text_document_position_params.text_document.uri,
|
||||
code_lens.range.start,
|
||||
locations,
|
||||
);
|
||||
Ok(CodeLens { range: code_lens.range, command: Some(cmd), data: None })
|
||||
}
|
||||
None => Ok(CodeLens {
|
||||
@ -1145,3 +1139,78 @@ pub fn handle_semantic_tokens_range(
|
||||
let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights);
|
||||
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,
|
||||
}
|
||||
```
|
||||
|
||||
## 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
|
||||
},
|
||||
"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",
|
||||
"default": true
|
||||
},
|
||||
"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",
|
||||
"default": true
|
||||
},
|
||||
"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",
|
||||
"default": true
|
||||
},
|
||||
|
@ -7,6 +7,20 @@ import { CallHierarchyFeature } from 'vscode-languageclient/lib/callHierarchy.pr
|
||||
import { SemanticTokensFeature, DocumentSemanticsTokensSignature } from 'vscode-languageclient/lib/semanticTokens.proposed';
|
||||
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 {
|
||||
// '.' Is the fallback if no folder is open
|
||||
// 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');
|
||||
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
|
||||
// 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) {
|
||||
@ -129,6 +160,7 @@ class ExperimentalFeatures implements lc.StaticFeature {
|
||||
caps.snippetTextEdit = true;
|
||||
caps.codeActionGroup = true;
|
||||
caps.resolveCodeAction = true;
|
||||
caps.hoverActions = true;
|
||||
capabilities.experimental = caps;
|
||||
}
|
||||
initialize(_capabilities: lc.ServerCapabilities<any>, _documentSelector: lc.DocumentSelector | undefined): void {
|
||||
|
@ -16,10 +16,8 @@ export class Config {
|
||||
"files",
|
||||
"highlighting",
|
||||
"updates.channel",
|
||||
"lens.enable",
|
||||
"lens.run",
|
||||
"lens.debug",
|
||||
"lens.implementations",
|
||||
"lens", // works as lens.*
|
||||
"hoverActions", // works as hoverActions.*
|
||||
]
|
||||
.map(opt => `${this.rootSection}.${opt}`);
|
||||
|
||||
@ -132,4 +130,11 @@ export class Config {
|
||||
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;
|
||||
}
|
||||
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…
Reference in New Issue
Block a user