From 01df4c04d12be89d53addca2885221419e56bf31 Mon Sep 17 00:00:00 2001 From: Jonas Schievink Date: Fri, 21 May 2021 23:45:09 +0200 Subject: impl Display for AttrInput/ImportAlias --- crates/hir_def/src/attr.rs | 11 ++++++++++- crates/hir_def/src/path.rs | 9 +++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/crates/hir_def/src/attr.rs b/crates/hir_def/src/attr.rs index aadd4e44a..89a1ea770 100644 --- a/crates/hir_def/src/attr.rs +++ b/crates/hir_def/src/attr.rs @@ -2,7 +2,7 @@ use std::{ convert::{TryFrom, TryInto}, - ops, + fmt, ops, sync::Arc, }; @@ -648,6 +648,15 @@ pub enum AttrInput { TokenTree(Subtree), } +impl fmt::Display for AttrInput { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + AttrInput::Literal(lit) => write!(f, " = \"{}\"", lit.escape_debug()), + AttrInput::TokenTree(subtree) => subtree.fmt(f), + } + } +} + impl Attr { fn from_src( db: &dyn DefDatabase, diff --git a/crates/hir_def/src/path.rs b/crates/hir_def/src/path.rs index a43441b1c..9b8873fd2 100644 --- a/crates/hir_def/src/path.rs +++ b/crates/hir_def/src/path.rs @@ -46,6 +46,15 @@ pub enum ImportAlias { Alias(Name), } +impl Display for ImportAlias { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ImportAlias::Underscore => f.write_str("_"), + ImportAlias::Alias(name) => f.write_str(&name.to_string()), + } + } +} + impl ModPath { pub fn from_src(db: &dyn DefDatabase, path: ast::Path, hygiene: &Hygiene) -> Option { let ctx = LowerCtx::with_hygiene(db, hygiene); -- cgit v1.2.3 From 8d13864440ba8b6ede1097c79b28e4981caf714a Mon Sep 17 00:00:00 2001 From: Jonas Schievink Date: Fri, 21 May 2021 23:45:27 +0200 Subject: Add an ItemTree pretty-printer --- crates/hir_def/src/item_tree.rs | 11 + crates/hir_def/src/item_tree/pretty.rs | 525 +++++++++++++++++++++++++++++++++ crates/hir_def/src/item_tree/tests.rs | 244 +++++++++++++++ 3 files changed, 780 insertions(+) create mode 100644 crates/hir_def/src/item_tree/pretty.rs create mode 100644 crates/hir_def/src/item_tree/tests.rs diff --git a/crates/hir_def/src/item_tree.rs b/crates/hir_def/src/item_tree.rs index 7440e7d29..528270d49 100644 --- a/crates/hir_def/src/item_tree.rs +++ b/crates/hir_def/src/item_tree.rs @@ -1,6 +1,9 @@ //! A simplified AST that only contains items. mod lower; +mod pretty; +#[cfg(test)] +mod tests; use std::{ any::type_name, @@ -205,6 +208,10 @@ impl ItemTree { } } + pub fn pretty_print(&self) -> String { + pretty::print_item_tree(self) + } + fn data(&self) -> &ItemTreeData { self.data.as_ref().expect("attempted to access data of empty ItemTree") } @@ -776,6 +783,10 @@ impl IdRange { fn new(range: Range>) -> Self { Self { range: range.start.into_raw().into()..range.end.into_raw().into(), _p: PhantomData } } + + fn is_empty(&self) -> bool { + self.range.is_empty() + } } impl Iterator for IdRange { diff --git a/crates/hir_def/src/item_tree/pretty.rs b/crates/hir_def/src/item_tree/pretty.rs new file mode 100644 index 000000000..5ec02d1be --- /dev/null +++ b/crates/hir_def/src/item_tree/pretty.rs @@ -0,0 +1,525 @@ +//! `ItemTree` debug printer. + +use std::fmt::{self, Write}; + +use crate::{attr::RawAttrs, visibility::RawVisibility}; + +use super::*; + +pub(super) fn print_item_tree(tree: &ItemTree) -> String { + let mut p = Printer { tree, buf: String::new(), indent_level: 0, needs_indent: true }; + + if let Some(attrs) = tree.attrs.get(&AttrOwner::TopLevel) { + p.print_attrs(attrs, true); + } + p.blank(); + + for item in tree.top_level_items() { + p.print_mod_item(*item); + } + + let mut s = p.buf.trim_end_matches('\n').to_string(); + s.push('\n'); + s +} + +macro_rules! w { + ($dst:expr, $($arg:tt)*) => { + drop(write!($dst, $($arg)*)) + }; +} + +macro_rules! wln { + ($dst:expr) => { + drop(writeln!($dst)) + }; + ($dst:expr, $($arg:tt)*) => { + drop(writeln!($dst, $($arg)*)) + }; +} + +struct Printer<'a> { + tree: &'a ItemTree, + buf: String, + indent_level: usize, + needs_indent: bool, +} + +impl<'a> Printer<'a> { + fn indented(&mut self, f: impl FnOnce(&mut Self)) { + self.indent_level += 1; + wln!(self); + f(self); + self.indent_level -= 1; + self.buf = self.buf.trim_end_matches('\n').to_string(); + } + + /// Ensures that a blank line is output before the next text. + fn blank(&mut self) { + let mut iter = self.buf.chars().rev().fuse(); + match (iter.next(), iter.next()) { + (Some('\n'), Some('\n')) | (Some('\n'), None) | (None, None) => {} + (Some('\n'), Some(_)) => { + self.buf.push('\n'); + } + (Some(_), _) => { + self.buf.push('\n'); + self.buf.push('\n'); + } + (None, Some(_)) => unreachable!(), + } + } + + fn print_attrs(&mut self, attrs: &RawAttrs, inner: bool) { + let inner = if inner { "!" } else { "" }; + for attr in &**attrs { + wln!( + self, + "#{}[{}{}] // {:?}", + inner, + attr.path, + attr.input.as_ref().map(|it| it.to_string()).unwrap_or_default(), + attr.id, + ); + } + } + + fn print_attrs_of(&mut self, of: impl Into) { + if let Some(attrs) = self.tree.attrs.get(&of.into()) { + self.print_attrs(attrs, false); + } + } + + fn print_visibility(&mut self, vis: RawVisibilityId) { + match &self.tree[vis] { + RawVisibility::Module(path) => w!(self, "pub({}) ", path), + RawVisibility::Public => w!(self, "pub "), + }; + } + + fn print_fields(&mut self, fields: &Fields) { + match fields { + Fields::Record(fields) => { + w!(self, " {{"); + self.indented(|this| { + for field in fields.clone() { + let Field { visibility, name, type_ref } = &this.tree[field]; + this.print_attrs_of(field); + this.print_visibility(*visibility); + w!(this, "{}: ", name); + this.print_type_ref(type_ref); + wln!(this, ","); + } + }); + w!(self, "}}"); + } + Fields::Tuple(fields) => { + w!(self, "("); + self.indented(|this| { + for field in fields.clone() { + let Field { visibility, name, type_ref } = &this.tree[field]; + this.print_attrs_of(field); + this.print_visibility(*visibility); + w!(this, "{}: ", name); + this.print_type_ref(type_ref); + wln!(this, ","); + } + }); + w!(self, ")"); + } + Fields::Unit => {} + } + } + + fn print_mod_item(&mut self, item: ModItem) { + self.print_attrs_of(item); + + match item { + ModItem::Import(it) => { + let Import { visibility, path, is_glob, alias, ast_id: _, index } = &self.tree[it]; + self.print_visibility(*visibility); + w!(self, "use {}", path); + if *is_glob { + w!(self, "::*"); + } + if let Some(alias) = alias { + w!(self, " as {}", alias); + } + wln!(self, "; // {}", index); + } + ModItem::ExternCrate(it) => { + let ExternCrate { name, alias, visibility, ast_id: _ } = &self.tree[it]; + self.print_visibility(*visibility); + w!(self, "extern crate {}", name); + if let Some(alias) = alias { + w!(self, " as {}", alias); + } + wln!(self, ";"); + } + ModItem::ExternBlock(it) => { + let ExternBlock { abi, ast_id: _, children } = &self.tree[it]; + w!(self, "extern "); + if let Some(abi) = abi { + w!(self, "\"{}\" ", abi); + } + w!(self, "{{"); + self.indented(|this| { + for child in &**children { + this.print_mod_item(*child); + } + }); + wln!(self, "}}"); + } + ModItem::Function(it) => { + let Function { + name, + visibility, + generic_params: _, // FIXME print these somehow + abi, + params, + ret_type, + ast_id: _, + flags, + } = &self.tree[it]; + if flags.bits != 0 { + wln!(self, "// flags = 0x{:X}", flags.bits); + } + self.print_visibility(*visibility); + if let Some(abi) = abi { + w!(self, "extern \"{}\" ", abi); + } + w!(self, "fn {}(", name); + if !params.is_empty() { + self.indented(|this| { + for param in params.clone() { + this.print_attrs_of(param); + match &this.tree[param] { + Param::Normal(ty) => { + w!(this, "_: "); + this.print_type_ref(ty); + wln!(this, ","); + } + Param::Varargs => { + wln!(this, "..."); + } + }; + } + }); + } + w!(self, ") -> "); + self.print_type_ref(ret_type); + wln!(self, ";"); + } + ModItem::Struct(it) => { + let Struct { visibility, name, fields, generic_params: _, ast_id: _ } = + &self.tree[it]; + self.print_visibility(*visibility); + w!(self, "struct {}", name); + self.print_fields(fields); + if matches!(fields, Fields::Record(_)) { + wln!(self); + } else { + wln!(self, ";"); + } + } + ModItem::Union(it) => { + let Union { name, visibility, fields, generic_params: _, ast_id: _ } = + &self.tree[it]; + self.print_visibility(*visibility); + w!(self, "union {}", name); + self.print_fields(fields); + if matches!(fields, Fields::Record(_)) { + wln!(self); + } else { + wln!(self, ";"); + } + } + ModItem::Enum(it) => { + let Enum { name, visibility, variants, generic_params: _, ast_id: _ } = + &self.tree[it]; + self.print_visibility(*visibility); + w!(self, "enum {} {{", name); + self.indented(|this| { + for variant in variants.clone() { + let Variant { name, fields } = &this.tree[variant]; + this.print_attrs_of(variant); + w!(this, "{}", name); + this.print_fields(fields); + wln!(this, ","); + } + }); + wln!(self, "}}"); + } + ModItem::Const(it) => { + let Const { name, visibility, type_ref, ast_id: _ } = &self.tree[it]; + self.print_visibility(*visibility); + w!(self, "const "); + match name { + Some(name) => w!(self, "{}", name), + None => w!(self, "_"), + } + w!(self, ": "); + self.print_type_ref(type_ref); + wln!(self, " = _;"); + } + ModItem::Static(it) => { + let Static { name, visibility, mutable, is_extern, type_ref, ast_id: _ } = + &self.tree[it]; + self.print_visibility(*visibility); + w!(self, "static "); + if *mutable { + w!(self, "mut "); + } + w!(self, "{}: ", name); + self.print_type_ref(type_ref); + w!(self, " = _;"); + if *is_extern { + w!(self, " // extern"); + } + wln!(self); + } + ModItem::Trait(it) => { + let Trait { + name, + visibility, + is_auto, + is_unsafe, + bounds, + items, + generic_params: _, + ast_id: _, + } = &self.tree[it]; + self.print_visibility(*visibility); + if *is_unsafe { + w!(self, "unsafe "); + } + if *is_auto { + w!(self, "auto "); + } + w!(self, "trait {}", name); + if !bounds.is_empty() { + w!(self, ": "); + self.print_type_bounds(bounds); + } + w!(self, " {{"); + self.indented(|this| { + for item in &**items { + this.print_mod_item((*item).into()); + } + }); + wln!(self, "}}"); + } + ModItem::Impl(it) => { + let Impl { + target_trait, + self_ty, + is_negative, + items, + generic_params: _, + ast_id: _, + } = &self.tree[it]; + w!(self, "impl "); + if *is_negative { + w!(self, "!"); + } + if let Some(tr) = target_trait { + self.print_path(&tr.path); + w!(self, " for "); + } + self.print_type_ref(self_ty); + w!(self, " {{"); + self.indented(|this| { + for item in &**items { + this.print_mod_item((*item).into()); + } + }); + wln!(self, "}}"); + } + ModItem::TypeAlias(it) => { + let TypeAlias { + name, + visibility, + bounds, + type_ref, + is_extern, + generic_params: _, + ast_id: _, + } = &self.tree[it]; + self.print_visibility(*visibility); + w!(self, "type {}", name); + if !bounds.is_empty() { + w!(self, ": "); + self.print_type_bounds(bounds); + } + if let Some(ty) = type_ref { + w!(self, " = "); + self.print_type_ref(ty); + } + w!(self, ";"); + if *is_extern { + w!(self, " // extern"); + } + wln!(self); + } + ModItem::Mod(it) => { + let Mod { name, visibility, kind, ast_id: _ } = &self.tree[it]; + self.print_visibility(*visibility); + w!(self, "mod {}", name); + match kind { + ModKind::Inline { items } => { + w!(self, " {{"); + self.indented(|this| { + for item in &**items { + this.print_mod_item((*item).into()); + } + }); + wln!(self, "}}"); + } + ModKind::Outline {} => { + wln!(self, ";"); + } + } + } + ModItem::MacroCall(it) => { + let MacroCall { path, ast_id: _, fragment: _ } = &self.tree[it]; + wln!(self, "{}!(...);", path); + } + ModItem::MacroRules(it) => { + let MacroRules { name, ast_id: _ } = &self.tree[it]; + wln!(self, "macro_rules! {} {{ ... }}", name); + } + ModItem::MacroDef(it) => { + let MacroDef { name, visibility, ast_id: _ } = &self.tree[it]; + self.print_visibility(*visibility); + wln!(self, "macro {} {{ ... }}", name); + } + } + + self.blank(); + } + + fn print_type_ref(&mut self, type_ref: &TypeRef) { + // FIXME: deduplicate with `HirDisplay` impl + match type_ref { + TypeRef::Never => w!(self, "!"), + TypeRef::Placeholder => w!(self, "_"), + TypeRef::Tuple(fields) => { + w!(self, "("); + for (i, field) in fields.iter().enumerate() { + if i != 0 { + w!(self, ", "); + } + self.print_type_ref(field); + } + w!(self, ")"); + } + TypeRef::Path(path) => self.print_path(path), + TypeRef::RawPtr(pointee, mtbl) => { + let mtbl = match mtbl { + Mutability::Shared => "*const", + Mutability::Mut => "*mut", + }; + w!(self, "{} ", mtbl); + self.print_type_ref(pointee); + } + TypeRef::Reference(pointee, lt, mtbl) => { + let mtbl = match mtbl { + Mutability::Shared => "", + Mutability::Mut => "mut ", + }; + w!(self, "&"); + if let Some(lt) = lt { + w!(self, "{} ", lt.name); + } + w!(self, "{}", mtbl); + self.print_type_ref(pointee); + } + TypeRef::Array(elem, len) => { + w!(self, "["); + self.print_type_ref(elem); + w!(self, "; {}]", len); + } + TypeRef::Slice(elem) => { + w!(self, "["); + self.print_type_ref(elem); + w!(self, "]"); + } + TypeRef::Fn(args_and_ret, varargs) => { + let (ret, args) = + args_and_ret.split_last().expect("TypeRef::Fn is missing return type"); + w!(self, "fn("); + for (i, arg) in args.iter().enumerate() { + if i != 0 { + w!(self, ", "); + } + self.print_type_ref(arg); + } + if *varargs { + if !args.is_empty() { + w!(self, ", "); + } + w!(self, "..."); + } + w!(self, ") -> "); + self.print_type_ref(ret); + } + TypeRef::Macro(_ast_id) => { + w!(self, ""); + } + TypeRef::Error => drop(write!(self, "{{unknown}}")), + TypeRef::ImplTrait(bounds) => { + w!(self, "impl "); + self.print_type_bounds(bounds); + } + TypeRef::DynTrait(bounds) => { + w!(self, "dyn "); + self.print_type_bounds(bounds); + } + } + } + + fn print_type_bounds(&mut self, bounds: &[TypeBound]) { + for (i, bound) in bounds.iter().enumerate() { + if i != 0 { + w!(self, " + "); + } + + match bound { + TypeBound::Path(path) => self.print_path(path), + TypeBound::Lifetime(lt) => w!(self, "{}", lt.name), + TypeBound::Error => w!(self, "{{unknown}}"), + } + } + } + + fn print_path(&mut self, path: &Path) { + if path.type_anchor().is_none() + && path.segments().iter().all(|seg| seg.args_and_bindings.is_none()) + { + w!(self, "{}", path.mod_path()); + } else { + // too complicated, just use `Debug` + w!(self, "{:?}", path); + } + } +} + +impl<'a> Write for Printer<'a> { + fn write_str(&mut self, s: &str) -> fmt::Result { + for line in s.split_inclusive('\n') { + if self.needs_indent { + match self.buf.chars().last() { + Some('\n') | None => {} + _ => self.buf.push('\n'), + } + self.buf.push_str(&" ".repeat(self.indent_level)); + self.needs_indent = false; + } + + self.buf.push_str(line); + self.needs_indent = line.ends_with('\n'); + } + + Ok(()) + } +} diff --git a/crates/hir_def/src/item_tree/tests.rs b/crates/hir_def/src/item_tree/tests.rs new file mode 100644 index 000000000..100ae9b97 --- /dev/null +++ b/crates/hir_def/src/item_tree/tests.rs @@ -0,0 +1,244 @@ +use base_db::fixture::WithFixture; +use expect_test::{expect, Expect}; + +use crate::{db::DefDatabase, test_db::TestDB}; + +fn check(ra_fixture: &str, expect: Expect) { + let (db, file_id) = TestDB::with_single_file(ra_fixture); + let item_tree = db.file_item_tree(file_id.into()); + let pretty = item_tree.pretty_print(); + expect.assert_eq(&pretty); +} + +#[test] +fn imports() { + check( + r#" +//! file comment +#![no_std] +//! another file comment + +extern crate self as renamed; +pub(super) extern crate bli; + +pub use crate::path::{nested, items as renamed, Trait as _}; +use globs::*; + +/// docs on import +use crate::{A, B}; + "#, + expect![[r##" + #![doc = " file comment"] // AttrId { is_doc_comment: true, ast_index: 0 } + #![no_std] // AttrId { is_doc_comment: false, ast_index: 0 } + #![doc = " another file comment"] // AttrId { is_doc_comment: true, ast_index: 1 } + + pub(self) extern crate self as renamed; + + pub(super) extern crate bli; + + pub use crate::path::nested; // 0 + + pub use crate::path::items as renamed; // 1 + + pub use crate::path::Trait as _; // 2 + + pub(self) use globs::*; // 0 + + #[doc = " docs on import"] // AttrId { is_doc_comment: true, ast_index: 0 } + pub(self) use crate::A; // 0 + + #[doc = " docs on import"] // AttrId { is_doc_comment: true, ast_index: 0 } + pub(self) use crate::B; // 1 + "##]], + ); +} + +#[test] +fn extern_blocks() { + check( + r#" +#[on_extern_block] +extern "C" { + #[on_extern_type] + type ExType; + + #[on_extern_static] + static EX_STATIC: u8; + + #[on_extern_fn] + fn ex_fn(); +} + "#, + expect![[r##" + #[on_extern_block] // AttrId { is_doc_comment: false, ast_index: 0 } + extern "C" { + #[on_extern_type] // AttrId { is_doc_comment: false, ast_index: 0 } + pub(self) type ExType; // extern + + #[on_extern_static] // AttrId { is_doc_comment: false, ast_index: 0 } + pub(self) static EX_STATIC: u8 = _; // extern + + #[on_extern_fn] // AttrId { is_doc_comment: false, ast_index: 0 } + // flags = 0x60 + pub(self) fn ex_fn() -> (); + } + "##]], + ); +} + +#[test] +fn adts() { + check( + r#" +struct Unit; + +#[derive(Debug)] +struct Struct { + /// fld docs + fld: (), +} + +struct Tuple(#[attr] u8); + +union Ize { + a: (), + b: (), +} + +enum E { + /// comment on Unit + Unit, + /// comment on Tuple + Tuple(u8), + Struct { + /// comment on a: u8 + a: u8, + } +} + "#, + expect![[r##" + pub(self) struct Unit; + + #[derive(Debug)] // AttrId { is_doc_comment: false, ast_index: 0 } + pub(self) struct Struct { + #[doc = " fld docs"] // AttrId { is_doc_comment: true, ast_index: 0 } + pub(self) fld: (), + } + + pub(self) struct Tuple( + #[attr] // AttrId { is_doc_comment: false, ast_index: 0 } + pub(self) 0: u8, + ); + + pub(self) union Ize { + pub(self) a: (), + pub(self) b: (), + } + + pub(self) enum E { + #[doc = " comment on Unit"] // AttrId { is_doc_comment: true, ast_index: 0 } + Unit, + #[doc = " comment on Tuple"] // AttrId { is_doc_comment: true, ast_index: 0 } + Tuple( + pub(self) 0: u8, + ), + Struct { + #[doc = " comment on a: u8"] // AttrId { is_doc_comment: true, ast_index: 0 } + pub(self) a: u8, + }, + } + "##]], + ); +} + +#[test] +fn misc() { + check( + r#" +pub static mut ST: () = (); + +const _: Anon = (); + +#[attr] +fn f(#[attr] arg: u8, _: ()) { + #![inner_attr_in_fn] +} + +trait Tr: SuperTrait + 'lifetime { + type Assoc: AssocBound = Default; + fn method(&self); +} + "#, + expect![[r##" + pub static mut ST: () = _; + + pub(self) const _: Anon = _; + + #[attr] // AttrId { is_doc_comment: false, ast_index: 0 } + #[inner_attr_in_fn] // AttrId { is_doc_comment: false, ast_index: 1 } + // flags = 0x2 + pub(self) fn f( + #[attr] // AttrId { is_doc_comment: false, ast_index: 0 } + _: u8, + _: (), + ) -> (); + + pub(self) trait Tr: SuperTrait + 'lifetime { + pub(self) type Assoc: AssocBound = Default; + + // flags = 0x1 + pub(self) fn method( + _: &Self, + ) -> (); + } + "##]], + ); +} + +#[test] +fn modules() { + check( + r#" +/// outer +mod inline { + //! inner + + use super::*; + + fn fn_in_module() {} +} + "#, + expect![[r##" + #[doc = " outer"] // AttrId { is_doc_comment: true, ast_index: 0 } + #[doc = " inner"] // AttrId { is_doc_comment: true, ast_index: 1 } + pub(self) mod inline { + pub(self) use super::*; // 0 + + // flags = 0x2 + pub(self) fn fn_in_module() -> (); + } + "##]], + ); +} + +#[test] +fn macros() { + check( + r#" +macro_rules! m { + () => {}; +} + +pub macro m2() {} + +m!(); + "#, + expect![[r#" + macro_rules! m { ... } + + pub macro m2 { ... } + + m!(...); + "#]], + ); +} -- cgit v1.2.3 From 271ec6b990523c79f93468a5b0ab5e1aceab50f6 Mon Sep 17 00:00:00 2001 From: Jonas Schievink Date: Fri, 21 May 2021 23:59:52 +0200 Subject: 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 f4b90db3a..ff2a54117 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 { + 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> { 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 000000000..3dc03085d --- /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 51041d7a0..aa12fd94b 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 { + let _p = profile::span("handle_view_item_tree"); + let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let res = snap.analysis.view_item_tree(file_id)?; + Ok(res) +} + pub(crate) fn handle_view_crate_graph(snap: GlobalStateSnapshot, (): ()) -> Result { 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 34b53a7a8..905048793 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 4e0791611..f837b89dd 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -514,6 +514,7 @@ impl GlobalState { .on::(handlers::handle_syntax_tree) .on::(handlers::handle_view_hir) .on::(handlers::handle_view_crate_graph) + .on::(handlers::handle_view_item_tree) .on::(handlers::handle_expand_macro) .on::(handlers::handle_parent_module) .on::(handlers::handle_runnables) diff --git a/editors/code/package.json b/editors/code/package.json index 1743b374c..17d9281ff 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 8ab259af2..8f672e68d 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(); + 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 { + 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 { + 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 aa745a65c..6d5c2ea72 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("ru export const viewHir = new lc.RequestType("rust-analyzer/viewHir"); +export interface ViewItemTreeParams { + textDocument: lc.TextDocumentIdentifier; +} + +export const viewItemTree = new lc.RequestType("rust-analyzer/viewItemTree"); + export const viewCrateGraph = new lc.RequestType0("rust-analyzer/viewCrateGraph"); export interface ExpandMacroParams { diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index 516322d03..92c797d47 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); -- cgit v1.2.3 From 3360053312af5063008d3d0e283c8a025ba7b10d Mon Sep 17 00:00:00 2001 From: Jonas Schievink Date: Sat, 22 May 2021 00:03:36 +0200 Subject: Update lsp-extensions.md --- docs/dev/lsp-extensions.md | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md index 3c4eacfeb..fbe2ce1c9 100644 --- a/docs/dev/lsp-extensions.md +++ b/docs/dev/lsp-extensions.md @@ -1,5 +1,5 @@