aboutsummaryrefslogtreecommitdiff
path: root/crates/ide
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2021-05-11 19:02:31 +0100
committerGitHub <[email protected]>2021-05-11 19:02:31 +0100
commite290891dd75f2ae2d156e8610fd037a84c1b853f (patch)
treea7a8eb48ae6d5e4ccb759d1b2c180d522170097d /crates/ide
parent6afd9b2b8dcfaa9338303f29d8fc6c90dbcdd6e7 (diff)
parentd1aa6bbe753e50517d568a4b7560f765fbc75bd2 (diff)
Merge #8801
8801: feat: Allow viewing the crate graph in a webview r=jonas-schievink a=jonas-schievink This uses `dot` to render the crate graph as an SVD file, and displays it in a VS Code panel. For simple crate graphs, it works quite well: ![screenshot-2021-05-11-16:19:32](https://user-images.githubusercontent.com/1786438/117831361-c4a48980-b274-11eb-9276-240cdf6919aa.png) Unfortunately, on rust-analyzer itself (and most medium-sized dependency graphs), `dot` runs for around a minute and then produces this mess: ![screenshot-2021-05-11-16:41:37](https://user-images.githubusercontent.com/1786438/117834831-c754ae00-b277-11eb-850b-138495dbeba8.png) Co-authored-by: Jonas Schievink <[email protected]>
Diffstat (limited to 'crates/ide')
-rw-r--r--crates/ide/Cargo.toml1
-rw-r--r--crates/ide/src/lib.rs5
-rw-r--r--crates/ide/src/view_crate_graph.rs111
3 files changed, 117 insertions, 0 deletions
diff --git a/crates/ide/Cargo.toml b/crates/ide/Cargo.toml
index f04bcf531..88f3d09d3 100644
--- a/crates/ide/Cargo.toml
+++ b/crates/ide/Cargo.toml
@@ -20,6 +20,7 @@ oorandom = "11.1.2"
20pulldown-cmark-to-cmark = "6.0.0" 20pulldown-cmark-to-cmark = "6.0.0"
21pulldown-cmark = { version = "0.8.0", default-features = false } 21pulldown-cmark = { version = "0.8.0", default-features = false }
22url = "2.1.1" 22url = "2.1.1"
23dot = "0.1.4"
23 24
24stdx = { path = "../stdx", version = "0.0.0" } 25stdx = { path = "../stdx", version = "0.0.0" }
25syntax = { path = "../syntax", version = "0.0.0" } 26syntax = { path = "../syntax", version = "0.0.0" }
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index 8e5b72044..34360501a 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -49,6 +49,7 @@ mod syntax_tree;
49mod typing; 49mod typing;
50mod markdown_remove; 50mod markdown_remove;
51mod doc_links; 51mod doc_links;
52mod view_crate_graph;
52 53
53use std::sync::Arc; 54use std::sync::Arc;
54 55
@@ -287,6 +288,10 @@ impl Analysis {
287 self.with_db(|db| view_hir::view_hir(&db, position)) 288 self.with_db(|db| view_hir::view_hir(&db, position))
288 } 289 }
289 290
291 pub fn view_crate_graph(&self) -> Cancelable<Result<String, String>> {
292 self.with_db(|db| view_crate_graph::view_crate_graph(&db))
293 }
294
290 pub fn expand_macro(&self, position: FilePosition) -> Cancelable<Option<ExpandedMacro>> { 295 pub fn expand_macro(&self, position: FilePosition) -> Cancelable<Option<ExpandedMacro>> {
291 self.with_db(|db| expand_macro::expand_macro(db, position)) 296 self.with_db(|db| expand_macro::expand_macro(db, position))
292 } 297 }
diff --git a/crates/ide/src/view_crate_graph.rs b/crates/ide/src/view_crate_graph.rs
new file mode 100644
index 000000000..5e4ba881e
--- /dev/null
+++ b/crates/ide/src/view_crate_graph.rs
@@ -0,0 +1,111 @@
1use std::{
2 error::Error,
3 io::{Read, Write},
4 process::{Command, Stdio},
5 sync::Arc,
6};
7
8use dot::{Id, LabelText};
9use ide_db::{
10 base_db::{CrateGraph, CrateId, Dependency, SourceDatabase, SourceDatabaseExt},
11 RootDatabase,
12};
13use rustc_hash::FxHashSet;
14
15// Feature: View Crate Graph
16//
17// Renders the currently loaded crate graph as an SVG graphic. Requires the `dot` tool, which
18// is part of graphviz, to be installed.
19//
20// Only workspace crates are included, no crates.io dependencies or sysroot crates.
21//
22// |===
23// | Editor | Action Name
24//
25// | VS Code | **Rust Analyzer: View Crate Graph**
26// |===
27pub(crate) fn view_crate_graph(db: &RootDatabase) -> Result<String, String> {
28 let crate_graph = db.crate_graph();
29 let crates_to_render = crate_graph
30 .iter()
31 .filter(|krate| {
32 // Only render workspace crates
33 let root_id = db.file_source_root(crate_graph[*krate].root_file_id);
34 !db.source_root(root_id).is_library
35 })
36 .collect();
37 let graph = DotCrateGraph { graph: crate_graph, crates_to_render };
38
39 let mut dot = Vec::new();
40 dot::render(&graph, &mut dot).unwrap();
41
42 render_svg(&dot).map_err(|e| e.to_string())
43}
44
45fn render_svg(dot: &[u8]) -> Result<String, Box<dyn Error>> {
46 // We shell out to `dot` to render to SVG, as there does not seem to be a pure-Rust renderer.
47 let child = Command::new("dot")
48 .arg("-Tsvg")
49 .stdin(Stdio::piped())
50 .stdout(Stdio::piped())
51 .spawn()
52 .map_err(|err| format!("failed to spawn `dot`: {}", err))?;
53 child.stdin.unwrap().write_all(&dot)?;
54
55 let mut svg = String::new();
56 child.stdout.unwrap().read_to_string(&mut svg)?;
57 Ok(svg)
58}
59
60struct DotCrateGraph {
61 graph: Arc<CrateGraph>,
62 crates_to_render: FxHashSet<CrateId>,
63}
64
65type Edge<'a> = (CrateId, &'a Dependency);
66
67impl<'a> dot::GraphWalk<'a, CrateId, Edge<'a>> for DotCrateGraph {
68 fn nodes(&'a self) -> dot::Nodes<'a, CrateId> {
69 self.crates_to_render.iter().copied().collect()
70 }
71
72 fn edges(&'a self) -> dot::Edges<'a, Edge<'a>> {
73 self.crates_to_render
74 .iter()
75 .flat_map(|krate| {
76 self.graph[*krate]
77 .dependencies
78 .iter()
79 .filter(|dep| self.crates_to_render.contains(&dep.crate_id))
80 .map(move |dep| (*krate, dep))
81 })
82 .collect()
83 }
84
85 fn source(&'a self, edge: &Edge<'a>) -> CrateId {
86 edge.0
87 }
88
89 fn target(&'a self, edge: &Edge<'a>) -> CrateId {
90 edge.1.crate_id
91 }
92}
93
94impl<'a> dot::Labeller<'a, CrateId, Edge<'a>> for DotCrateGraph {
95 fn graph_id(&'a self) -> Id<'a> {
96 Id::new("rust_analyzer_crate_graph").unwrap()
97 }
98
99 fn node_id(&'a self, n: &CrateId) -> Id<'a> {
100 Id::new(format!("_{}", n.0)).unwrap()
101 }
102
103 fn node_shape(&'a self, _node: &CrateId) -> Option<LabelText<'a>> {
104 Some(LabelText::LabelStr("box".into()))
105 }
106
107 fn node_label(&'a self, n: &CrateId) -> LabelText<'a> {
108 let name = self.graph[*n].display_name.as_ref().map_or("(unnamed crate)", |name| &*name);
109 LabelText::LabelStr(name.into())
110 }
111}