1
1
//! "Recursive" Syntax highlighting for code in doctests and fixtures.
2
2
3
- use std:: mem;
3
+ use std:: { mem, ops :: Range } ;
4
4
5
5
use either:: Either ;
6
6
use hir:: { HasAttrs , Semantics } ;
7
- use ide_db:: call_info:: ActiveParameter ;
7
+ use ide_db:: { call_info:: ActiveParameter , defs :: Definition } ;
8
8
use syntax:: {
9
9
ast:: { self , AstNode , AttrsOwner , DocCommentsOwner } ,
10
10
match_ast, AstToken , NodeOrToken , SyntaxNode , SyntaxToken , TextRange , TextSize ,
11
11
} ;
12
12
13
- use crate :: { Analysis , HlMod , HlRange , HlTag , RootDatabase } ;
13
+ use crate :: {
14
+ doc_links:: extract_definitions_from_markdown, Analysis , HlMod , HlRange , HlTag , RootDatabase ,
15
+ } ;
14
16
15
17
use super :: { highlights:: Highlights , injector:: Injector } ;
16
18
@@ -120,24 +122,24 @@ impl AstNode for AttrsOwnerNode {
120
122
fn doc_attributes < ' node > (
121
123
sema : & Semantics < RootDatabase > ,
122
124
node : & ' node SyntaxNode ,
123
- ) -> Option < ( AttrsOwnerNode , hir:: Attrs ) > {
125
+ ) -> Option < ( AttrsOwnerNode , hir:: Attrs , Definition ) > {
124
126
match_ast ! {
125
127
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 ) ) ) ,
141
143
// ast::MacroDef(it) => sema.to_def(&it).map(|def| (Box::new(it) as _, def.attrs(sema.db))),
142
144
// ast::Use(it) => sema.to_def(&it).map(|def| (Box::new(it) as _, def.attrs(sema.db))),
143
145
_ => return None
@@ -147,25 +149,23 @@ fn doc_attributes<'node>(
147
149
148
150
/// Injection of syntax highlighting of doctests.
149
151
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) {
151
153
Some ( it) => it,
152
154
None => return ,
153
155
} ;
154
156
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
-
160
157
let mut inj = Injector :: default ( ) ;
161
158
inj. add_unmapped ( "fn doctest() {\n " ) ;
162
159
160
+ let attrs_source_map = attributes. source_map ( & owner) ;
161
+
163
162
let mut is_codeblock = false ;
164
163
let mut is_doctest = false ;
165
164
166
165
// Replace the original, line-spanning comment ranges by new, only comment-prefix
167
166
// spanning comment ranges.
168
167
let mut new_comments = Vec :: new ( ) ;
168
+ let mut intra_doc_links = Vec :: new ( ) ;
169
169
let mut string;
170
170
for attr in attributes. by_key ( "doc" ) . attrs ( ) {
171
171
let src = attrs_source_map. source_of ( & attr) ;
@@ -209,7 +209,22 @@ pub(super) fn doc_comment(hl: &mut Highlights, sema: &Semantics<RootDatabase>, n
209
209
is_doctest = is_codeblock && is_rust;
210
210
continue ;
211
211
}
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
+ }
213
228
None => ( ) ,
214
229
}
215
230
@@ -227,17 +242,28 @@ pub(super) fn doc_comment(hl: &mut Highlights, sema: &Semantics<RootDatabase>, n
227
242
inj. add_unmapped ( "\n " ) ;
228
243
}
229
244
}
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
+
230
258
inj. add_unmapped ( "\n }" ) ;
231
259
232
260
let ( analysis, tmp_file_id) = Analysis :: from_single_file ( inj. text ( ) . to_string ( ) ) ;
233
261
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 } ) ;
241
267
}
242
268
}
243
269
@@ -273,3 +299,31 @@ fn find_doc_string_in_attr(attr: &hir::Attr, it: &ast::Attr) -> Option<ast::Stri
273
299
}
274
300
}
275
301
}
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
+ }
0 commit comments