Skip to content

Commit a48e0e1

Browse files
committed
Auto merge of rust-lang#11557 - bruno-ortiz:rust-dependencies, r=bruno-ortiz
Creating rust dependencies tree explorer Hello! I tried to implement a tree view that shows the dependencies of a project. It allows to see all dependencies to the project and it uses `cargo tree` for it. Also it allows to click and open the files, the viewtree tries its best to follow the openned file in the editor. Here is an example: ![image](https://user-images.githubusercontent.com/5748995/155822183-1e227c7b-7929-4fc8-8eed-29ccfc5e14fe.png) Any feedback is welcome since i have basically no professional experience with TS.
2 parents cffc402 + ecfe7c0 commit a48e0e1

File tree

17 files changed

+522
-13
lines changed

17 files changed

+522
-13
lines changed

crates/ide/src/fetch_crates.rs

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
use ide_db::{
2+
base_db::{CrateOrigin, FileId, SourceDatabase},
3+
FxIndexSet, RootDatabase,
4+
};
5+
6+
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
7+
pub struct CrateInfo {
8+
pub name: Option<String>,
9+
pub version: Option<String>,
10+
pub root_file_id: FileId,
11+
}
12+
13+
// Feature: Show Dependency Tree
14+
//
15+
// Shows a view tree with all the dependencies of this project
16+
//
17+
// |===
18+
// image::https://user-images.githubusercontent.com/5748995/229394139-2625beab-f4c9-484b-84ed-ad5dee0b1e1a.png[]
19+
pub(crate) fn fetch_crates(db: &RootDatabase) -> FxIndexSet<CrateInfo> {
20+
let crate_graph = db.crate_graph();
21+
crate_graph
22+
.iter()
23+
.map(|crate_id| &crate_graph[crate_id])
24+
.filter(|&data| !matches!(data.origin, CrateOrigin::Local { .. }))
25+
.map(|data| crate_info(data))
26+
.collect()
27+
}
28+
29+
fn crate_info(data: &ide_db::base_db::CrateData) -> CrateInfo {
30+
let crate_name = crate_name(data);
31+
let version = data.version.clone();
32+
CrateInfo { name: crate_name, version, root_file_id: data.root_file_id }
33+
}
34+
35+
fn crate_name(data: &ide_db::base_db::CrateData) -> Option<String> {
36+
data.display_name.as_ref().map(|it| it.canonical_name().to_owned())
37+
}

crates/ide/src/lib.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -59,16 +59,18 @@ mod view_mir;
5959
mod interpret_function;
6060
mod view_item_tree;
6161
mod shuffle_crate_graph;
62+
mod fetch_crates;
6263

6364
use std::sync::Arc;
6465

6566
use cfg::CfgOptions;
67+
use fetch_crates::CrateInfo;
6668
use ide_db::{
6769
base_db::{
6870
salsa::{self, ParallelDatabase},
6971
CrateOrigin, Env, FileLoader, FileSet, SourceDatabase, VfsPath,
7072
},
71-
symbol_index, FxHashMap, LineIndexDatabase,
73+
symbol_index, FxHashMap, FxIndexSet, LineIndexDatabase,
7274
};
7375
use syntax::SourceFile;
7476

@@ -331,6 +333,10 @@ impl Analysis {
331333
self.with_db(|db| view_crate_graph::view_crate_graph(db, full))
332334
}
333335

336+
pub fn fetch_crates(&self) -> Cancellable<FxIndexSet<CrateInfo>> {
337+
self.with_db(|db| fetch_crates::fetch_crates(db))
338+
}
339+
334340
pub fn expand_macro(&self, position: FilePosition) -> Cancellable<Option<ExpandedMacro>> {
335341
self.with_db(|db| expand_macro::expand_macro(db, position))
336342
}

crates/paths/src/lib.rs

+7
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,13 @@ impl AbsPath {
184184
self.0.ends_with(&suffix.0)
185185
}
186186

187+
pub fn name_and_extension(&self) -> Option<(&str, Option<&str>)> {
188+
Some((
189+
self.file_stem()?.to_str()?,
190+
self.extension().and_then(|extension| extension.to_str()),
191+
))
192+
}
193+
187194
// region:delegate-methods
188195

189196
// Note that we deliberately don't implement `Deref<Target = Path>` here.

crates/project-model/src/tests.rs

+13
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,18 @@ fn replace_root(s: &mut String, direction: bool) {
102102
}
103103
}
104104

105+
fn replace_fake_sys_root(s: &mut String) {
106+
let fake_sysroot_path = get_test_path("fake-sysroot");
107+
let fake_sysroot_path = if cfg!(windows) {
108+
let normalized_path =
109+
fake_sysroot_path.to_str().expect("expected str").replace(r#"\"#, r#"\\"#);
110+
format!(r#"{}\\"#, normalized_path)
111+
} else {
112+
format!("{}/", fake_sysroot_path.to_str().expect("expected str"))
113+
};
114+
*s = s.replace(&fake_sysroot_path, "$FAKESYSROOT$")
115+
}
116+
105117
fn get_test_path(file: &str) -> PathBuf {
106118
let base = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
107119
base.join("test_data").join(file)
@@ -140,6 +152,7 @@ fn to_crate_graph(project_workspace: ProjectWorkspace) -> (CrateGraph, ProcMacro
140152
fn check_crate_graph(crate_graph: CrateGraph, expect: ExpectFile) {
141153
let mut crate_graph = format!("{crate_graph:#?}");
142154
replace_root(&mut crate_graph, false);
155+
replace_fake_sys_root(&mut crate_graph);
143156
expect.assert_eq(&crate_graph);
144157
}
145158

crates/rust-analyzer/src/handlers.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
//! `ide` crate.
44
55
use ide::AssistResolveStrategy;
6-
use lsp_types::{Diagnostic, DiagnosticTag, NumberOrString};
6+
use lsp_types::{Diagnostic, DiagnosticTag, NumberOrString, Url};
7+
78
use vfs::FileId;
89

910
use crate::{global_state::GlobalStateSnapshot, to_proto, Result};
@@ -27,7 +28,7 @@ pub(crate) fn publish_diagnostics(
2728
severity: Some(to_proto::diagnostic_severity(d.severity)),
2829
code: Some(NumberOrString::String(d.code.as_str().to_string())),
2930
code_description: Some(lsp_types::CodeDescription {
30-
href: lsp_types::Url::parse(&format!(
31+
href: Url::parse(&format!(
3132
"https://rust-analyzer.github.io/manual.html#{}",
3233
d.code.as_str()
3334
))

crates/rust-analyzer/src/handlers/request.rs

+55-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
//! Protocol. This module specifically handles requests.
33
44
use std::{
5+
fs,
56
io::Write as _,
67
process::{self, Stdio},
78
sync::Arc,
@@ -29,7 +30,7 @@ use project_model::{ManifestPath, ProjectWorkspace, TargetKind};
2930
use serde_json::json;
3031
use stdx::{format_to, never};
3132
use syntax::{algo, ast, AstNode, TextRange, TextSize};
32-
use vfs::{AbsPath, AbsPathBuf};
33+
use vfs::{AbsPath, AbsPathBuf, VfsPath};
3334

3435
use crate::{
3536
cargo_target_spec::CargoTargetSpec,
@@ -38,7 +39,10 @@ use crate::{
3839
from_proto,
3940
global_state::{GlobalState, GlobalStateSnapshot},
4041
line_index::LineEndings,
41-
lsp_ext::{self, PositionOrRange, ViewCrateGraphParams, WorkspaceSymbolParams},
42+
lsp_ext::{
43+
self, CrateInfoResult, FetchDependencyListParams, FetchDependencyListResult,
44+
PositionOrRange, ViewCrateGraphParams, WorkspaceSymbolParams,
45+
},
4246
lsp_utils::{all_edits_are_disjoint, invalid_params_error},
4347
to_proto, LspError, Result,
4448
};
@@ -1881,3 +1885,52 @@ fn run_rustfmt(
18811885
Ok(Some(to_proto::text_edit_vec(&line_index, diff(&file, &new_text))))
18821886
}
18831887
}
1888+
1889+
pub(crate) fn fetch_dependency_list(
1890+
state: GlobalStateSnapshot,
1891+
_params: FetchDependencyListParams,
1892+
) -> Result<FetchDependencyListResult> {
1893+
let crates = state.analysis.fetch_crates()?;
1894+
let crate_infos = crates
1895+
.into_iter()
1896+
.filter_map(|it| {
1897+
let root_file_path = state.file_id_to_file_path(it.root_file_id);
1898+
crate_path(root_file_path).and_then(to_url).map(|path| CrateInfoResult {
1899+
name: it.name,
1900+
version: it.version,
1901+
path,
1902+
})
1903+
})
1904+
.collect();
1905+
Ok(FetchDependencyListResult { crates: crate_infos })
1906+
}
1907+
1908+
/// Searches for the directory of a Rust crate given this crate's root file path.
1909+
///
1910+
/// # Arguments
1911+
///
1912+
/// * `root_file_path`: The path to the root file of the crate.
1913+
///
1914+
/// # Returns
1915+
///
1916+
/// An `Option` value representing the path to the directory of the crate with the given
1917+
/// name, if such a crate is found. If no crate with the given name is found, this function
1918+
/// returns `None`.
1919+
fn crate_path(root_file_path: VfsPath) -> Option<VfsPath> {
1920+
let mut current_dir = root_file_path.parent();
1921+
while let Some(path) = current_dir {
1922+
let cargo_toml_path = path.join("../Cargo.toml")?;
1923+
if fs::metadata(cargo_toml_path.as_path()?).is_ok() {
1924+
let crate_path = cargo_toml_path.parent()?;
1925+
return Some(crate_path);
1926+
}
1927+
current_dir = path.parent();
1928+
}
1929+
None
1930+
}
1931+
1932+
fn to_url(path: VfsPath) -> Option<Url> {
1933+
let path = path.as_path()?;
1934+
let str_path = path.as_os_str().to_str()?;
1935+
Url::from_file_path(str_path).ok()
1936+
}

crates/rust-analyzer/src/lsp_ext.rs

+27-1
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ use std::{collections::HashMap, path::PathBuf};
44

55
use ide_db::line_index::WideEncoding;
66
use lsp_types::request::Request;
7-
use lsp_types::PositionEncodingKind;
87
use lsp_types::{
98
notification::Notification, CodeActionKind, DocumentOnTypeFormattingParams,
109
PartialResultParams, Position, Range, TextDocumentIdentifier, WorkDoneProgressParams,
1110
};
11+
use lsp_types::{PositionEncodingKind, Url};
1212
use serde::{Deserialize, Serialize};
1313

1414
use crate::line_index::PositionEncoding;
@@ -27,6 +27,31 @@ pub struct AnalyzerStatusParams {
2727
pub text_document: Option<TextDocumentIdentifier>,
2828
}
2929

30+
#[derive(Deserialize, Serialize, Debug)]
31+
#[serde(rename_all = "camelCase")]
32+
pub struct CrateInfoResult {
33+
pub name: Option<String>,
34+
pub version: Option<String>,
35+
pub path: Url,
36+
}
37+
pub enum FetchDependencyList {}
38+
39+
impl Request for FetchDependencyList {
40+
type Params = FetchDependencyListParams;
41+
type Result = FetchDependencyListResult;
42+
const METHOD: &'static str = "rust-analyzer/fetchDependencyList";
43+
}
44+
45+
#[derive(Deserialize, Serialize, Debug)]
46+
#[serde(rename_all = "camelCase")]
47+
pub struct FetchDependencyListParams {}
48+
49+
#[derive(Deserialize, Serialize, Debug)]
50+
#[serde(rename_all = "camelCase")]
51+
pub struct FetchDependencyListResult {
52+
pub crates: Vec<CrateInfoResult>,
53+
}
54+
3055
pub enum MemoryUsage {}
3156

3257
impl Request for MemoryUsage {
@@ -359,6 +384,7 @@ impl Request for CodeActionRequest {
359384
}
360385

361386
pub enum CodeActionResolveRequest {}
387+
362388
impl Request for CodeActionResolveRequest {
363389
type Params = CodeAction;
364390
type Result = CodeAction;

crates/rust-analyzer/src/main_loop.rs

+1
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,7 @@ impl GlobalState {
660660
.on_sync::<lsp_ext::OnEnter>(handlers::handle_on_enter)
661661
.on_sync::<lsp_types::request::SelectionRangeRequest>(handlers::handle_selection_range)
662662
.on_sync::<lsp_ext::MatchingBrace>(handlers::handle_matching_brace)
663+
.on::<lsp_ext::FetchDependencyList>(handlers::fetch_dependency_list)
663664
.on::<lsp_ext::AnalyzerStatus>(handlers::handle_analyzer_status)
664665
.on::<lsp_ext::SyntaxTree>(handlers::handle_syntax_tree)
665666
.on::<lsp_ext::ViewHir>(handlers::handle_view_hir)

crates/vfs/src/vfs_path.rs

+1-4
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,7 @@ impl VfsPath {
107107
/// Returns `self`'s base name and file extension.
108108
pub fn name_and_extension(&self) -> Option<(&str, Option<&str>)> {
109109
match &self.0 {
110-
VfsPathRepr::PathBuf(p) => Some((
111-
p.file_stem()?.to_str()?,
112-
p.extension().and_then(|extension| extension.to_str()),
113-
)),
110+
VfsPathRepr::PathBuf(p) => p.name_and_extension(),
114111
VfsPathRepr::VirtualPath(p) => p.name_and_extension(),
115112
}
116113
}

docs/dev/lsp-extensions.md

+24-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<!---
2-
lsp_ext.rs hash: 31ca513a249753ab
2+
lsp_ext.rs hash: fdf1afd34548abbc
33
44
If you need to change the above hash to make the test pass, please check if you
55
need to adjust this doc as well and ping this issue:
@@ -851,3 +851,26 @@ export interface Diagnostic {
851851
rendered?: string;
852852
};
853853
}
854+
```
855+
856+
## Dependency Tree
857+
858+
**Method:** `rust-analyzer/fetchDependencyList`
859+
860+
**Request:**
861+
862+
```typescript
863+
export interface FetchDependencyListParams {}
864+
```
865+
866+
**Response:**
867+
```typescript
868+
export interface FetchDependencyListResult {
869+
crates: {
870+
name: string;
871+
version: string;
872+
path: string;
873+
}[];
874+
}
875+
```
876+
Returns all crates from this workspace, so it can be used create a viewTree to help navigate the dependency tree.

editors/code/package.json

+16
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,14 @@
284284
"command": "rust-analyzer.clearFlycheck",
285285
"title": "Clear flycheck diagnostics",
286286
"category": "rust-analyzer"
287+
},
288+
{
289+
"command": "rust-analyzer.revealDependency",
290+
"title": "Reveal File"
291+
},
292+
{
293+
"command": "rust-analyzer.revealDependency",
294+
"title": "Reveal File"
287295
}
288296
],
289297
"keybindings": [
@@ -1956,6 +1964,14 @@
19561964
}
19571965
]
19581966
},
1967+
"views": {
1968+
"explorer": [
1969+
{
1970+
"id": "rustDependencies",
1971+
"name": "Rust Dependencies"
1972+
}
1973+
]
1974+
},
19591975
"jsonValidation": [
19601976
{
19611977
"fileMatch": "rust-project.json",

0 commit comments

Comments
 (0)