-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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: Allow viewing the crate graph in a webview #8801
Changes from all commits
a85a2c4
435c422
9e6d9ba
b8d40a0
5b3af25
d1aa6bb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
use std::{ | ||
error::Error, | ||
io::{Read, Write}, | ||
process::{Command, Stdio}, | ||
sync::Arc, | ||
}; | ||
|
||
use dot::{Id, LabelText}; | ||
use ide_db::{ | ||
base_db::{CrateGraph, CrateId, Dependency, SourceDatabase, SourceDatabaseExt}, | ||
RootDatabase, | ||
}; | ||
use rustc_hash::FxHashSet; | ||
|
||
// Feature: View Crate Graph | ||
// | ||
// Renders the currently loaded crate graph as an SVG graphic. Requires the `dot` tool, which | ||
// is part of graphviz, to be installed. | ||
// | ||
// Only workspace crates are included, no crates.io dependencies or sysroot crates. | ||
// | ||
// |=== | ||
// | Editor | Action Name | ||
// | ||
// | VS Code | **Rust Analyzer: View Crate Graph** | ||
// |=== | ||
pub(crate) fn view_crate_graph(db: &RootDatabase) -> Result<String, String> { | ||
let crate_graph = db.crate_graph(); | ||
let crates_to_render = crate_graph | ||
.iter() | ||
.filter(|krate| { | ||
// Only render workspace crates | ||
let root_id = db.file_source_root(crate_graph[*krate].root_file_id); | ||
!db.source_root(root_id).is_library | ||
}) | ||
.collect(); | ||
let graph = DotCrateGraph { graph: crate_graph, crates_to_render }; | ||
|
||
let mut dot = Vec::new(); | ||
dot::render(&graph, &mut dot).unwrap(); | ||
|
||
render_svg(&dot).map_err(|e| e.to_string()) | ||
} | ||
|
||
fn render_svg(dot: &[u8]) -> Result<String, Box<dyn Error>> { | ||
// We shell out to `dot` to render to SVG, as there does not seem to be a pure-Rust renderer. | ||
let child = Command::new("dot") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems that the layering is slightly off here, we generally don’t do IO, including IPC, in the ide layer and below. I think it’d be more orthogonal to return a string in a .dot format from here, and let the client (rust-analyzer binary) render it to svg. |
||
.arg("-Tsvg") | ||
.stdin(Stdio::piped()) | ||
.stdout(Stdio::piped()) | ||
.spawn() | ||
.map_err(|err| format!("failed to spawn `dot`: {}", err))?; | ||
child.stdin.unwrap().write_all(&dot)?; | ||
|
||
let mut svg = String::new(); | ||
child.stdout.unwrap().read_to_string(&mut svg)?; | ||
Ok(svg) | ||
} | ||
|
||
struct DotCrateGraph { | ||
graph: Arc<CrateGraph>, | ||
crates_to_render: FxHashSet<CrateId>, | ||
} | ||
|
||
type Edge<'a> = (CrateId, &'a Dependency); | ||
|
||
impl<'a> dot::GraphWalk<'a, CrateId, Edge<'a>> for DotCrateGraph { | ||
fn nodes(&'a self) -> dot::Nodes<'a, CrateId> { | ||
self.crates_to_render.iter().copied().collect() | ||
} | ||
|
||
fn edges(&'a self) -> dot::Edges<'a, Edge<'a>> { | ||
self.crates_to_render | ||
.iter() | ||
.flat_map(|krate| { | ||
self.graph[*krate] | ||
.dependencies | ||
.iter() | ||
.filter(|dep| self.crates_to_render.contains(&dep.crate_id)) | ||
.map(move |dep| (*krate, dep)) | ||
}) | ||
.collect() | ||
} | ||
|
||
fn source(&'a self, edge: &Edge<'a>) -> CrateId { | ||
edge.0 | ||
} | ||
|
||
fn target(&'a self, edge: &Edge<'a>) -> CrateId { | ||
edge.1.crate_id | ||
} | ||
} | ||
|
||
impl<'a> dot::Labeller<'a, CrateId, Edge<'a>> for DotCrateGraph { | ||
fn graph_id(&'a self) -> Id<'a> { | ||
Id::new("rust_analyzer_crate_graph").unwrap() | ||
} | ||
|
||
fn node_id(&'a self, n: &CrateId) -> Id<'a> { | ||
Id::new(format!("_{}", n.0)).unwrap() | ||
} | ||
|
||
fn node_shape(&'a self, _node: &CrateId) -> Option<LabelText<'a>> { | ||
Some(LabelText::LabelStr("box".into())) | ||
} | ||
|
||
fn node_label(&'a self, n: &CrateId) -> LabelText<'a> { | ||
let name = self.graph[*n].display_name.as_ref().map_or("(unnamed crate)", |name| &*name); | ||
LabelText::LabelStr(name.into()) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You might want to consider investigating the different layout options. For example
dot -Kneato
. (all options fordot -K
are: circo dot fdp neato nop nop1 nop2 osage patchwork sfdp twopi) Be aware that some option are extremely slow for large graphs though. You can also use options like-Gnslimit=1
,-Glheight=1000
and-Gmaxiter=1
to increase the svg height for clearer graphs or reduce the amount of effort used to layout, increasing speed.