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 <jonasschievink@gmail.com>
This commit is contained in:
commit
e290891dd7
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -319,6 +319,12 @@ version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc4b29f4b9bb94bf267d57269fd0706d343a160937108e9619fe380645428abb"
|
||||
|
||||
[[package]]
|
||||
name = "dot"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a74b6c4d4a1cff5f454164363c16b72fa12463ca6b31f4b5f2035a65fa3d5906"
|
||||
|
||||
[[package]]
|
||||
name = "drop_bomb"
|
||||
version = "0.1.5"
|
||||
@ -588,6 +594,7 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"cfg",
|
||||
"cov-mark",
|
||||
"dot",
|
||||
"either",
|
||||
"expect-test",
|
||||
"hir",
|
||||
|
@ -20,6 +20,7 @@ oorandom = "11.1.2"
|
||||
pulldown-cmark-to-cmark = "6.0.0"
|
||||
pulldown-cmark = { version = "0.8.0", default-features = false }
|
||||
url = "2.1.1"
|
||||
dot = "0.1.4"
|
||||
|
||||
stdx = { path = "../stdx", version = "0.0.0" }
|
||||
syntax = { path = "../syntax", version = "0.0.0" }
|
||||
|
@ -49,6 +49,7 @@ macro_rules! eprintln {
|
||||
mod typing;
|
||||
mod markdown_remove;
|
||||
mod doc_links;
|
||||
mod view_crate_graph;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
@ -287,6 +288,10 @@ pub fn view_hir(&self, position: FilePosition) -> Cancelable<String> {
|
||||
self.with_db(|db| view_hir::view_hir(&db, position))
|
||||
}
|
||||
|
||||
pub fn view_crate_graph(&self) -> Cancelable<Result<String, String>> {
|
||||
self.with_db(|db| view_crate_graph::view_crate_graph(&db))
|
||||
}
|
||||
|
||||
pub fn expand_macro(&self, position: FilePosition) -> Cancelable<Option<ExpandedMacro>> {
|
||||
self.with_db(|db| expand_macro::expand_macro(db, position))
|
||||
}
|
||||
|
111
crates/ide/src/view_crate_graph.rs
Normal file
111
crates/ide/src/view_crate_graph.rs
Normal file
@ -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")
|
||||
.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())
|
||||
}
|
||||
}
|
@ -117,6 +117,12 @@ pub(crate) fn handle_view_hir(
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub(crate) fn handle_view_crate_graph(snap: GlobalStateSnapshot, (): ()) -> Result<String> {
|
||||
let _p = profile::span("handle_view_crate_graph");
|
||||
let res = snap.analysis.view_crate_graph()??;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub(crate) fn handle_expand_macro(
|
||||
snap: GlobalStateSnapshot,
|
||||
params: lsp_ext::ExpandMacroParams,
|
||||
|
@ -61,6 +61,14 @@ impl Request for ViewHir {
|
||||
const METHOD: &'static str = "rust-analyzer/viewHir";
|
||||
}
|
||||
|
||||
pub enum ViewCrateGraph {}
|
||||
|
||||
impl Request for ViewCrateGraph {
|
||||
type Params = ();
|
||||
type Result = String;
|
||||
const METHOD: &'static str = "rust-analyzer/viewCrateGraph";
|
||||
}
|
||||
|
||||
pub enum ExpandMacro {}
|
||||
|
||||
impl Request for ExpandMacro {
|
||||
|
@ -513,6 +513,7 @@ fn on_request(&mut self, request_received: Instant, req: Request) -> Result<()>
|
||||
.on::<lsp_ext::AnalyzerStatus>(handlers::handle_analyzer_status)
|
||||
.on::<lsp_ext::SyntaxTree>(handlers::handle_syntax_tree)
|
||||
.on::<lsp_ext::ViewHir>(handlers::handle_view_hir)
|
||||
.on::<lsp_ext::ViewCrateGraph>(handlers::handle_view_crate_graph)
|
||||
.on::<lsp_ext::ExpandMacro>(handlers::handle_expand_macro)
|
||||
.on::<lsp_ext::ParentModule>(handlers::handle_parent_module)
|
||||
.on::<lsp_ext::Runnables>(handlers::handle_runnables)
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!---
|
||||
lsp_ext.rs hash: 28a9d5a24b7ca396
|
||||
lsp_ext.rs hash: 6e57fc1b345b00e9
|
||||
|
||||
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:
|
||||
@ -486,6 +486,16 @@ Primarily for debugging, but very useful for all people working on rust-analyzer
|
||||
Returns a textual representation of the HIR of the function containing the cursor.
|
||||
For debugging or when working on rust-analyzer itself.
|
||||
|
||||
## View Crate Graph
|
||||
|
||||
**Method:** `rust-analyzer/viewCrateGraph`
|
||||
|
||||
**Request:** `null`
|
||||
|
||||
**Response:** `string`
|
||||
|
||||
Renders rust-analyzer's crate graph as an SVG image.
|
||||
|
||||
## Expand Macro
|
||||
|
||||
**Method:** `rust-analyzer/expandMacro`
|
||||
|
@ -109,6 +109,11 @@
|
||||
"title": "View Hir",
|
||||
"category": "Rust Analyzer"
|
||||
},
|
||||
{
|
||||
"command": "rust-analyzer.viewCrateGraph",
|
||||
"title": "View Crate Graph",
|
||||
"category": "Rust Analyzer"
|
||||
},
|
||||
{
|
||||
"command": "rust-analyzer.expandMacro",
|
||||
"title": "Expand macro recursively",
|
||||
|
@ -429,6 +429,14 @@ export function viewHir(ctx: Ctx): Cmd {
|
||||
};
|
||||
}
|
||||
|
||||
export function viewCrateGraph(ctx: Ctx): Cmd {
|
||||
return async () => {
|
||||
const panel = vscode.window.createWebviewPanel("rust-analyzer.crate-graph", "rust-analyzer crate graph", vscode.ViewColumn.Two);
|
||||
const svg = await ctx.client.sendRequest(ra.viewCrateGraph);
|
||||
panel.webview.html = svg;
|
||||
};
|
||||
}
|
||||
|
||||
// Opens the virtual file that will show the syntax tree
|
||||
//
|
||||
// The contents of the file come from the `TextDocumentContentProvider`
|
||||
|
@ -27,6 +27,8 @@ export const syntaxTree = new lc.RequestType<SyntaxTreeParams, string, void>("ru
|
||||
|
||||
export const viewHir = new lc.RequestType<lc.TextDocumentPositionParams, string, void>("rust-analyzer/viewHir");
|
||||
|
||||
export const viewCrateGraph = new lc.RequestType0<string, void>("rust-analyzer/viewCrateGraph");
|
||||
|
||||
export interface ExpandMacroParams {
|
||||
textDocument: lc.TextDocumentIdentifier;
|
||||
position: lc.Position;
|
||||
|
@ -106,6 +106,7 @@ async function tryActivate(context: vscode.ExtensionContext) {
|
||||
ctx.registerCommand('parentModule', commands.parentModule);
|
||||
ctx.registerCommand('syntaxTree', commands.syntaxTree);
|
||||
ctx.registerCommand('viewHir', commands.viewHir);
|
||||
ctx.registerCommand('viewCrateGraph', commands.viewCrateGraph);
|
||||
ctx.registerCommand('expandMacro', commands.expandMacro);
|
||||
ctx.registerCommand('run', commands.run);
|
||||
ctx.registerCommand('copyRunCommandLine', commands.copyRunCommandLine);
|
||||
|
Loading…
Reference in New Issue
Block a user