1- use std:: iter;
1+ use std:: { convert :: TryFrom , iter} ;
22
33use either:: Either ;
44use hir:: { AsAssocItem , HasAttrs , HasSource , HirDisplay , Semantics , TypeInfo } ;
@@ -14,8 +14,12 @@ use ide_db::{
1414use itertools:: Itertools ;
1515use stdx:: format_to;
1616use 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 ,
1923} ;
2024
2125use crate :: {
@@ -115,36 +119,53 @@ pub(crate) fn hover(
115119 } ) ?;
116120
117121 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 {
123133 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+
124139 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+
132150 let ( attributes, def) = doc_attributes ( sema, & node) ?;
133151 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 (
135153 |( range, link, ns) | {
136154 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) )
140157 } ,
141158 ) ?;
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;
142163 let def = match resolve_doc_path_for_def ( sema. db , def, & link, ns) ? {
143164 Either :: Left ( it) => Definition :: ModuleDef ( it) ,
144165 Either :: Right ( it) => Definition :: Macro ( it) ,
145166 } ;
146167 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) )
148169 } ) ;
149170 }
150171
@@ -4941,4 +4962,63 @@ fn foo() {
49414962 "# ] ] ,
49424963 ) ;
49434964 }
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+ }
49445024}
0 commit comments