diff options
-rw-r--r-- | crates/hir/src/attrs.rs | 5 | ||||
-rw-r--r-- | crates/hir/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/hir/src/semantics.rs | 1 | ||||
-rw-r--r-- | crates/hir/src/semantics/source_to_def.rs | 6 | ||||
-rw-r--r-- | crates/hir_def/src/attr.rs | 45 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting.rs | 2 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting/inject.rs | 135 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html | 18 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting/tests.rs | 16 | ||||
-rw-r--r-- | crates/syntax/src/ast/traits.rs | 2 |
10 files changed, 205 insertions, 27 deletions
diff --git a/crates/hir/src/attrs.rs b/crates/hir/src/attrs.rs index b9c695921..dab8da7bb 100644 --- a/crates/hir/src/attrs.rs +++ b/crates/hir/src/attrs.rs | |||
@@ -11,8 +11,8 @@ use hir_ty::db::HirDatabase; | |||
11 | use syntax::ast; | 11 | use syntax::ast; |
12 | 12 | ||
13 | use crate::{ | 13 | use crate::{ |
14 | Adt, Const, ConstParam, Enum, Field, Function, GenericParam, LifetimeParam, MacroDef, Module, | 14 | Adt, Const, ConstParam, Enum, Field, Function, GenericParam, Impl, LifetimeParam, MacroDef, |
15 | ModuleDef, Static, Struct, Trait, TypeAlias, TypeParam, Union, Variant, | 15 | Module, ModuleDef, Static, Struct, Trait, TypeAlias, TypeParam, Union, Variant, |
16 | }; | 16 | }; |
17 | 17 | ||
18 | pub trait HasAttrs { | 18 | pub trait HasAttrs { |
@@ -64,6 +64,7 @@ impl_has_attrs![ | |||
64 | (Adt, AdtId), | 64 | (Adt, AdtId), |
65 | (Module, ModuleId), | 65 | (Module, ModuleId), |
66 | (GenericParam, GenericParamId), | 66 | (GenericParam, GenericParamId), |
67 | (Impl, ImplId), | ||
67 | ]; | 68 | ]; |
68 | 69 | ||
69 | macro_rules! impl_has_attrs_enum { | 70 | macro_rules! impl_has_attrs_enum { |
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 861b7329e..b41a36a78 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs | |||
@@ -89,7 +89,7 @@ pub use crate::{ | |||
89 | pub use { | 89 | pub use { |
90 | hir_def::{ | 90 | hir_def::{ |
91 | adt::StructKind, | 91 | adt::StructKind, |
92 | attr::{Attrs, Documentation}, | 92 | attr::{Attr, Attrs, Documentation}, |
93 | body::scope::ExprScopes, | 93 | body::scope::ExprScopes, |
94 | find_path::PrefixKind, | 94 | find_path::PrefixKind, |
95 | import_map, | 95 | import_map, |
diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index 00b076175..e0eb2a66d 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs | |||
@@ -752,6 +752,7 @@ macro_rules! to_def_impls { | |||
752 | 752 | ||
753 | to_def_impls![ | 753 | to_def_impls![ |
754 | (crate::Module, ast::Module, module_to_def), | 754 | (crate::Module, ast::Module, module_to_def), |
755 | (crate::Module, ast::SourceFile, source_file_to_def), | ||
755 | (crate::Struct, ast::Struct, struct_to_def), | 756 | (crate::Struct, ast::Struct, struct_to_def), |
756 | (crate::Enum, ast::Enum, enum_to_def), | 757 | (crate::Enum, ast::Enum, enum_to_def), |
757 | (crate::Union, ast::Union, union_to_def), | 758 | (crate::Union, ast::Union, union_to_def), |
diff --git a/crates/hir/src/semantics/source_to_def.rs b/crates/hir/src/semantics/source_to_def.rs index e9d820140..c6ad5ecb5 100644 --- a/crates/hir/src/semantics/source_to_def.rs +++ b/crates/hir/src/semantics/source_to_def.rs | |||
@@ -71,6 +71,12 @@ impl SourceToDefCtx<'_, '_> { | |||
71 | Some(def_map.module_id(child_id)) | 71 | Some(def_map.module_id(child_id)) |
72 | } | 72 | } |
73 | 73 | ||
74 | pub(super) fn source_file_to_def(&mut self, src: InFile<ast::SourceFile>) -> Option<ModuleId> { | ||
75 | let _p = profile::span("source_file_to_def"); | ||
76 | let file_id = src.file_id.original_file(self.db.upcast()); | ||
77 | self.file_to_def(file_id).get(0).copied() | ||
78 | } | ||
79 | |||
74 | pub(super) fn trait_to_def(&mut self, src: InFile<ast::Trait>) -> Option<TraitId> { | 80 | pub(super) fn trait_to_def(&mut self, src: InFile<ast::Trait>) -> Option<TraitId> { |
75 | self.to_def(src, keys::TRAIT) | 81 | self.to_def(src, keys::TRAIT) |
76 | } | 82 | } |
diff --git a/crates/hir_def/src/attr.rs b/crates/hir_def/src/attr.rs index b0b4b5052..7ba53ee5c 100644 --- a/crates/hir_def/src/attr.rs +++ b/crates/hir_def/src/attr.rs | |||
@@ -136,16 +136,15 @@ impl RawAttrs { | |||
136 | let new_attrs = self | 136 | let new_attrs = self |
137 | .iter() | 137 | .iter() |
138 | .flat_map(|attr| -> SmallVec<[_; 1]> { | 138 | .flat_map(|attr| -> SmallVec<[_; 1]> { |
139 | let attr = attr.clone(); | ||
140 | let is_cfg_attr = | 139 | let is_cfg_attr = |
141 | attr.path.as_ident().map_or(false, |name| *name == hir_expand::name![cfg_attr]); | 140 | attr.path.as_ident().map_or(false, |name| *name == hir_expand::name![cfg_attr]); |
142 | if !is_cfg_attr { | 141 | if !is_cfg_attr { |
143 | return smallvec![attr]; | 142 | return smallvec![attr.clone()]; |
144 | } | 143 | } |
145 | 144 | ||
146 | let subtree = match &attr.input { | 145 | let subtree = match &attr.input { |
147 | Some(AttrInput::TokenTree(it)) => it, | 146 | Some(AttrInput::TokenTree(it)) => it, |
148 | _ => return smallvec![attr], | 147 | _ => return smallvec![attr.clone()], |
149 | }; | 148 | }; |
150 | 149 | ||
151 | // Input subtree is: `(cfg, $(attr),+)` | 150 | // Input subtree is: `(cfg, $(attr),+)` |
@@ -157,11 +156,13 @@ impl RawAttrs { | |||
157 | let cfg = parts.next().unwrap(); | 156 | let cfg = parts.next().unwrap(); |
158 | let cfg = Subtree { delimiter: subtree.delimiter, token_trees: cfg.to_vec() }; | 157 | let cfg = Subtree { delimiter: subtree.delimiter, token_trees: cfg.to_vec() }; |
159 | let cfg = CfgExpr::parse(&cfg); | 158 | let cfg = CfgExpr::parse(&cfg); |
159 | let index = attr.index; | ||
160 | let attrs = parts.filter(|a| !a.is_empty()).filter_map(|attr| { | 160 | let attrs = parts.filter(|a| !a.is_empty()).filter_map(|attr| { |
161 | let tree = Subtree { delimiter: None, token_trees: attr.to_vec() }; | 161 | let tree = Subtree { delimiter: None, token_trees: attr.to_vec() }; |
162 | let attr = ast::Attr::parse(&format!("#[{}]", tree)).ok()?; | 162 | let attr = ast::Attr::parse(&format!("#[{}]", tree)).ok()?; |
163 | let hygiene = Hygiene::new_unhygienic(); // FIXME | 163 | // FIXME hygiene |
164 | Attr::from_src(attr, &hygiene) | 164 | let hygiene = Hygiene::new_unhygienic(); |
165 | Attr::from_src(attr, &hygiene).map(|attr| Attr { index, ..attr }) | ||
165 | }); | 166 | }); |
166 | 167 | ||
167 | let cfg_options = &crate_graph[krate].cfg_options; | 168 | let cfg_options = &crate_graph[krate].cfg_options; |
@@ -293,6 +294,13 @@ impl Attrs { | |||
293 | Arc::new(res) | 294 | Arc::new(res) |
294 | } | 295 | } |
295 | 296 | ||
297 | /// Constructs a map that maps the lowered `Attr`s in this `Attrs` back to its original syntax nodes. | ||
298 | /// | ||
299 | /// `owner` must be the original owner of the attributes. | ||
300 | pub fn source_map(&self, owner: &dyn AttrsOwner) -> AttrSourceMap { | ||
301 | AttrSourceMap { attrs: collect_attrs(owner).collect() } | ||
302 | } | ||
303 | |||
296 | pub fn by_key(&self, key: &'static str) -> AttrQuery<'_> { | 304 | pub fn by_key(&self, key: &'static str) -> AttrQuery<'_> { |
297 | AttrQuery { attrs: self, key } | 305 | AttrQuery { attrs: self, key } |
298 | } | 306 | } |
@@ -365,6 +373,24 @@ fn inner_attributes( | |||
365 | Some((attrs, docs)) | 373 | Some((attrs, docs)) |
366 | } | 374 | } |
367 | 375 | ||
376 | pub struct AttrSourceMap { | ||
377 | attrs: Vec<Either<ast::Attr, ast::Comment>>, | ||
378 | } | ||
379 | |||
380 | impl AttrSourceMap { | ||
381 | /// Maps the lowered `Attr` back to its original syntax node. | ||
382 | /// | ||
383 | /// `attr` must come from the `owner` used for AttrSourceMap | ||
384 | /// | ||
385 | /// Note that the returned syntax node might be a `#[cfg_attr]`, or a doc comment, instead of | ||
386 | /// the attribute represented by `Attr`. | ||
387 | pub fn source_of(&self, attr: &Attr) -> &Either<ast::Attr, ast::Comment> { | ||
388 | self.attrs | ||
389 | .get(attr.index as usize) | ||
390 | .unwrap_or_else(|| panic!("cannot find `Attr` at index {}", attr.index)) | ||
391 | } | ||
392 | } | ||
393 | |||
368 | #[derive(Debug, Clone, PartialEq, Eq)] | 394 | #[derive(Debug, Clone, PartialEq, Eq)] |
369 | pub struct Attr { | 395 | pub struct Attr { |
370 | index: u32, | 396 | index: u32, |
@@ -448,6 +474,13 @@ impl Attr { | |||
448 | _ => None, | 474 | _ => None, |
449 | } | 475 | } |
450 | } | 476 | } |
477 | |||
478 | pub fn string_value(&self) -> Option<&SmolStr> { | ||
479 | match self.input.as_ref()? { | ||
480 | AttrInput::Literal(it) => Some(it), | ||
481 | _ => None, | ||
482 | } | ||
483 | } | ||
451 | } | 484 | } |
452 | 485 | ||
453 | #[derive(Debug, Clone, Copy)] | 486 | #[derive(Debug, Clone, Copy)] |
@@ -475,7 +508,7 @@ impl<'a> AttrQuery<'a> { | |||
475 | self.attrs().next().is_some() | 508 | self.attrs().next().is_some() |
476 | } | 509 | } |
477 | 510 | ||
478 | pub(crate) fn attrs(self) -> impl Iterator<Item = &'a Attr> { | 511 | pub fn attrs(self) -> impl Iterator<Item = &'a Attr> { |
479 | let key = self.key; | 512 | let key = self.key; |
480 | self.attrs | 513 | self.attrs |
481 | .iter() | 514 | .iter() |
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs index 870146d24..ba3447b3a 100644 --- a/crates/ide/src/syntax_highlighting.rs +++ b/crates/ide/src/syntax_highlighting.rs | |||
@@ -150,7 +150,7 @@ fn traverse( | |||
150 | WalkEvent::Enter(it) => it, | 150 | WalkEvent::Enter(it) => it, |
151 | WalkEvent::Leave(it) => { | 151 | WalkEvent::Leave(it) => { |
152 | if let Some(node) = it.as_node() { | 152 | if let Some(node) = it.as_node() { |
153 | inject::doc_comment(hl, node); | 153 | inject::doc_comment(hl, sema, node); |
154 | } | 154 | } |
155 | continue; | 155 | continue; |
156 | } | 156 | } |
diff --git a/crates/ide/src/syntax_highlighting/inject.rs b/crates/ide/src/syntax_highlighting/inject.rs index 4f825523c..d57ce4027 100644 --- a/crates/ide/src/syntax_highlighting/inject.rs +++ b/crates/ide/src/syntax_highlighting/inject.rs | |||
@@ -1,8 +1,12 @@ | |||
1 | //! "Recursive" Syntax highlighting for code in doctests and fixtures. | 1 | //! "Recursive" Syntax highlighting for code in doctests and fixtures. |
2 | 2 | ||
3 | use hir::Semantics; | 3 | use either::Either; |
4 | use hir::{HasAttrs, Semantics}; | ||
4 | use ide_db::call_info::ActiveParameter; | 5 | use ide_db::call_info::ActiveParameter; |
5 | use syntax::{ast, AstToken, SyntaxNode, SyntaxToken, TextRange, TextSize}; | 6 | use syntax::{ |
7 | ast::{self, AstNode, AttrsOwner, DocCommentsOwner}, | ||
8 | match_ast, AstToken, NodeOrToken, SyntaxNode, SyntaxToken, TextRange, TextSize, | ||
9 | }; | ||
6 | 10 | ||
7 | use crate::{Analysis, HlMod, HlRange, HlTag, RootDatabase}; | 11 | use crate::{Analysis, HlMod, HlRange, HlTag, RootDatabase}; |
8 | 12 | ||
@@ -81,16 +85,75 @@ const RUSTDOC_FENCE_TOKENS: &[&'static str] = &[ | |||
81 | "edition2021", | 85 | "edition2021", |
82 | ]; | 86 | ]; |
83 | 87 | ||
88 | // Basically an owned dyn AttrsOwner without extra Boxing | ||
89 | struct AttrsOwnerNode { | ||
90 | node: SyntaxNode, | ||
91 | } | ||
92 | |||
93 | impl AttrsOwnerNode { | ||
94 | fn new<N: DocCommentsOwner>(node: N) -> Self { | ||
95 | AttrsOwnerNode { node: node.syntax().clone() } | ||
96 | } | ||
97 | } | ||
98 | |||
99 | impl AttrsOwner for AttrsOwnerNode {} | ||
100 | impl AstNode for AttrsOwnerNode { | ||
101 | fn can_cast(_: syntax::SyntaxKind) -> bool | ||
102 | where | ||
103 | Self: Sized, | ||
104 | { | ||
105 | false | ||
106 | } | ||
107 | fn cast(_: SyntaxNode) -> Option<Self> | ||
108 | where | ||
109 | Self: Sized, | ||
110 | { | ||
111 | None | ||
112 | } | ||
113 | fn syntax(&self) -> &SyntaxNode { | ||
114 | &self.node | ||
115 | } | ||
116 | } | ||
117 | |||
118 | fn doc_attributes<'node>( | ||
119 | sema: &Semantics<RootDatabase>, | ||
120 | node: &'node SyntaxNode, | ||
121 | ) -> Option<(AttrsOwnerNode, hir::Attrs)> { | ||
122 | match_ast! { | ||
123 | match node { | ||
124 | ast::SourceFile(it) => sema.to_def(&it).map(|def| (AttrsOwnerNode::new(it), def.attrs(sema.db))), | ||
125 | ast::Fn(it) => sema.to_def(&it).map(|def| (AttrsOwnerNode::new(it), def.attrs(sema.db))), | ||
126 | ast::Struct(it) => sema.to_def(&it).map(|def| (AttrsOwnerNode::new(it), def.attrs(sema.db))), | ||
127 | ast::Union(it) => sema.to_def(&it).map(|def| (AttrsOwnerNode::new(it), def.attrs(sema.db))), | ||
128 | ast::RecordField(it) => sema.to_def(&it).map(|def| (AttrsOwnerNode::new(it), def.attrs(sema.db))), | ||
129 | ast::TupleField(it) => sema.to_def(&it).map(|def| (AttrsOwnerNode::new(it), def.attrs(sema.db))), | ||
130 | ast::Enum(it) => sema.to_def(&it).map(|def| (AttrsOwnerNode::new(it), def.attrs(sema.db))), | ||
131 | ast::Variant(it) => sema.to_def(&it).map(|def| (AttrsOwnerNode::new(it), def.attrs(sema.db))), | ||
132 | ast::Trait(it) => sema.to_def(&it).map(|def| (AttrsOwnerNode::new(it), def.attrs(sema.db))), | ||
133 | ast::Module(it) => sema.to_def(&it).map(|def| (AttrsOwnerNode::new(it), def.attrs(sema.db))), | ||
134 | ast::Static(it) => sema.to_def(&it).map(|def| (AttrsOwnerNode::new(it), def.attrs(sema.db))), | ||
135 | ast::Const(it) => sema.to_def(&it).map(|def| (AttrsOwnerNode::new(it), def.attrs(sema.db))), | ||
136 | ast::TypeAlias(it) => sema.to_def(&it).map(|def| (AttrsOwnerNode::new(it), def.attrs(sema.db))), | ||
137 | ast::Impl(it) => sema.to_def(&it).map(|def| (AttrsOwnerNode::new(it), def.attrs(sema.db))), | ||
138 | ast::MacroRules(it) => sema.to_def(&it).map(|def| (AttrsOwnerNode::new(it), def.attrs(sema.db))), | ||
139 | // ast::MacroDef(it) => sema.to_def(&it).map(|def| (Box::new(it) as _, def.attrs(sema.db))), | ||
140 | // ast::Use(it) => sema.to_def(&it).map(|def| (Box::new(it) as _, def.attrs(sema.db))), | ||
141 | _ => return None | ||
142 | } | ||
143 | } | ||
144 | } | ||
145 | |||
84 | /// Injection of syntax highlighting of doctests. | 146 | /// Injection of syntax highlighting of doctests. |
85 | pub(super) fn doc_comment(hl: &mut Highlights, node: &SyntaxNode) { | 147 | pub(super) fn doc_comment(hl: &mut Highlights, sema: &Semantics<RootDatabase>, node: &SyntaxNode) { |
86 | let doc_comments = node | 148 | let (owner, attributes) = match doc_attributes(sema, node) { |
87 | .children_with_tokens() | 149 | Some(it) => it, |
88 | .filter_map(|it| it.into_token().and_then(ast::Comment::cast)) | 150 | None => return, |
89 | .filter(|it| it.kind().doc.is_some()); | 151 | }; |
90 | 152 | ||
91 | if !doc_comments.clone().any(|it| it.text().contains(RUSTDOC_FENCE)) { | 153 | if attributes.docs().map_or(true, |docs| !String::from(docs).contains(RUSTDOC_FENCE)) { |
92 | return; | 154 | return; |
93 | } | 155 | } |
156 | let attrs_source_map = attributes.source_map(&owner); | ||
94 | 157 | ||
95 | let mut inj = Injector::default(); | 158 | let mut inj = Injector::default(); |
96 | inj.add_unmapped("fn doctest() {\n"); | 159 | inj.add_unmapped("fn doctest() {\n"); |
@@ -101,12 +164,33 @@ pub(super) fn doc_comment(hl: &mut Highlights, node: &SyntaxNode) { | |||
101 | // Replace the original, line-spanning comment ranges by new, only comment-prefix | 164 | // Replace the original, line-spanning comment ranges by new, only comment-prefix |
102 | // spanning comment ranges. | 165 | // spanning comment ranges. |
103 | let mut new_comments = Vec::new(); | 166 | let mut new_comments = Vec::new(); |
104 | for comment in doc_comments { | 167 | let mut string; |
105 | match comment.text().find(RUSTDOC_FENCE) { | 168 | for attr in attributes.by_key("doc").attrs() { |
169 | let src = attrs_source_map.source_of(&attr); | ||
170 | let (line, range, prefix) = match &src { | ||
171 | Either::Left(it) => { | ||
172 | string = match find_doc_string_in_attr(attr, it) { | ||
173 | Some(it) => it, | ||
174 | None => continue, | ||
175 | }; | ||
176 | let text_range = string.syntax().text_range(); | ||
177 | let text_range = TextRange::new( | ||
178 | text_range.start() + TextSize::from(1), | ||
179 | text_range.end() - TextSize::from(1), | ||
180 | ); | ||
181 | let text = string.text(); | ||
182 | (&text[1..text.len() - 1], text_range, "") | ||
183 | } | ||
184 | Either::Right(comment) => { | ||
185 | (comment.text(), comment.syntax().text_range(), comment.prefix()) | ||
186 | } | ||
187 | }; | ||
188 | |||
189 | match line.find(RUSTDOC_FENCE) { | ||
106 | Some(idx) => { | 190 | Some(idx) => { |
107 | is_codeblock = !is_codeblock; | 191 | is_codeblock = !is_codeblock; |
108 | // Check whether code is rust by inspecting fence guards | 192 | // Check whether code is rust by inspecting fence guards |
109 | let guards = &comment.text()[idx + RUSTDOC_FENCE.len()..]; | 193 | let guards = &line[idx + RUSTDOC_FENCE.len()..]; |
110 | let is_rust = | 194 | let is_rust = |
111 | guards.split(',').all(|sub| RUSTDOC_FENCE_TOKENS.contains(&sub.trim())); | 195 | guards.split(',').all(|sub| RUSTDOC_FENCE_TOKENS.contains(&sub.trim())); |
112 | is_doctest = is_codeblock && is_rust; | 196 | is_doctest = is_codeblock && is_rust; |
@@ -116,10 +200,7 @@ pub(super) fn doc_comment(hl: &mut Highlights, node: &SyntaxNode) { | |||
116 | None => (), | 200 | None => (), |
117 | } | 201 | } |
118 | 202 | ||
119 | let line: &str = comment.text(); | 203 | let mut pos = TextSize::of(prefix); |
120 | let range = comment.syntax().text_range(); | ||
121 | |||
122 | let mut pos = TextSize::of(comment.prefix()); | ||
123 | // whitespace after comment is ignored | 204 | // whitespace after comment is ignored |
124 | if let Some(ws) = line[pos.into()..].chars().next().filter(|c| c.is_whitespace()) { | 205 | if let Some(ws) = line[pos.into()..].chars().next().filter(|c| c.is_whitespace()) { |
125 | pos += TextSize::of(ws); | 206 | pos += TextSize::of(ws); |
@@ -156,3 +237,27 @@ pub(super) fn doc_comment(hl: &mut Highlights, node: &SyntaxNode) { | |||
156 | }); | 237 | }); |
157 | } | 238 | } |
158 | } | 239 | } |
240 | |||
241 | fn find_doc_string_in_attr(attr: &hir::Attr, it: &ast::Attr) -> Option<ast::String> { | ||
242 | match it.literal() { | ||
243 | // #[doc = lit] | ||
244 | Some(lit) => match lit.kind() { | ||
245 | ast::LiteralKind::String(it) => Some(it), | ||
246 | _ => None, | ||
247 | }, | ||
248 | // #[cfg_attr(..., doc = "", ...)] | ||
249 | None => { | ||
250 | // We gotta hunt the string token manually here | ||
251 | let text = attr.string_value()?; | ||
252 | // FIXME: We just pick the first string literal that has the same text as the doc attribute | ||
253 | // This means technically we might highlight the wrong one | ||
254 | it.syntax() | ||
255 | .descendants_with_tokens() | ||
256 | .filter_map(NodeOrToken::into_token) | ||
257 | .filter_map(ast::String::cast) | ||
258 | .find(|string| { | ||
259 | string.text().get(1..string.text().len() - 1).map_or(false, |it| it == text) | ||
260 | }) | ||
261 | } | ||
262 | } | ||
263 | } | ||
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html index 5e877df88..45817faf9 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html | |||
@@ -105,4 +105,20 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
105 | <span class="parenthesis">(</span><span class="punctuation">$</span>expr<span class="colon">:</span>expr<span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">></span> <span class="brace">{</span> | 105 | <span class="parenthesis">(</span><span class="punctuation">$</span>expr<span class="colon">:</span>expr<span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">></span> <span class="brace">{</span> |
106 | <span class="punctuation">$</span>expr | 106 | <span class="punctuation">$</span>expr |
107 | <span class="brace">}</span> | 107 | <span class="brace">}</span> |
108 | <span class="brace">}</span></code></pre> \ No newline at end of file | 108 | <span class="brace">}</span> |
109 | |||
110 | <span class="comment documentation">/// ```rust</span> | ||
111 | <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="punctuation injected">_</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="function injected">example</span><span class="parenthesis injected">(</span><span class="operator injected">&</span><span class="bracket injected">[</span><span class="numeric_literal injected">1</span><span class="comma injected">,</span><span class="none injected"> </span><span class="numeric_literal injected">2</span><span class="comma injected">,</span><span class="none injected"> </span><span class="numeric_literal injected">3</span><span class="bracket injected">]</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span> | ||
112 | <span class="comment documentation">/// ```</span> | ||
113 | <span class="comment documentation">///</span> | ||
114 | <span class="comment documentation">/// ```</span> | ||
115 | <span class="comment documentation">/// </span><span class="keyword control injected">loop</span><span class="none injected"> </span><span class="brace injected">{</span><span class="brace injected">}</span> | ||
116 | <span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="function attribute">cfg_attr</span><span class="parenthesis attribute">(</span><span class="attribute attribute">not</span><span class="parenthesis attribute">(</span><span class="attribute attribute">feature </span><span class="operator attribute">=</span><span class="attribute attribute"> </span><span class="string_literal attribute">"false"</span><span class="parenthesis attribute">)</span><span class="comma attribute">,</span><span class="attribute attribute"> doc </span><span class="operator attribute">=</span><span class="attribute attribute"> </span><span class="string_literal attribute">"</span><span class="keyword control injected">loop</span><span class="none injected"> </span><span class="brace injected">{</span><span class="brace injected">}</span><span class="string_literal attribute">"</span><span class="parenthesis attribute">)</span><span class="attribute attribute">]</span> | ||
117 | <span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="function attribute">doc</span><span class="attribute attribute"> </span><span class="operator attribute">=</span><span class="attribute attribute"> </span><span class="string_literal attribute">"</span><span class="keyword control injected">loop</span><span class="none injected"> </span><span class="brace injected">{</span><span class="brace injected">}</span><span class="string_literal attribute">"</span><span class="attribute attribute">]</span> | ||
118 | <span class="comment documentation">/// ```</span> | ||
119 | <span class="comment documentation">///</span> | ||
120 | <span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="function attribute">cfg_attr</span><span class="parenthesis attribute">(</span><span class="attribute attribute">feature </span><span class="operator attribute">=</span><span class="attribute attribute"> </span><span class="string_literal attribute">"alloc"</span><span class="comma attribute">,</span><span class="attribute attribute"> doc </span><span class="operator attribute">=</span><span class="attribute attribute"> </span><span class="string_literal attribute">"```rust"</span><span class="parenthesis attribute">)</span><span class="attribute attribute">]</span> | ||
121 | <span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="function attribute">cfg_attr</span><span class="parenthesis attribute">(</span><span class="attribute attribute">not</span><span class="parenthesis attribute">(</span><span class="attribute attribute">feature </span><span class="operator attribute">=</span><span class="attribute attribute"> </span><span class="string_literal attribute">"alloc"</span><span class="parenthesis attribute">)</span><span class="comma attribute">,</span><span class="attribute attribute"> doc </span><span class="operator attribute">=</span><span class="attribute attribute"> </span><span class="string_literal attribute">"```ignore"</span><span class="parenthesis attribute">)</span><span class="attribute attribute">]</span> | ||
122 | <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="punctuation injected">_</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="function injected">example</span><span class="parenthesis injected">(</span><span class="operator injected">&</span><span class="none injected">alloc::</span><span class="macro injected">vec!</span><span class="bracket injected">[</span><span class="numeric_literal injected">1</span><span class="comma injected">,</span><span class="none injected"> </span><span class="numeric_literal injected">2</span><span class="comma injected">,</span><span class="none injected"> </span><span class="numeric_literal injected">3</span><span class="bracket injected">]</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span> | ||
123 | <span class="comment documentation">/// ```</span> | ||
124 | <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration">mix_and_match</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span></code></pre> \ No newline at end of file | ||
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs index 9d0cd1af5..a5ef2d29b 100644 --- a/crates/ide/src/syntax_highlighting/tests.rs +++ b/crates/ide/src/syntax_highlighting/tests.rs | |||
@@ -541,6 +541,22 @@ macro_rules! noop { | |||
541 | $expr | 541 | $expr |
542 | } | 542 | } |
543 | } | 543 | } |
544 | |||
545 | /// ```rust | ||
546 | /// let _ = example(&[1, 2, 3]); | ||
547 | /// ``` | ||
548 | /// | ||
549 | /// ``` | ||
550 | /// loop {} | ||
551 | #[cfg_attr(not(feature = "false"), doc = "loop {}")] | ||
552 | #[doc = "loop {}"] | ||
553 | /// ``` | ||
554 | /// | ||
555 | #[cfg_attr(feature = "alloc", doc = "```rust")] | ||
556 | #[cfg_attr(not(feature = "alloc"), doc = "```ignore")] | ||
557 | /// let _ = example(&alloc::vec![1, 2, 3]); | ||
558 | /// ``` | ||
559 | pub fn mix_and_match() {} | ||
544 | "# | 560 | "# |
545 | .trim(), | 561 | .trim(), |
546 | expect_file!["./test_data/highlight_doctest.html"], | 562 | expect_file!["./test_data/highlight_doctest.html"], |
diff --git a/crates/syntax/src/ast/traits.rs b/crates/syntax/src/ast/traits.rs index 13a769d51..96d4cc997 100644 --- a/crates/syntax/src/ast/traits.rs +++ b/crates/syntax/src/ast/traits.rs | |||
@@ -72,7 +72,7 @@ pub trait AttrsOwner: AstNode { | |||
72 | } | 72 | } |
73 | } | 73 | } |
74 | 74 | ||
75 | pub trait DocCommentsOwner: AstNode { | 75 | pub trait DocCommentsOwner: AttrsOwner { |
76 | fn doc_comments(&self) -> CommentIter { | 76 | fn doc_comments(&self) -> CommentIter { |
77 | CommentIter { iter: self.syntax().children_with_tokens() } | 77 | CommentIter { iter: self.syntax().children_with_tokens() } |
78 | } | 78 | } |