From 271ec6b990523c79f93468a5b0ab5e1aceab50f6 Mon Sep 17 00:00:00 2001
From: Jonas Schievink <jonasschievink@gmail.com>
Date: Fri, 21 May 2021 23:59:52 +0200
Subject: [PATCH] Add a "Debug ItemTree" LSP request

---
 crates/ide/src/lib.rs                 |  5 +++
 crates/ide/src/view_item_tree.rs      | 16 +++++++++
 crates/rust-analyzer/src/handlers.rs  | 10 ++++++
 crates/rust-analyzer/src/lsp_ext.rs   | 14 ++++++++
 crates/rust-analyzer/src/main_loop.rs |  1 +
 editors/code/package.json             |  5 +++
 editors/code/src/commands.ts          | 50 +++++++++++++++++++++++++++
 editors/code/src/lsp_ext.ts           |  6 ++++
 editors/code/src/main.ts              |  1 +
 9 files changed, 108 insertions(+)
 create mode 100644 crates/ide/src/view_item_tree.rs

diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index f4b90db3a02..ff2a54117bb 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -50,6 +50,7 @@ mod typing;
 mod markdown_remove;
 mod doc_links;
 mod view_crate_graph;
+mod view_item_tree;
 
 use std::sync::Arc;
 
@@ -288,6 +289,10 @@ impl Analysis {
         self.with_db(|db| view_hir::view_hir(&db, position))
     }
 
+    pub fn view_item_tree(&self, file_id: FileId) -> Cancelable<String> {
+        self.with_db(|db| view_item_tree::view_item_tree(&db, file_id))
+    }
+
     /// Renders the crate graph to GraphViz "dot" syntax.
     pub fn view_crate_graph(&self) -> Cancelable<Result<String, String>> {
         self.with_db(|db| view_crate_graph::view_crate_graph(&db))
diff --git a/crates/ide/src/view_item_tree.rs b/crates/ide/src/view_item_tree.rs
new file mode 100644
index 00000000000..3dc03085d65
--- /dev/null
+++ b/crates/ide/src/view_item_tree.rs
@@ -0,0 +1,16 @@
+use hir::db::DefDatabase;
+use ide_db::base_db::FileId;
+use ide_db::RootDatabase;
+
+// Feature: Debug ItemTree
+//
+// Displays the ItemTree of the currently open file, for debugging.
+//
+// |===
+// | Editor  | Action Name
+//
+// | VS Code | **Rust Analyzer: Debug ItemTree**
+// |===
+pub(crate) fn view_item_tree(db: &RootDatabase, file_id: FileId) -> String {
+    db.file_item_tree(file_id.into()).pretty_print()
+}
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index 51041d7a0c3..aa12fd94bd9 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -117,6 +117,16 @@ pub(crate) fn handle_view_hir(
     Ok(res)
 }
 
+pub(crate) fn handle_view_item_tree(
+    snap: GlobalStateSnapshot,
+    params: lsp_ext::ViewItemTreeParams,
+) -> Result<String> {
+    let _p = profile::span("handle_view_item_tree");
+    let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
+    let res = snap.analysis.view_item_tree(file_id)?;
+    Ok(res)
+}
+
 pub(crate) fn handle_view_crate_graph(snap: GlobalStateSnapshot, (): ()) -> Result<String> {
     let _p = profile::span("handle_view_crate_graph");
     let dot = snap.analysis.view_crate_graph()??;
diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs
index 34b53a7a808..90504879304 100644
--- a/crates/rust-analyzer/src/lsp_ext.rs
+++ b/crates/rust-analyzer/src/lsp_ext.rs
@@ -70,6 +70,20 @@ impl Request for ViewCrateGraph {
     const METHOD: &'static str = "rust-analyzer/viewCrateGraph";
 }
 
+#[derive(Deserialize, Serialize, Debug)]
+#[serde(rename_all = "camelCase")]
+pub struct ViewItemTreeParams {
+    pub text_document: TextDocumentIdentifier,
+}
+
+pub enum ViewItemTree {}
+
+impl Request for ViewItemTree {
+    type Params = ViewItemTreeParams;
+    type Result = String;
+    const METHOD: &'static str = "rust-analyzer/viewItemTree";
+}
+
 pub enum ExpandMacro {}
 
 impl Request for ExpandMacro {
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index 4e07916113d..f837b89ddc9 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -514,6 +514,7 @@ impl GlobalState {
             .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::ViewItemTree>(handlers::handle_view_item_tree)
             .on::<lsp_ext::ExpandMacro>(handlers::handle_expand_macro)
             .on::<lsp_ext::ParentModule>(handlers::handle_parent_module)
             .on::<lsp_ext::Runnables>(handlers::handle_runnables)
diff --git a/editors/code/package.json b/editors/code/package.json
index 1743b374c04..17d9281ff74 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -109,6 +109,11 @@
                 "title": "View Hir",
                 "category": "Rust Analyzer"
             },
+            {
+                "command": "rust-analyzer.viewItemTree",
+                "title": "Debug ItemTree",
+                "category": "Rust Analyzer"
+            },
             {
                 "command": "rust-analyzer.viewCrateGraph",
                 "title": "View Crate Graph",
diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts
index 8ab259af221..8f672e68db5 100644
--- a/editors/code/src/commands.ts
+++ b/editors/code/src/commands.ts
@@ -429,6 +429,56 @@ export function viewHir(ctx: Ctx): Cmd {
     };
 }
 
+export function viewItemTree(ctx: Ctx): Cmd {
+    const tdcp = new class implements vscode.TextDocumentContentProvider {
+        readonly uri = vscode.Uri.parse('rust-analyzer://viewItemTree/itemtree.txt');
+        readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>();
+        constructor() {
+            vscode.workspace.onDidChangeTextDocument(this.onDidChangeTextDocument, this, ctx.subscriptions);
+            vscode.window.onDidChangeActiveTextEditor(this.onDidChangeActiveTextEditor, this, ctx.subscriptions);
+        }
+
+        private onDidChangeTextDocument(event: vscode.TextDocumentChangeEvent) {
+            if (isRustDocument(event.document)) {
+                // We need to order this after language server updates, but there's no API for that.
+                // Hence, good old sleep().
+                void sleep(10).then(() => this.eventEmitter.fire(this.uri));
+            }
+        }
+        private onDidChangeActiveTextEditor(editor: vscode.TextEditor | undefined) {
+            if (editor && isRustEditor(editor)) {
+                this.eventEmitter.fire(this.uri);
+            }
+        }
+
+        provideTextDocumentContent(_uri: vscode.Uri, ct: vscode.CancellationToken): vscode.ProviderResult<string> {
+            const rustEditor = ctx.activeRustEditor;
+            const client = ctx.client;
+            if (!rustEditor || !client) return '';
+
+            const params = {
+                textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(rustEditor.document),
+            };
+            return client.sendRequest(ra.viewItemTree, params, ct);
+        }
+
+        get onDidChange(): vscode.Event<vscode.Uri> {
+            return this.eventEmitter.event;
+        }
+    };
+
+    ctx.pushCleanup(vscode.workspace.registerTextDocumentContentProvider('rust-analyzer', tdcp));
+
+    return async () => {
+        const document = await vscode.workspace.openTextDocument(tdcp.uri);
+        tdcp.eventEmitter.fire(tdcp.uri);
+        void await vscode.window.showTextDocument(document, {
+            viewColumn: vscode.ViewColumn.Two,
+            preserveFocus: true
+        });
+    };
+}
+
 export function viewCrateGraph(ctx: Ctx): Cmd {
     return async () => {
         const panel = vscode.window.createWebviewPanel("rust-analyzer.crate-graph", "rust-analyzer crate graph", vscode.ViewColumn.Two);
diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts
index aa745a65ce6..6d5c2ea72dc 100644
--- a/editors/code/src/lsp_ext.ts
+++ b/editors/code/src/lsp_ext.ts
@@ -27,6 +27,12 @@ export const syntaxTree = new lc.RequestType<SyntaxTreeParams, string, void>("ru
 
 export const viewHir = new lc.RequestType<lc.TextDocumentPositionParams, string, void>("rust-analyzer/viewHir");
 
+export interface ViewItemTreeParams {
+    textDocument: lc.TextDocumentIdentifier;
+}
+
+export const viewItemTree = new lc.RequestType<ViewItemTreeParams, string, void>("rust-analyzer/viewItemTree");
+
 export const viewCrateGraph = new lc.RequestType0<string, void>("rust-analyzer/viewCrateGraph");
 
 export interface ExpandMacroParams {
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts
index 516322d035d..92c797d47fa 100644
--- a/editors/code/src/main.ts
+++ b/editors/code/src/main.ts
@@ -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('viewItemTree', commands.viewItemTree);
     ctx.registerCommand('viewCrateGraph', commands.viewCrateGraph);
     ctx.registerCommand('expandMacro', commands.expandMacro);
     ctx.registerCommand('run', commands.run);