aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide/src/hover.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide/src/hover.rs')
-rw-r--r--crates/ra_ide/src/hover.rs126
1 files changed, 64 insertions, 62 deletions
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs
index 260a7b869..35e39f965 100644
--- a/crates/ra_ide/src/hover.rs
+++ b/crates/ra_ide/src/hover.rs
@@ -6,14 +6,13 @@ use ra_syntax::{
6 algo::find_covering_element, 6 algo::find_covering_element,
7 ast::{self, DocCommentsOwner}, 7 ast::{self, DocCommentsOwner},
8 match_ast, AstNode, 8 match_ast, AstNode,
9 SyntaxKind::*,
10 SyntaxToken, TokenAtOffset,
9}; 11};
10 12
11use crate::{ 13use crate::{
12 db::RootDatabase, 14 db::RootDatabase,
13 display::{ 15 display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel},
14 description_from_symbol, docs_from_symbol, macro_label, rust_code_markup,
15 rust_code_markup_with_doc, ShortLabel,
16 },
17 expand::descend_into_macros, 16 expand::descend_into_macros,
18 references::{classify_name, classify_name_ref, NameKind, NameKind::*}, 17 references::{classify_name, classify_name_ref, NameKind, NameKind::*},
19 FilePosition, FileRange, RangeInfo, 18 FilePosition, FileRange, RangeInfo,
@@ -93,11 +92,7 @@ fn hover_text(docs: Option<String>, desc: Option<String>) -> Option<String> {
93 } 92 }
94} 93}
95 94
96fn hover_text_from_name_kind( 95fn hover_text_from_name_kind(db: &RootDatabase, name_kind: NameKind) -> Option<String> {
97 db: &RootDatabase,
98 name_kind: NameKind,
99 no_fallback: &mut bool,
100) -> Option<String> {
101 return match name_kind { 96 return match name_kind {
102 Macro(it) => { 97 Macro(it) => {
103 let src = it.source(db); 98 let src = it.source(db);
@@ -133,12 +128,8 @@ fn hover_text_from_name_kind(
133 hir::ModuleDef::TypeAlias(it) => from_def_source(db, it), 128 hir::ModuleDef::TypeAlias(it) => from_def_source(db, it),
134 hir::ModuleDef::BuiltinType(it) => Some(it.to_string()), 129 hir::ModuleDef::BuiltinType(it) => Some(it.to_string()),
135 }, 130 },
136 Local(_) => { 131 Local(_) => None,
137 // Hover for these shows type names 132 TypeParam(_) | SelfType(_) => {
138 *no_fallback = true;
139 None
140 }
141 GenericParam(_) | SelfType(_) => {
142 // FIXME: Hover for generic param 133 // FIXME: Hover for generic param
143 None 134 None
144 } 135 }
@@ -156,68 +147,55 @@ fn hover_text_from_name_kind(
156 147
157pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<HoverResult>> { 148pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<HoverResult>> {
158 let file = db.parse_or_expand(position.file_id.into())?; 149 let file = db.parse_or_expand(position.file_id.into())?;
159 let token = file.token_at_offset(position.offset).filter(|it| !it.kind().is_trivia()).next()?; 150 let token = pick_best(file.token_at_offset(position.offset))?;
160 let token = descend_into_macros(db, position.file_id, token); 151 let token = descend_into_macros(db, position.file_id, token);
161 152
162 let mut res = HoverResult::new(); 153 let mut res = HoverResult::new();
163 154
164 let mut range = match_ast! { 155 if let Some((range, name_kind)) = match_ast! {
165 match (token.value.parent()) { 156 match (token.value.parent()) {
166 ast::NameRef(name_ref) => { 157 ast::NameRef(name_ref) => {
167 let mut no_fallback = false; 158 classify_name_ref(db, token.with_value(&name_ref)).map(|d| (name_ref.syntax().text_range(), d.kind))
168 if let Some(name_kind) =
169 classify_name_ref(db, token.with_value(&name_ref)).map(|d| d.kind)
170 {
171 res.extend(hover_text_from_name_kind(db, name_kind, &mut no_fallback))
172 }
173
174 if res.is_empty() && !no_fallback {
175 // Fallback index based approach:
176 let symbols = crate::symbol_index::index_resolve(db, &name_ref);
177 for sym in symbols {
178 let docs = docs_from_symbol(db, &sym);
179 let desc = description_from_symbol(db, &sym);
180 res.extend(hover_text(docs, desc));
181 }
182 }
183
184 if !res.is_empty() {
185 Some(name_ref.syntax().text_range())
186 } else {
187 None
188 }
189 }, 159 },
190 ast::Name(name) => { 160 ast::Name(name) => {
191 if let Some(name_kind) = classify_name(db, token.with_value(&name)).map(|d| d.kind) { 161 classify_name(db, token.with_value(&name)).map(|d| (name.syntax().text_range(), d.kind))
192 res.extend(hover_text_from_name_kind(db, name_kind, &mut true));
193 }
194
195 if !res.is_empty() {
196 Some(name.syntax().text_range())
197 } else {
198 None
199 }
200 }, 162 },
201 _ => None, 163 _ => None,
202 } 164 }
203 }; 165 } {
166 res.extend(hover_text_from_name_kind(db, name_kind));
204 167
205 if range.is_none() { 168 if !res.is_empty() {
206 let node = token.value.ancestors().find(|n| { 169 return Some(RangeInfo::new(range, res));
207 ast::Expr::cast(n.clone()).is_some() || ast::Pat::cast(n.clone()).is_some() 170 }
208 })?; 171 }
209 let frange = FileRange { file_id: position.file_id, range: node.text_range() };
210 res.extend(type_of(db, frange).map(rust_code_markup));
211 range = Some(node.text_range());
212 };
213 172
214 let range = range?; 173 let node = token
174 .value
175 .ancestors()
176 .find(|n| ast::Expr::cast(n.clone()).is_some() || ast::Pat::cast(n.clone()).is_some())?;
177 let frange = FileRange { file_id: position.file_id, range: node.text_range() };
178 res.extend(type_of(db, frange).map(rust_code_markup));
215 if res.is_empty() { 179 if res.is_empty() {
216 return None; 180 return None;
217 } 181 }
182 let range = node.text_range();
183
218 Some(RangeInfo::new(range, res)) 184 Some(RangeInfo::new(range, res))
219} 185}
220 186
187fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
188 return tokens.max_by_key(priority);
189 fn priority(n: &SyntaxToken) -> usize {
190 match n.kind() {
191 IDENT | INT_NUMBER => 3,
192 L_PAREN | R_PAREN => 2,
193 kind if kind.is_trivia() => 0,
194 _ => 1,
195 }
196 }
197}
198
221pub(crate) fn type_of(db: &RootDatabase, frange: FileRange) -> Option<String> { 199pub(crate) fn type_of(db: &RootDatabase, frange: FileRange) -> Option<String> {
222 let parse = db.parse(frange.file_id); 200 let parse = db.parse(frange.file_id);
223 let leaf_node = find_covering_element(parse.tree().syntax(), frange.range); 201 let leaf_node = find_covering_element(parse.tree().syntax(), frange.range);
@@ -227,7 +205,7 @@ pub(crate) fn type_of(db: &RootDatabase, frange: FileRange) -> Option<String> {
227 .take_while(|it| it.text_range() == leaf_node.text_range()) 205 .take_while(|it| it.text_range() == leaf_node.text_range())
228 .find(|it| ast::Expr::cast(it.clone()).is_some() || ast::Pat::cast(it.clone()).is_some())?; 206 .find(|it| ast::Expr::cast(it.clone()).is_some() || ast::Pat::cast(it.clone()).is_some())?;
229 let analyzer = 207 let analyzer =
230 hir::SourceAnalyzer::new(db, hir::Source::new(frange.file_id.into(), &node), None); 208 hir::SourceAnalyzer::new(db, hir::InFile::new(frange.file_id.into(), &node), None);
231 let ty = if let Some(ty) = ast::Expr::cast(node.clone()).and_then(|e| analyzer.type_of(db, &e)) 209 let ty = if let Some(ty) = ast::Expr::cast(node.clone()).and_then(|e| analyzer.type_of(db, &e))
232 { 210 {
233 ty 211 ty
@@ -236,7 +214,7 @@ pub(crate) fn type_of(db: &RootDatabase, frange: FileRange) -> Option<String> {
236 } else { 214 } else {
237 return None; 215 return None;
238 }; 216 };
239 Some(ty.display(db).to_string()) 217 Some(ty.display_truncated(db, None).to_string())
240} 218}
241 219
242#[cfg(test)] 220#[cfg(test)]
@@ -300,7 +278,7 @@ mod tests {
300 &["pub fn foo() -> u32"], 278 &["pub fn foo() -> u32"],
301 ); 279 );
302 280
303 // Multiple results 281 // Multiple candidates but results are ambiguous.
304 check_hover_result( 282 check_hover_result(
305 r#" 283 r#"
306 //- /a.rs 284 //- /a.rs
@@ -321,7 +299,7 @@ mod tests {
321 let foo_test = fo<|>o(); 299 let foo_test = fo<|>o();
322 } 300 }
323 "#, 301 "#,
324 &["pub fn foo() -> &str", "pub fn foo() -> u32", "pub fn foo(a: u32, b: u32)"], 302 &["{unknown}"],
325 ); 303 );
326 } 304 }
327 305
@@ -411,6 +389,23 @@ mod tests {
411 } 389 }
412 390
413 #[test] 391 #[test]
392 fn hover_omits_default_generic_types() {
393 check_hover_result(
394 r#"
395//- /main.rs
396struct Test<K, T = u8> {
397 k: K,
398 t: T,
399}
400
401fn main() {
402 let zz<|> = Test { t: 23, k: 33 };
403}"#,
404 &["Test<i32>"],
405 );
406 }
407
408 #[test]
414 fn hover_some() { 409 fn hover_some() {
415 let (analysis, position) = single_file_with_position( 410 let (analysis, position) = single_file_with_position(
416 " 411 "
@@ -505,6 +500,13 @@ fn func(foo: i32) { if true { <|>foo; }; }
505 } 500 }
506 501
507 #[test] 502 #[test]
503 fn hover_for_param_edge() {
504 let (analysis, position) = single_file_with_position("fn func(<|>foo: i32) {}");
505 let hover = analysis.hover(position).unwrap().unwrap();
506 assert_eq!(trim_markup_opt(hover.info.first()), Some("i32"));
507 }
508
509 #[test]
508 fn test_type_of_for_function() { 510 fn test_type_of_for_function() {
509 let (analysis, range) = single_file_with_range( 511 let (analysis, range) = single_file_with_range(
510 " 512 "