Skip to content

Commit

Permalink
Auto merge of rust-lang#11557 - bruno-ortiz:rust-dependencies, r=brun…
Browse files Browse the repository at this point in the history
…o-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.
  • Loading branch information
bors committed May 2, 2023
2 parents cffc402 + ecfe7c0 commit a48e0e1
Show file tree
Hide file tree
Showing 17 changed files with 522 additions and 13 deletions.
37 changes: 37 additions & 0 deletions crates/ide/src/fetch_crates.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use ide_db::{
base_db::{CrateOrigin, FileId, SourceDatabase},
FxIndexSet, RootDatabase,
};

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct CrateInfo {
pub name: Option<String>,
pub version: Option<String>,
pub root_file_id: FileId,
}

// Feature: Show Dependency Tree
//
// Shows a view tree with all the dependencies of this project
//
// |===
// image::https://user-images.githubusercontent.com/5748995/229394139-2625beab-f4c9-484b-84ed-ad5dee0b1e1a.png[]
pub(crate) fn fetch_crates(db: &RootDatabase) -> FxIndexSet<CrateInfo> {
let crate_graph = db.crate_graph();
crate_graph
.iter()
.map(|crate_id| &crate_graph[crate_id])
.filter(|&data| !matches!(data.origin, CrateOrigin::Local { .. }))
.map(|data| crate_info(data))
.collect()
}

fn crate_info(data: &ide_db::base_db::CrateData) -> CrateInfo {
let crate_name = crate_name(data);
let version = data.version.clone();
CrateInfo { name: crate_name, version, root_file_id: data.root_file_id }
}

fn crate_name(data: &ide_db::base_db::CrateData) -> Option<String> {
data.display_name.as_ref().map(|it| it.canonical_name().to_owned())
}
8 changes: 7 additions & 1 deletion crates/ide/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,18 @@ mod view_mir;
mod interpret_function;
mod view_item_tree;
mod shuffle_crate_graph;
mod fetch_crates;

use std::sync::Arc;

use cfg::CfgOptions;
use fetch_crates::CrateInfo;
use ide_db::{
base_db::{
salsa::{self, ParallelDatabase},
CrateOrigin, Env, FileLoader, FileSet, SourceDatabase, VfsPath,
},
symbol_index, FxHashMap, LineIndexDatabase,
symbol_index, FxHashMap, FxIndexSet, LineIndexDatabase,
};
use syntax::SourceFile;

Expand Down Expand Up @@ -331,6 +333,10 @@ impl Analysis {
self.with_db(|db| view_crate_graph::view_crate_graph(db, full))
}

pub fn fetch_crates(&self) -> Cancellable<FxIndexSet<CrateInfo>> {
self.with_db(|db| fetch_crates::fetch_crates(db))
}

pub fn expand_macro(&self, position: FilePosition) -> Cancellable<Option<ExpandedMacro>> {
self.with_db(|db| expand_macro::expand_macro(db, position))
}
Expand Down
7 changes: 7 additions & 0 deletions crates/paths/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,13 @@ impl AbsPath {
self.0.ends_with(&suffix.0)
}

pub fn name_and_extension(&self) -> Option<(&str, Option<&str>)> {
Some((
self.file_stem()?.to_str()?,
self.extension().and_then(|extension| extension.to_str()),
))
}

// region:delegate-methods

// Note that we deliberately don't implement `Deref<Target = Path>` here.
Expand Down
13 changes: 13 additions & 0 deletions crates/project-model/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,18 @@ fn replace_root(s: &mut String, direction: bool) {
}
}

fn replace_fake_sys_root(s: &mut String) {
let fake_sysroot_path = get_test_path("fake-sysroot");
let fake_sysroot_path = if cfg!(windows) {
let normalized_path =
fake_sysroot_path.to_str().expect("expected str").replace(r#"\"#, r#"\\"#);
format!(r#"{}\\"#, normalized_path)
} else {
format!("{}/", fake_sysroot_path.to_str().expect("expected str"))
};
*s = s.replace(&fake_sysroot_path, "$FAKESYSROOT$")
}

fn get_test_path(file: &str) -> PathBuf {
let base = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
base.join("test_data").join(file)
Expand Down Expand Up @@ -140,6 +152,7 @@ fn to_crate_graph(project_workspace: ProjectWorkspace) -> (CrateGraph, ProcMacro
fn check_crate_graph(crate_graph: CrateGraph, expect: ExpectFile) {
let mut crate_graph = format!("{crate_graph:#?}");
replace_root(&mut crate_graph, false);
replace_fake_sys_root(&mut crate_graph);
expect.assert_eq(&crate_graph);
}

Expand Down
5 changes: 3 additions & 2 deletions crates/rust-analyzer/src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
//! `ide` crate.

use ide::AssistResolveStrategy;
use lsp_types::{Diagnostic, DiagnosticTag, NumberOrString};
use lsp_types::{Diagnostic, DiagnosticTag, NumberOrString, Url};

use vfs::FileId;

use crate::{global_state::GlobalStateSnapshot, to_proto, Result};
Expand All @@ -27,7 +28,7 @@ pub(crate) fn publish_diagnostics(
severity: Some(to_proto::diagnostic_severity(d.severity)),
code: Some(NumberOrString::String(d.code.as_str().to_string())),
code_description: Some(lsp_types::CodeDescription {
href: lsp_types::Url::parse(&format!(
href: Url::parse(&format!(
"https://rust-analyzer.github.io/manual.html#{}",
d.code.as_str()
))
Expand Down
57 changes: 55 additions & 2 deletions crates/rust-analyzer/src/handlers/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
//! Protocol. This module specifically handles requests.

use std::{
fs,
io::Write as _,
process::{self, Stdio},
sync::Arc,
Expand Down Expand Up @@ -29,7 +30,7 @@ use project_model::{ManifestPath, ProjectWorkspace, TargetKind};
use serde_json::json;
use stdx::{format_to, never};
use syntax::{algo, ast, AstNode, TextRange, TextSize};
use vfs::{AbsPath, AbsPathBuf};
use vfs::{AbsPath, AbsPathBuf, VfsPath};

use crate::{
cargo_target_spec::CargoTargetSpec,
Expand All @@ -38,7 +39,10 @@ use crate::{
from_proto,
global_state::{GlobalState, GlobalStateSnapshot},
line_index::LineEndings,
lsp_ext::{self, PositionOrRange, ViewCrateGraphParams, WorkspaceSymbolParams},
lsp_ext::{
self, CrateInfoResult, FetchDependencyListParams, FetchDependencyListResult,
PositionOrRange, ViewCrateGraphParams, WorkspaceSymbolParams,
},
lsp_utils::{all_edits_are_disjoint, invalid_params_error},
to_proto, LspError, Result,
};
Expand Down Expand Up @@ -1881,3 +1885,52 @@ fn run_rustfmt(
Ok(Some(to_proto::text_edit_vec(&line_index, diff(&file, &new_text))))
}
}

pub(crate) fn fetch_dependency_list(
state: GlobalStateSnapshot,
_params: FetchDependencyListParams,
) -> Result<FetchDependencyListResult> {
let crates = state.analysis.fetch_crates()?;
let crate_infos = crates
.into_iter()
.filter_map(|it| {
let root_file_path = state.file_id_to_file_path(it.root_file_id);
crate_path(root_file_path).and_then(to_url).map(|path| CrateInfoResult {
name: it.name,
version: it.version,
path,
})
})
.collect();
Ok(FetchDependencyListResult { crates: crate_infos })
}

/// Searches for the directory of a Rust crate given this crate's root file path.
///
/// # Arguments
///
/// * `root_file_path`: The path to the root file of the crate.
///
/// # Returns
///
/// An `Option` value representing the path to the directory of the crate with the given
/// name, if such a crate is found. If no crate with the given name is found, this function
/// returns `None`.
fn crate_path(root_file_path: VfsPath) -> Option<VfsPath> {
let mut current_dir = root_file_path.parent();
while let Some(path) = current_dir {
let cargo_toml_path = path.join("../Cargo.toml")?;
if fs::metadata(cargo_toml_path.as_path()?).is_ok() {
let crate_path = cargo_toml_path.parent()?;
return Some(crate_path);
}
current_dir = path.parent();
}
None
}

fn to_url(path: VfsPath) -> Option<Url> {
let path = path.as_path()?;
let str_path = path.as_os_str().to_str()?;
Url::from_file_path(str_path).ok()
}
28 changes: 27 additions & 1 deletion crates/rust-analyzer/src/lsp_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ use std::{collections::HashMap, path::PathBuf};

use ide_db::line_index::WideEncoding;
use lsp_types::request::Request;
use lsp_types::PositionEncodingKind;
use lsp_types::{
notification::Notification, CodeActionKind, DocumentOnTypeFormattingParams,
PartialResultParams, Position, Range, TextDocumentIdentifier, WorkDoneProgressParams,
};
use lsp_types::{PositionEncodingKind, Url};
use serde::{Deserialize, Serialize};

use crate::line_index::PositionEncoding;
Expand All @@ -27,6 +27,31 @@ pub struct AnalyzerStatusParams {
pub text_document: Option<TextDocumentIdentifier>,
}

#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct CrateInfoResult {
pub name: Option<String>,
pub version: Option<String>,
pub path: Url,
}
pub enum FetchDependencyList {}

impl Request for FetchDependencyList {
type Params = FetchDependencyListParams;
type Result = FetchDependencyListResult;
const METHOD: &'static str = "rust-analyzer/fetchDependencyList";
}

#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct FetchDependencyListParams {}

#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct FetchDependencyListResult {
pub crates: Vec<CrateInfoResult>,
}

pub enum MemoryUsage {}

impl Request for MemoryUsage {
Expand Down Expand Up @@ -359,6 +384,7 @@ impl Request for CodeActionRequest {
}

pub enum CodeActionResolveRequest {}

impl Request for CodeActionResolveRequest {
type Params = CodeAction;
type Result = CodeAction;
Expand Down
1 change: 1 addition & 0 deletions crates/rust-analyzer/src/main_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,7 @@ impl GlobalState {
.on_sync::<lsp_ext::OnEnter>(handlers::handle_on_enter)
.on_sync::<lsp_types::request::SelectionRangeRequest>(handlers::handle_selection_range)
.on_sync::<lsp_ext::MatchingBrace>(handlers::handle_matching_brace)
.on::<lsp_ext::FetchDependencyList>(handlers::fetch_dependency_list)
.on::<lsp_ext::AnalyzerStatus>(handlers::handle_analyzer_status)
.on::<lsp_ext::SyntaxTree>(handlers::handle_syntax_tree)
.on::<lsp_ext::ViewHir>(handlers::handle_view_hir)
Expand Down
5 changes: 1 addition & 4 deletions crates/vfs/src/vfs_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,7 @@ impl VfsPath {
/// Returns `self`'s base name and file extension.
pub fn name_and_extension(&self) -> Option<(&str, Option<&str>)> {
match &self.0 {
VfsPathRepr::PathBuf(p) => Some((
p.file_stem()?.to_str()?,
p.extension().and_then(|extension| extension.to_str()),
)),
VfsPathRepr::PathBuf(p) => p.name_and_extension(),
VfsPathRepr::VirtualPath(p) => p.name_and_extension(),
}
}
Expand Down
25 changes: 24 additions & 1 deletion docs/dev/lsp-extensions.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!---
lsp_ext.rs hash: 31ca513a249753ab
lsp_ext.rs hash: fdf1afd34548abbc
If you need to change the above hash to make the test pass, please check if you
need to adjust this doc as well and ping this issue:
Expand Down Expand Up @@ -851,3 +851,26 @@ export interface Diagnostic {
rendered?: string;
};
}
```

## Dependency Tree

**Method:** `rust-analyzer/fetchDependencyList`

**Request:**

```typescript
export interface FetchDependencyListParams {}
```

**Response:**
```typescript
export interface FetchDependencyListResult {
crates: {
name: string;
version: string;
path: string;
}[];
}
```
Returns all crates from this workspace, so it can be used create a viewTree to help navigate the dependency tree.
16 changes: 16 additions & 0 deletions editors/code/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,14 @@
"command": "rust-analyzer.clearFlycheck",
"title": "Clear flycheck diagnostics",
"category": "rust-analyzer"
},
{
"command": "rust-analyzer.revealDependency",
"title": "Reveal File"
},
{
"command": "rust-analyzer.revealDependency",
"title": "Reveal File"
}
],
"keybindings": [
Expand Down Expand Up @@ -1956,6 +1964,14 @@
}
]
},
"views": {
"explorer": [
{
"id": "rustDependencies",
"name": "Rust Dependencies"
}
]
},
"jsonValidation": [
{
"fileMatch": "rust-project.json",
Expand Down
Loading

0 comments on commit a48e0e1

Please sign in to comment.