1
- use std:: iter;
1
+ use std:: { convert :: TryFrom , iter} ;
2
2
3
3
use either:: Either ;
4
4
use hir:: { AsAssocItem , HasAttrs , HasSource , HirDisplay , Semantics , TypeInfo } ;
@@ -14,8 +14,12 @@ use ide_db::{
14
14
use itertools:: Itertools ;
15
15
use stdx:: format_to;
16
16
use syntax:: {
17
- algo, ast, display:: fn_as_proc_macro_label, match_ast, AstNode , Direction , SyntaxKind :: * ,
18
- SyntaxNode , SyntaxToken , T ,
17
+ algo,
18
+ ast:: { self , IsString } ,
19
+ display:: fn_as_proc_macro_label,
20
+ match_ast, AstNode , AstToken , Direction ,
21
+ SyntaxKind :: * ,
22
+ SyntaxNode , SyntaxToken , TextSize , T ,
19
23
} ;
20
24
21
25
use crate :: {
@@ -115,36 +119,53 @@ pub(crate) fn hover(
115
119
} ) ?;
116
120
117
121
let descended = sema. descend_into_macros_many ( original_token. clone ( ) ) ;
118
-
119
- // FIXME handle doc attributes? TokenMap currently doesn't work with comments
120
- if original_token. kind ( ) == COMMENT {
121
- let relative_comment_offset = offset - original_token. text_range ( ) . start ( ) ;
122
- // intra-doc links
122
+ // magic intra doc link handling
123
+ // FIXME: Lift this out to some other place, goto def wants this as well
124
+ let comment_prefix_len = match_ast ! {
125
+ match original_token {
126
+ ast:: Comment ( comment) => TextSize :: try_from( comment. prefix( ) . len( ) ) . ok( ) ,
127
+ ast:: String ( string) => original_token. ancestors( ) . find_map( ast:: Attr :: cast)
128
+ . filter( |attr| attr. simple_name( ) . as_deref( ) == Some ( "doc" ) ) . and_then( |_| string. open_quote_text_range( ) . map( |it| it. len( ) ) ) ,
129
+ _ => None ,
130
+ }
131
+ } ;
132
+ if let Some ( prefix_len) = comment_prefix_len {
123
133
cov_mark:: hit!( no_highlight_on_comment_hover) ;
134
+
135
+ // offset relative to the comments contents
136
+ let original_start = original_token. text_range ( ) . start ( ) ;
137
+ let relative_comment_offset = offset - original_start - prefix_len;
138
+
124
139
return descended. iter ( ) . find_map ( |t| {
125
- match t. kind ( ) {
126
- COMMENT => ( ) ,
127
- TOKEN_TREE => { }
128
- _ => return None ,
129
- }
130
- let node = t. parent ( ) ?;
131
- let absolute_comment_offset = t. text_range ( ) . start ( ) + relative_comment_offset;
140
+ let ( node, descended_prefix_len) = match_ast ! {
141
+ match t {
142
+ ast:: Comment ( comment) => ( t. parent( ) ?, TextSize :: try_from( comment. prefix( ) . len( ) ) . ok( ) ?) ,
143
+ ast:: String ( string) => ( t. ancestors( ) . skip_while( |n| n. kind( ) != ATTR ) . nth( 1 ) ?, string. open_quote_text_range( ) ?. len( ) ) ,
144
+ _ => return None ,
145
+ }
146
+ } ;
147
+ let token_start = t. text_range ( ) . start ( ) ;
148
+ let abs_in_expansion_offset = token_start + relative_comment_offset + descended_prefix_len;
149
+
132
150
let ( attributes, def) = doc_attributes ( sema, & node) ?;
133
151
let ( docs, doc_mapping) = attributes. docs_with_rangemap ( sema. db ) ?;
134
- let ( idl_range , link, ns) = extract_definitions_from_docs ( & docs) . into_iter ( ) . find_map (
152
+ let ( in_expansion_range , link, ns) = extract_definitions_from_docs ( & docs) . into_iter ( ) . find_map (
135
153
|( range, link, ns) | {
136
154
let mapped = doc_mapping. map ( range) ?;
137
- ( mapped. file_id == file_id. into ( )
138
- && mapped. value . contains ( absolute_comment_offset) )
139
- . then ( || ( mapped. value , link, ns) )
155
+ ( mapped. value . contains ( abs_in_expansion_offset) )
156
+ . then ( || ( mapped. value , link, ns) )
140
157
} ,
141
158
) ?;
159
+ // get the relative range to the doc/attribute in the expansion
160
+ let in_expansion_relative_range = in_expansion_range - descended_prefix_len - token_start;
161
+ // Apply relative range to the original input comment
162
+ let absolute_range = in_expansion_relative_range + original_start + prefix_len;
142
163
let def = match resolve_doc_path_for_def ( sema. db , def, & link, ns) ? {
143
164
Either :: Left ( it) => Definition :: ModuleDef ( it) ,
144
165
Either :: Right ( it) => Definition :: Macro ( it) ,
145
166
} ;
146
167
let res = hover_for_definition ( sema, file_id, def, & node, config) ?;
147
- Some ( RangeInfo :: new ( idl_range , res) )
168
+ Some ( RangeInfo :: new ( absolute_range , res) )
148
169
} ) ;
149
170
}
150
171
@@ -4941,4 +4962,63 @@ fn foo() {
4941
4962
"# ] ] ,
4942
4963
) ;
4943
4964
}
4965
+
4966
+ #[ test]
4967
+ fn hover_intra_in_macro ( ) {
4968
+ check (
4969
+ r#"
4970
+ macro_rules! foo_macro {
4971
+ ($(#[$attr:meta])* $name:ident) => {
4972
+ $(#[$attr])*
4973
+ pub struct $name;
4974
+ }
4975
+ }
4976
+
4977
+ foo_macro!(
4978
+ /// Doc comment for [`Foo$0`]
4979
+ Foo
4980
+ );
4981
+ "# ,
4982
+ expect ! [ [ r#"
4983
+ *[`Foo`]*
4984
+
4985
+ ```rust
4986
+ test
4987
+ ```
4988
+
4989
+ ```rust
4990
+ pub struct Foo
4991
+ ```
4992
+
4993
+ ---
4994
+
4995
+ Doc comment for [`Foo`](https://docs.rs/test/*/test/struct.Foo.html)
4996
+ "# ] ] ,
4997
+ ) ;
4998
+ }
4999
+
5000
+ #[ test]
5001
+ fn hover_intra_in_attr ( ) {
5002
+ check (
5003
+ r#"
5004
+ #[doc = "Doc comment for [`Foo$0`]"]
5005
+ pub struct Foo;
5006
+ "# ,
5007
+ expect ! [ [ r#"
5008
+ *[`Foo`]*
5009
+
5010
+ ```rust
5011
+ test
5012
+ ```
5013
+
5014
+ ```rust
5015
+ pub struct Foo
5016
+ ```
5017
+
5018
+ ---
5019
+
5020
+ Doc comment for [`Foo`](https://docs.rs/test/*/test/struct.Foo.html)
5021
+ "# ] ] ,
5022
+ ) ;
5023
+ }
4944
5024
}
0 commit comments