Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Creating rust dependencies tree explorer #11557

Merged
merged 31 commits into from
May 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
795a1cb
Creating rust dependencies tree view
bruno-ortiz Feb 26, 2022
68aa20b
fixing stblib loading
bruno-ortiz Feb 26, 2022
77a4bfd
fixing linting problemas
bruno-ortiz Feb 26, 2022
364308d
removing unused function
bruno-ortiz Feb 26, 2022
76432d3
Reformat VSCode client code
Veykril Jul 17, 2022
16cba19
Remove unnecessary openFile command
Veykril Jul 17, 2022
1201b15
WIP: Add lsp-ext scaffold
Veykril Jul 17, 2022
09e0a00
fetching dependencies from the server
bruno-ortiz Apr 3, 2023
d01fc64
Creating rust dependencies tree view
bruno-ortiz Feb 26, 2022
f8215dd
fixing stblib loading
bruno-ortiz Feb 26, 2022
ee54c65
fixing linting problemas
bruno-ortiz Feb 26, 2022
d1721b1
removing unused function
bruno-ortiz Feb 26, 2022
af999f1
Reformat VSCode client code
Veykril Jul 17, 2022
9533644
Remove unnecessary openFile command
Veykril Jul 17, 2022
299382d
WIP: Add lsp-ext scaffold
Veykril Jul 17, 2022
fc57339
removing unused code
bruno-ortiz Apr 3, 2023
440889e
fixing main_loop.rs
bruno-ortiz Apr 3, 2023
061940d
running prettier
bruno-ortiz Apr 3, 2023
e253592
Fixing tests
bruno-ortiz Apr 3, 2023
1b8288f
Fixing naming from graph to list
bruno-ortiz Apr 4, 2023
8e687f7
improving code to work with multi-workspaces
bruno-ortiz Apr 5, 2023
a3081a6
Adding crate_root_path to crate_graph
bruno-ortiz Apr 8, 2023
fe7874a
reveal only when tree is visible
bruno-ortiz Apr 8, 2023
c372fb3
fixing tests for windows
bruno-ortiz Apr 8, 2023
66fe84d
accepting review suggestions
bruno-ortiz Apr 13, 2023
bd2160f
final rabasing fixes
bruno-ortiz Apr 27, 2023
072f69e
fixing ts linting and rust test
bruno-ortiz Apr 27, 2023
800b3b6
adding doc and simplifying function
bruno-ortiz Apr 27, 2023
bcb2131
Accepting review suggestions
bruno-ortiz Apr 27, 2023
0aed507
fixing TS linting, removing import
bruno-ortiz Apr 27, 2023
ecfe7c0
last fixes after rebase
bruno-ortiz May 2, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it be more general (and more in line with the language server protocol) to use textDocument: TextDocumentIdentifier here instead of the current path: string?

And as a side note: why this is called a graph/tree, when this is just a list? It is not possible to construct a graph from the response. Ie.: visualize the the dependencies of a dependency.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nemethf you're right about the naming, I changed from "graph" to "list". The tree is because the crates are displayed in a "ViewTree".
About the TextDocumentIdentifier , I tried using it, but it just made the code more complicated, because to reveal the items in the viewTree I need to work with the filesystem path, and the TextDocumentIdentifier inputs the "scheme" in the path.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

About the TextDocumentIdentifier , I tried using it, but it just made the code more complicated, because to reveal the items in the viewTree I need to work with the filesystem path, and the TextDocumentIdentifier inputs the "scheme" in the path.

Looking over vscode.TreeItem, it seems like it requires you to operate in terms of URIs anyways, so I think it might clean up some of the code here if everything here operated in terms of URIs—the scheme can be dropped accordingly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tree is because the crates are displayed in a "ViewTree".

Thank you. I have no objections against it. However, it is a bit strange that a client-side implementation detail determines an abstract name in protocol specification. I can imagine some clients using this extension for something else, for example, to provide a "jump to dependency" functionality that implements an incremental search on the dependencies without showing them beforehand.

Also this fetchDependencyGraph reminds me of the rust-analyzer/viewCrateGraph LSP extension. In the long run, it might make sense to generalize both of them to send the same details. fetchDependencyGraph could list the dependencies of a dependency, and viewCrateGraph could be extended to have a uri/path for each dependency.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A TextDocumentIdentifier would indeed make more sense if possible

}[];
}
```
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