Skip to content

Commit bba474b

Browse files
bors[bot]Veykril
andauthored
Merge #8071
8071: Semantic highlight intradoclinks in documentation r=Veykril a=Veykril Co-authored-by: Lukas Wirth <[email protected]>
2 parents ec10835 + 9763f0a commit bba474b

16 files changed

+132
-55
lines changed

crates/ide/src/doc_links.rs

+6-7
Original file line numberDiff line numberDiff line change
@@ -65,20 +65,19 @@ pub(crate) fn extract_definitions_from_markdown(
6565
) -> Vec<(String, Option<hir::Namespace>, Range<usize>)> {
6666
let mut res = vec![];
6767
let mut cb = |link: BrokenLink| {
68+
// These allocations are actually unnecessary but the lifetimes on BrokenLinkCallback are wrong
69+
// this is fixed in the repo but not on the crates.io release yet
6870
Some((
6971
/*url*/ link.reference.to_owned().into(),
7072
/*title*/ link.reference.to_owned().into(),
7173
))
7274
};
7375
let doc = Parser::new_with_broken_link_callback(markdown, Options::empty(), Some(&mut cb));
7476
for (event, range) in doc.into_offset_iter() {
75-
match event {
76-
Event::Start(Tag::Link(_link_type, ref target, ref title)) => {
77-
let link = if target.is_empty() { title } else { target };
78-
let (link, ns) = parse_link(link);
79-
res.push((link.to_string(), ns, range));
80-
}
81-
_ => {}
77+
if let Event::Start(Tag::Link(_, target, title)) = event {
78+
let link = if target.is_empty() { title } else { target };
79+
let (link, ns) = parse_link(&link);
80+
res.push((link.to_string(), ns, range));
8281
}
8382
}
8483
res

crates/ide/src/syntax_highlighting/html.rs

+1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
5959
.label { color: #DFAF8F; font-style: italic; }
6060
.comment { color: #7F9F7F; }
6161
.documentation { color: #629755; }
62+
.intra_doc_link { color: #A9C577; }
6263
.injected { opacity: 0.65 ; }
6364
.struct, .enum { color: #7CB8BB; }
6465
.enum_variant { color: #BDE0F3; }

crates/ide/src/syntax_highlighting/inject.rs

+87-33
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
//! "Recursive" Syntax highlighting for code in doctests and fixtures.
22
3-
use std::mem;
3+
use std::{mem, ops::Range};
44

55
use either::Either;
66
use hir::{HasAttrs, Semantics};
7-
use ide_db::call_info::ActiveParameter;
7+
use ide_db::{call_info::ActiveParameter, defs::Definition};
88
use syntax::{
99
ast::{self, AstNode, AttrsOwner, DocCommentsOwner},
1010
match_ast, AstToken, NodeOrToken, SyntaxNode, SyntaxToken, TextRange, TextSize,
1111
};
1212

13-
use crate::{Analysis, HlMod, HlRange, HlTag, RootDatabase};
13+
use crate::{
14+
doc_links::extract_definitions_from_markdown, Analysis, HlMod, HlRange, HlTag, RootDatabase,
15+
};
1416

1517
use super::{highlights::Highlights, injector::Injector};
1618

@@ -120,24 +122,24 @@ impl AstNode for AttrsOwnerNode {
120122
fn doc_attributes<'node>(
121123
sema: &Semantics<RootDatabase>,
122124
node: &'node SyntaxNode,
123-
) -> Option<(AttrsOwnerNode, hir::Attrs)> {
125+
) -> Option<(AttrsOwnerNode, hir::Attrs, Definition)> {
124126
match_ast! {
125127
match node {
126-
ast::SourceFile(it) => sema.to_def(&it).map(|def| (AttrsOwnerNode::new(it), def.attrs(sema.db))),
127-
ast::Fn(it) => sema.to_def(&it).map(|def| (AttrsOwnerNode::new(it), def.attrs(sema.db))),
128-
ast::Struct(it) => sema.to_def(&it).map(|def| (AttrsOwnerNode::new(it), def.attrs(sema.db))),
129-
ast::Union(it) => sema.to_def(&it).map(|def| (AttrsOwnerNode::new(it), def.attrs(sema.db))),
130-
ast::RecordField(it) => sema.to_def(&it).map(|def| (AttrsOwnerNode::new(it), def.attrs(sema.db))),
131-
ast::TupleField(it) => sema.to_def(&it).map(|def| (AttrsOwnerNode::new(it), def.attrs(sema.db))),
132-
ast::Enum(it) => sema.to_def(&it).map(|def| (AttrsOwnerNode::new(it), def.attrs(sema.db))),
133-
ast::Variant(it) => sema.to_def(&it).map(|def| (AttrsOwnerNode::new(it), def.attrs(sema.db))),
134-
ast::Trait(it) => sema.to_def(&it).map(|def| (AttrsOwnerNode::new(it), def.attrs(sema.db))),
135-
ast::Module(it) => sema.to_def(&it).map(|def| (AttrsOwnerNode::new(it), def.attrs(sema.db))),
136-
ast::Static(it) => sema.to_def(&it).map(|def| (AttrsOwnerNode::new(it), def.attrs(sema.db))),
137-
ast::Const(it) => sema.to_def(&it).map(|def| (AttrsOwnerNode::new(it), def.attrs(sema.db))),
138-
ast::TypeAlias(it) => sema.to_def(&it).map(|def| (AttrsOwnerNode::new(it), def.attrs(sema.db))),
139-
ast::Impl(it) => sema.to_def(&it).map(|def| (AttrsOwnerNode::new(it), def.attrs(sema.db))),
140-
ast::MacroRules(it) => sema.to_def(&it).map(|def| (AttrsOwnerNode::new(it), def.attrs(sema.db))),
128+
ast::SourceFile(it) => sema.to_def(&it).map(|def| (AttrsOwnerNode::new(it), def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Module(def)))),
129+
ast::Module(it) => sema.to_def(&it).map(|def| (AttrsOwnerNode::new(it), def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Module(def)))),
130+
ast::Fn(it) => sema.to_def(&it).map(|def| (AttrsOwnerNode::new(it), def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Function(def)))),
131+
ast::Struct(it) => sema.to_def(&it).map(|def| (AttrsOwnerNode::new(it), def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Struct(def))))),
132+
ast::Union(it) => sema.to_def(&it).map(|def| (AttrsOwnerNode::new(it), def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Union(def))))),
133+
ast::Enum(it) => sema.to_def(&it).map(|def| (AttrsOwnerNode::new(it), def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Enum(def))))),
134+
ast::Variant(it) => sema.to_def(&it).map(|def| (AttrsOwnerNode::new(it), def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Variant(def)))),
135+
ast::Trait(it) => sema.to_def(&it).map(|def| (AttrsOwnerNode::new(it), def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Trait(def)))),
136+
ast::Static(it) => sema.to_def(&it).map(|def| (AttrsOwnerNode::new(it), def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Static(def)))),
137+
ast::Const(it) => sema.to_def(&it).map(|def| (AttrsOwnerNode::new(it), def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Const(def)))),
138+
ast::TypeAlias(it) => sema.to_def(&it).map(|def| (AttrsOwnerNode::new(it), def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::TypeAlias(def)))),
139+
ast::Impl(it) => sema.to_def(&it).map(|def| (AttrsOwnerNode::new(it), def.attrs(sema.db), Definition::SelfType(def))),
140+
ast::RecordField(it) => sema.to_def(&it).map(|def| (AttrsOwnerNode::new(it), def.attrs(sema.db), Definition::Field(def))),
141+
ast::TupleField(it) => sema.to_def(&it).map(|def| (AttrsOwnerNode::new(it), def.attrs(sema.db), Definition::Field(def))),
142+
ast::MacroRules(it) => sema.to_def(&it).map(|def| (AttrsOwnerNode::new(it), def.attrs(sema.db), Definition::Macro(def))),
141143
// ast::MacroDef(it) => sema.to_def(&it).map(|def| (Box::new(it) as _, def.attrs(sema.db))),
142144
// ast::Use(it) => sema.to_def(&it).map(|def| (Box::new(it) as _, def.attrs(sema.db))),
143145
_ => return None
@@ -147,25 +149,23 @@ fn doc_attributes<'node>(
147149

148150
/// Injection of syntax highlighting of doctests.
149151
pub(super) fn doc_comment(hl: &mut Highlights, sema: &Semantics<RootDatabase>, node: &SyntaxNode) {
150-
let (owner, attributes) = match doc_attributes(sema, node) {
152+
let (owner, attributes, def) = match doc_attributes(sema, node) {
151153
Some(it) => it,
152154
None => return,
153155
};
154156

155-
if attributes.docs().map_or(true, |docs| !String::from(docs).contains(RUSTDOC_FENCE)) {
156-
return;
157-
}
158-
let attrs_source_map = attributes.source_map(&owner);
159-
160157
let mut inj = Injector::default();
161158
inj.add_unmapped("fn doctest() {\n");
162159

160+
let attrs_source_map = attributes.source_map(&owner);
161+
163162
let mut is_codeblock = false;
164163
let mut is_doctest = false;
165164

166165
// Replace the original, line-spanning comment ranges by new, only comment-prefix
167166
// spanning comment ranges.
168167
let mut new_comments = Vec::new();
168+
let mut intra_doc_links = Vec::new();
169169
let mut string;
170170
for attr in attributes.by_key("doc").attrs() {
171171
let src = attrs_source_map.source_of(&attr);
@@ -209,7 +209,22 @@ pub(super) fn doc_comment(hl: &mut Highlights, sema: &Semantics<RootDatabase>, n
209209
is_doctest = is_codeblock && is_rust;
210210
continue;
211211
}
212-
None if !is_doctest => continue,
212+
None if !is_doctest => {
213+
intra_doc_links.extend(
214+
extract_definitions_from_markdown(line)
215+
.into_iter()
216+
.filter(|(link, ns, _)| {
217+
validate_intra_doc_link(sema.db, &def, link, *ns)
218+
})
219+
.map(|(.., Range { start, end })| {
220+
TextRange::at(
221+
prev_range_start + TextSize::from(start as u32),
222+
TextSize::from((end - start) as u32),
223+
)
224+
}),
225+
);
226+
continue;
227+
}
213228
None => (),
214229
}
215230

@@ -227,17 +242,28 @@ pub(super) fn doc_comment(hl: &mut Highlights, sema: &Semantics<RootDatabase>, n
227242
inj.add_unmapped("\n");
228243
}
229244
}
245+
246+
for range in intra_doc_links {
247+
hl.add(HlRange {
248+
range,
249+
highlight: HlTag::IntraDocLink | HlMod::Documentation,
250+
binding_hash: None,
251+
});
252+
}
253+
254+
if new_comments.is_empty() {
255+
return; // no need to run an analysis on an empty file
256+
}
257+
230258
inj.add_unmapped("\n}");
231259

232260
let (analysis, tmp_file_id) = Analysis::from_single_file(inj.text().to_string());
233261

234-
for h in analysis.with_db(|db| super::highlight(db, tmp_file_id, None, true)).unwrap() {
235-
for r in inj.map_range_up(h.range) {
236-
hl.add(HlRange {
237-
range: r,
238-
highlight: h.highlight | HlMod::Injected,
239-
binding_hash: h.binding_hash,
240-
});
262+
for HlRange { range, highlight, binding_hash } in
263+
analysis.with_db(|db| super::highlight(db, tmp_file_id, None, true)).unwrap()
264+
{
265+
for range in inj.map_range_up(range) {
266+
hl.add(HlRange { range, highlight: highlight | HlMod::Injected, binding_hash });
241267
}
242268
}
243269

@@ -273,3 +299,31 @@ fn find_doc_string_in_attr(attr: &hir::Attr, it: &ast::Attr) -> Option<ast::Stri
273299
}
274300
}
275301
}
302+
303+
fn validate_intra_doc_link(
304+
db: &RootDatabase,
305+
def: &Definition,
306+
link: &str,
307+
ns: Option<hir::Namespace>,
308+
) -> bool {
309+
match def {
310+
Definition::ModuleDef(def) => match def {
311+
hir::ModuleDef::Module(it) => it.resolve_doc_path(db, &link, ns),
312+
hir::ModuleDef::Function(it) => it.resolve_doc_path(db, &link, ns),
313+
hir::ModuleDef::Adt(it) => it.resolve_doc_path(db, &link, ns),
314+
hir::ModuleDef::Variant(it) => it.resolve_doc_path(db, &link, ns),
315+
hir::ModuleDef::Const(it) => it.resolve_doc_path(db, &link, ns),
316+
hir::ModuleDef::Static(it) => it.resolve_doc_path(db, &link, ns),
317+
hir::ModuleDef::Trait(it) => it.resolve_doc_path(db, &link, ns),
318+
hir::ModuleDef::TypeAlias(it) => it.resolve_doc_path(db, &link, ns),
319+
hir::ModuleDef::BuiltinType(_) => None,
320+
},
321+
Definition::Macro(it) => it.resolve_doc_path(db, &link, ns),
322+
Definition::Field(it) => it.resolve_doc_path(db, &link, ns),
323+
Definition::SelfType(_)
324+
| Definition::Local(_)
325+
| Definition::GenericParam(_)
326+
| Definition::Label(_) => None,
327+
}
328+
.is_some()
329+
}

crates/ide/src/syntax_highlighting/tags.rs

+6-4
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,20 @@ pub struct HlMods(u32);
1818
pub enum HlTag {
1919
Symbol(SymbolKind),
2020

21+
Attribute,
2122
BoolLiteral,
2223
BuiltinType,
2324
ByteLiteral,
2425
CharLiteral,
25-
NumericLiteral,
26-
StringLiteral,
27-
Attribute,
2826
Comment,
2927
EscapeSequence,
3028
FormatSpecifier,
29+
IntraDocLink,
3130
Keyword,
32-
Punctuation(HlPunct),
31+
NumericLiteral,
3332
Operator,
33+
Punctuation(HlPunct),
34+
StringLiteral,
3435
UnresolvedReference,
3536

3637
// For things which don't have a specific highlight.
@@ -116,6 +117,7 @@ impl HlTag {
116117
HlTag::Comment => "comment",
117118
HlTag::EscapeSequence => "escape_sequence",
118119
HlTag::FormatSpecifier => "format_specifier",
120+
HlTag::IntraDocLink => "intra_doc_link",
119121
HlTag::Keyword => "keyword",
120122
HlTag::Punctuation(punct) => match punct {
121123
HlPunct::Bracket => "bracket",

crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
.label { color: #DFAF8F; font-style: italic; }
88
.comment { color: #7F9F7F; }
99
.documentation { color: #629755; }
10+
.intra_doc_link { color: #A9C577; }
1011
.injected { opacity: 0.65 ; }
1112
.struct, .enum { color: #7CB8BB; }
1213
.enum_variant { color: #BDE0F3; }

crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html

+6
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
.label { color: #DFAF8F; font-style: italic; }
88
.comment { color: #7F9F7F; }
99
.documentation { color: #629755; }
10+
.intra_doc_link { color: #A9C577; }
1011
.injected { opacity: 0.65 ; }
1112
.struct, .enum { color: #7CB8BB; }
1213
.enum_variant { color: #BDE0F3; }
@@ -98,6 +99,11 @@
9899
<span class="brace">}</span>
99100
<span class="brace">}</span>
100101

102+
<span class="comment documentation">/// </span><span class="intra_doc_link documentation">[`Foo`](Foo)</span><span class="comment documentation"> is a struct</span>
103+
<span class="comment documentation">/// </span><span class="intra_doc_link documentation">[`all_the_links`](all_the_links)</span><span class="comment documentation"> is this function</span>
104+
<span class="comment documentation">/// [`noop`](noop) is a macro below</span>
105+
<span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration">all_the_links</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
106+
101107
<span class="comment documentation">/// ```</span>
102108
<span class="comment documentation">/// </span><span class="macro injected">noop!</span><span class="parenthesis injected">(</span><span class="numeric_literal injected">1</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span>
103109
<span class="comment documentation">/// ```</span>

crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
.label { color: #DFAF8F; font-style: italic; }
88
.comment { color: #7F9F7F; }
99
.documentation { color: #629755; }
10+
.intra_doc_link { color: #A9C577; }
1011
.injected { opacity: 0.65 ; }
1112
.struct, .enum { color: #7CB8BB; }
1213
.enum_variant { color: #BDE0F3; }

crates/ide/src/syntax_highlighting/test_data/highlight_injection.html

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
.label { color: #DFAF8F; font-style: italic; }
88
.comment { color: #7F9F7F; }
99
.documentation { color: #629755; }
10+
.intra_doc_link { color: #A9C577; }
1011
.injected { opacity: 0.65 ; }
1112
.struct, .enum { color: #7CB8BB; }
1213
.enum_variant { color: #BDE0F3; }

crates/ide/src/syntax_highlighting/test_data/highlight_strings.html

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
.label { color: #DFAF8F; font-style: italic; }
88
.comment { color: #7F9F7F; }
99
.documentation { color: #629755; }
10+
.intra_doc_link { color: #A9C577; }
1011
.injected { opacity: 0.65 ; }
1112
.struct, .enum { color: #7CB8BB; }
1213
.enum_variant { color: #BDE0F3; }

crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
.label { color: #DFAF8F; font-style: italic; }
88
.comment { color: #7F9F7F; }
99
.documentation { color: #629755; }
10+
.intra_doc_link { color: #A9C577; }
1011
.injected { opacity: 0.65 ; }
1112
.struct, .enum { color: #7CB8BB; }
1213
.enum_variant { color: #BDE0F3; }

crates/ide/src/syntax_highlighting/test_data/highlighting.html

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
.label { color: #DFAF8F; font-style: italic; }
88
.comment { color: #7F9F7F; }
99
.documentation { color: #629755; }
10+
.intra_doc_link { color: #A9C577; }
1011
.injected { opacity: 0.65 ; }
1112
.struct, .enum { color: #7CB8BB; }
1213
.enum_variant { color: #BDE0F3; }

crates/ide/src/syntax_highlighting/test_data/injection.html

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
.label { color: #DFAF8F; font-style: italic; }
88
.comment { color: #7F9F7F; }
99
.documentation { color: #629755; }
10+
.intra_doc_link { color: #A9C577; }
1011
.injected { opacity: 0.65 ; }
1112
.struct, .enum { color: #7CB8BB; }
1213
.enum_variant { color: #BDE0F3; }

crates/ide/src/syntax_highlighting/test_data/rainbow_highlighting.html

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
.label { color: #DFAF8F; font-style: italic; }
88
.comment { color: #7F9F7F; }
99
.documentation { color: #629755; }
10+
.intra_doc_link { color: #A9C577; }
1011
.injected { opacity: 0.65 ; }
1112
.struct, .enum { color: #7CB8BB; }
1213
.enum_variant { color: #BDE0F3; }

crates/ide/src/syntax_highlighting/tests.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -468,7 +468,7 @@ fn main() {
468468
}
469469

470470
#[test]
471-
fn test_highlight_doctest() {
471+
fn test_highlight_doc_comment() {
472472
check_highlighting(
473473
r#"
474474
/// ```
@@ -533,6 +533,11 @@ impl Foo {
533533
}
534534
}
535535
536+
/// [`Foo`](Foo) is a struct
537+
/// [`all_the_links`](all_the_links) is this function
538+
/// [`noop`](noop) is a macro below
539+
pub fn all_the_links() {}
540+
536541
/// ```
537542
/// noop!(1);
538543
/// ```

crates/rust-analyzer/src/semantic_tokens.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,16 @@ define_semantic_token_types![
4545
(BRACKET, "bracket"),
4646
(BUILTIN_TYPE, "builtinType"),
4747
(CHAR_LITERAL, "characterLiteral"),
48-
(COMMA, "comma"),
4948
(COLON, "colon"),
49+
(COMMA, "comma"),
50+
(CONST_PARAMETER, "constParameter"),
5051
(DOT, "dot"),
5152
(ESCAPE_SEQUENCE, "escapeSequence"),
5253
(FORMAT_SPECIFIER, "formatSpecifier"),
5354
(GENERIC, "generic"),
54-
(CONST_PARAMETER, "constParameter"),
55-
(LIFETIME, "lifetime"),
55+
(INTRA_DOC_LINK, "intraDocLink"),
5656
(LABEL, "label"),
57+
(LIFETIME, "lifetime"),
5758
(PARENTHESIS, "parenthesis"),
5859
(PUNCTUATION, "punctuation"),
5960
(SELF_KEYWORD, "selfKeyword"),

0 commit comments

Comments
 (0)