aboutsummaryrefslogtreecommitdiff
path: root/crates/ide
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide')
-rw-r--r--crates/ide/src/syntax_highlighting.rs2
-rw-r--r--crates/ide/src/syntax_highlighting/inject.rs135
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html18
-rw-r--r--crates/ide/src/syntax_highlighting/tests.rs16
4 files changed, 154 insertions, 17 deletions
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
3use hir::Semantics; 3use either::Either;
4use hir::{HasAttrs, Semantics};
4use ide_db::call_info::ActiveParameter; 5use ide_db::call_info::ActiveParameter;
5use syntax::{ast, AstToken, SyntaxNode, SyntaxToken, TextRange, TextSize}; 6use syntax::{
7 ast::{self, AstNode, AttrsOwner, DocCommentsOwner},
8 match_ast, AstToken, NodeOrToken, SyntaxNode, SyntaxToken, TextRange, TextSize,
9};
6 10
7use crate::{Analysis, HlMod, HlRange, HlTag, RootDatabase}; 11use 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
89struct AttrsOwnerNode {
90 node: SyntaxNode,
91}
92
93impl AttrsOwnerNode {
94 fn new<N: DocCommentsOwner>(node: N) -> Self {
95 AttrsOwnerNode { node: node.syntax().clone() }
96 }
97}
98
99impl AttrsOwner for AttrsOwnerNode {}
100impl 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
118fn 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.
85pub(super) fn doc_comment(hl: &mut Highlights, node: &SyntaxNode) { 147pub(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
241fn 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">&gt;</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">&gt;</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/// ```
559pub 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"],