diff options
-rw-r--r-- | crates/ra_ide_api/src/hover.rs | 204 |
1 files changed, 93 insertions, 111 deletions
diff --git a/crates/ra_ide_api/src/hover.rs b/crates/ra_ide_api/src/hover.rs index 086e6dec3..07d511fb3 100644 --- a/crates/ra_ide_api/src/hover.rs +++ b/crates/ra_ide_api/src/hover.rs | |||
@@ -5,7 +5,7 @@ use ra_db::SourceDatabase; | |||
5 | use ra_syntax::{ | 5 | use ra_syntax::{ |
6 | algo::{ancestors_at_offset, find_covering_element, find_node_at_offset}, | 6 | algo::{ancestors_at_offset, find_covering_element, find_node_at_offset}, |
7 | ast::{self, DocCommentsOwner}, | 7 | ast::{self, DocCommentsOwner}, |
8 | match_ast, AstNode, | 8 | AstNode, |
9 | }; | 9 | }; |
10 | 10 | ||
11 | use crate::{ | 11 | use crate::{ |
@@ -14,7 +14,7 @@ use crate::{ | |||
14 | description_from_symbol, docs_from_symbol, macro_label, rust_code_markup, | 14 | description_from_symbol, docs_from_symbol, macro_label, rust_code_markup, |
15 | rust_code_markup_with_doc, ShortLabel, | 15 | rust_code_markup_with_doc, ShortLabel, |
16 | }, | 16 | }, |
17 | references::{classify_name_ref, NameKind::*}, | 17 | references::{classify_name, classify_name_ref, NameKind, NameKind::*}, |
18 | FilePosition, FileRange, RangeInfo, | 18 | FilePosition, FileRange, RangeInfo, |
19 | }; | 19 | }; |
20 | 20 | ||
@@ -92,65 +92,88 @@ fn hover_text(docs: Option<String>, desc: Option<String>) -> Option<String> { | |||
92 | } | 92 | } |
93 | } | 93 | } |
94 | 94 | ||
95 | fn hover_text_from_name_kind( | ||
96 | db: &RootDatabase, | ||
97 | name_kind: NameKind, | ||
98 | no_fallback: &mut bool, | ||
99 | ) -> Option<String> { | ||
100 | return match name_kind { | ||
101 | Macro(it) => { | ||
102 | let src = it.source(db); | ||
103 | hover_text(src.ast.doc_comment_text(), Some(macro_label(&src.ast))) | ||
104 | } | ||
105 | Field(it) => { | ||
106 | let src = it.source(db); | ||
107 | match src.ast { | ||
108 | hir::FieldSource::Named(it) => hover_text(it.doc_comment_text(), it.short_label()), | ||
109 | _ => None, | ||
110 | } | ||
111 | } | ||
112 | AssocItem(it) => match it { | ||
113 | hir::AssocItem::Function(it) => from_def_source(db, it), | ||
114 | hir::AssocItem::Const(it) => from_def_source(db, it), | ||
115 | hir::AssocItem::TypeAlias(it) => from_def_source(db, it), | ||
116 | }, | ||
117 | Def(it) => match it { | ||
118 | hir::ModuleDef::Module(it) => match it.definition_source(db).ast { | ||
119 | hir::ModuleSource::Module(it) => { | ||
120 | hover_text(it.doc_comment_text(), it.short_label()) | ||
121 | } | ||
122 | _ => None, | ||
123 | }, | ||
124 | hir::ModuleDef::Function(it) => from_def_source(db, it), | ||
125 | hir::ModuleDef::Adt(Adt::Struct(it)) => from_def_source(db, it), | ||
126 | hir::ModuleDef::Adt(Adt::Union(it)) => from_def_source(db, it), | ||
127 | hir::ModuleDef::Adt(Adt::Enum(it)) => from_def_source(db, it), | ||
128 | hir::ModuleDef::EnumVariant(it) => from_def_source(db, it), | ||
129 | hir::ModuleDef::Const(it) => from_def_source(db, it), | ||
130 | hir::ModuleDef::Static(it) => from_def_source(db, it), | ||
131 | hir::ModuleDef::Trait(it) => from_def_source(db, it), | ||
132 | hir::ModuleDef::TypeAlias(it) => from_def_source(db, it), | ||
133 | hir::ModuleDef::BuiltinType(it) => Some(it.to_string()), | ||
134 | }, | ||
135 | SelfType(ty) => match ty.as_adt() { | ||
136 | Some((adt_def, _)) => match adt_def { | ||
137 | hir::Adt::Struct(it) => from_def_source(db, it), | ||
138 | hir::Adt::Union(it) => from_def_source(db, it), | ||
139 | hir::Adt::Enum(it) => from_def_source(db, it), | ||
140 | }, | ||
141 | _ => None, | ||
142 | }, | ||
143 | Local(_) => { | ||
144 | // Hover for these shows type names | ||
145 | *no_fallback = true; | ||
146 | None | ||
147 | } | ||
148 | GenericParam(_) => { | ||
149 | // FIXME: Hover for generic param | ||
150 | None | ||
151 | } | ||
152 | }; | ||
153 | |||
154 | fn from_def_source<A, D>(db: &RootDatabase, def: D) -> Option<String> | ||
155 | where | ||
156 | D: HasSource<Ast = A>, | ||
157 | A: ast::DocCommentsOwner + ast::NameOwner + ShortLabel, | ||
158 | { | ||
159 | let src = def.source(db); | ||
160 | hover_text(src.ast.doc_comment_text(), src.ast.short_label()) | ||
161 | } | ||
162 | } | ||
163 | |||
95 | pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<HoverResult>> { | 164 | pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<HoverResult>> { |
96 | let parse = db.parse(position.file_id); | 165 | let parse = db.parse(position.file_id); |
97 | let file = parse.tree(); | 166 | let file = parse.tree(); |
167 | |||
98 | let mut res = HoverResult::new(); | 168 | let mut res = HoverResult::new(); |
99 | 169 | ||
100 | let mut range = None; | 170 | let mut range = if let Some(name_ref) = |
101 | if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(file.syntax(), position.offset) { | 171 | find_node_at_offset::<ast::NameRef>(file.syntax(), position.offset) |
172 | { | ||
102 | let mut no_fallback = false; | 173 | let mut no_fallback = false; |
103 | let name_kind = classify_name_ref(db, position.file_id, &name_ref).map(|d| d.kind); | 174 | if let Some(name_kind) = classify_name_ref(db, position.file_id, &name_ref).map(|d| d.kind) |
104 | match name_kind { | 175 | { |
105 | Some(Macro(it)) => { | 176 | res.extend(hover_text_from_name_kind(db, name_kind, &mut no_fallback)) |
106 | let src = it.source(db); | ||
107 | res.extend(hover_text(src.ast.doc_comment_text(), Some(macro_label(&src.ast)))); | ||
108 | } | ||
109 | Some(Field(it)) => { | ||
110 | let src = it.source(db); | ||
111 | if let hir::FieldSource::Named(it) = src.ast { | ||
112 | res.extend(hover_text(it.doc_comment_text(), it.short_label())); | ||
113 | } | ||
114 | } | ||
115 | Some(AssocItem(it)) => res.extend(match it { | ||
116 | hir::AssocItem::Function(it) => from_def_source(db, it), | ||
117 | hir::AssocItem::Const(it) => from_def_source(db, it), | ||
118 | hir::AssocItem::TypeAlias(it) => from_def_source(db, it), | ||
119 | }), | ||
120 | Some(Def(it)) => match it { | ||
121 | hir::ModuleDef::Module(it) => { | ||
122 | if let hir::ModuleSource::Module(it) = it.definition_source(db).ast { | ||
123 | res.extend(hover_text(it.doc_comment_text(), it.short_label())) | ||
124 | } | ||
125 | } | ||
126 | hir::ModuleDef::Function(it) => res.extend(from_def_source(db, it)), | ||
127 | hir::ModuleDef::Adt(Adt::Struct(it)) => res.extend(from_def_source(db, it)), | ||
128 | hir::ModuleDef::Adt(Adt::Union(it)) => res.extend(from_def_source(db, it)), | ||
129 | hir::ModuleDef::Adt(Adt::Enum(it)) => res.extend(from_def_source(db, it)), | ||
130 | hir::ModuleDef::EnumVariant(it) => res.extend(from_def_source(db, it)), | ||
131 | hir::ModuleDef::Const(it) => res.extend(from_def_source(db, it)), | ||
132 | hir::ModuleDef::Static(it) => res.extend(from_def_source(db, it)), | ||
133 | hir::ModuleDef::Trait(it) => res.extend(from_def_source(db, it)), | ||
134 | hir::ModuleDef::TypeAlias(it) => res.extend(from_def_source(db, it)), | ||
135 | hir::ModuleDef::BuiltinType(it) => res.extend(Some(it.to_string())), | ||
136 | }, | ||
137 | Some(SelfType(ty)) => { | ||
138 | if let Some((adt_def, _)) = ty.as_adt() { | ||
139 | res.extend(match adt_def { | ||
140 | hir::Adt::Struct(it) => from_def_source(db, it), | ||
141 | hir::Adt::Union(it) => from_def_source(db, it), | ||
142 | hir::Adt::Enum(it) => from_def_source(db, it), | ||
143 | }) | ||
144 | } | ||
145 | } | ||
146 | Some(Local(_)) => { | ||
147 | // Hover for these shows type names | ||
148 | no_fallback = true; | ||
149 | } | ||
150 | Some(GenericParam(_)) => { | ||
151 | // FIXME: Hover for generic param | ||
152 | } | ||
153 | None => {} | ||
154 | } | 177 | } |
155 | 178 | ||
156 | if res.is_empty() && !no_fallback { | 179 | if res.is_empty() && !no_fallback { |
@@ -164,55 +187,24 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn | |||
164 | } | 187 | } |
165 | 188 | ||
166 | if !res.is_empty() { | 189 | if !res.is_empty() { |
167 | range = Some(name_ref.syntax().text_range()) | 190 | Some(name_ref.syntax().text_range()) |
191 | } else { | ||
192 | None | ||
168 | } | 193 | } |
169 | } else if let Some(name) = find_node_at_offset::<ast::Name>(file.syntax(), position.offset) { | 194 | } else if let Some(name) = find_node_at_offset::<ast::Name>(file.syntax(), position.offset) { |
170 | if let Some(parent) = name.syntax().parent() { | 195 | if let Some(name_kind) = classify_name(db, position.file_id, &name).map(|d| d.kind) { |
171 | let text = match_ast! { | 196 | let mut _b: bool = true; |
172 | match parent { | 197 | res.extend(hover_text_from_name_kind(db, name_kind, &mut _b)); |
173 | ast::StructDef(it) => { | ||
174 | hover_text(it.doc_comment_text(), it.short_label()) | ||
175 | }, | ||
176 | ast::EnumDef(it) => { | ||
177 | hover_text(it.doc_comment_text(), it.short_label()) | ||
178 | }, | ||
179 | ast::EnumVariant(it) => { | ||
180 | hover_text(it.doc_comment_text(), it.short_label()) | ||
181 | }, | ||
182 | ast::FnDef(it) => { | ||
183 | hover_text(it.doc_comment_text(), it.short_label()) | ||
184 | }, | ||
185 | ast::TypeAliasDef(it) => { | ||
186 | hover_text(it.doc_comment_text(), it.short_label()) | ||
187 | }, | ||
188 | ast::ConstDef(it) => { | ||
189 | hover_text(it.doc_comment_text(), it.short_label()) | ||
190 | }, | ||
191 | ast::StaticDef(it) => { | ||
192 | hover_text(it.doc_comment_text(), it.short_label()) | ||
193 | }, | ||
194 | ast::TraitDef(it) => { | ||
195 | hover_text(it.doc_comment_text(), it.short_label()) | ||
196 | }, | ||
197 | ast::RecordFieldDef(it) => { | ||
198 | hover_text(it.doc_comment_text(), it.short_label()) | ||
199 | }, | ||
200 | ast::Module(it) => { | ||
201 | hover_text(it.doc_comment_text(), it.short_label()) | ||
202 | }, | ||
203 | ast::MacroCall(it) => { | ||
204 | hover_text(it.doc_comment_text(), None) | ||
205 | }, | ||
206 | _ => None, | ||
207 | } | ||
208 | }; | ||
209 | res.extend(text); | ||
210 | } | 198 | } |
211 | 199 | ||
212 | if !res.is_empty() && range.is_none() { | 200 | if !res.is_empty() { |
213 | range = Some(name.syntax().text_range()); | 201 | Some(name.syntax().text_range()) |
202 | } else { | ||
203 | None | ||
214 | } | 204 | } |
215 | } | 205 | } else { |
206 | None | ||
207 | }; | ||
216 | 208 | ||
217 | if range.is_none() { | 209 | if range.is_none() { |
218 | let node = ancestors_at_offset(file.syntax(), position.offset).find(|n| { | 210 | let node = ancestors_at_offset(file.syntax(), position.offset).find(|n| { |
@@ -221,23 +213,13 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn | |||
221 | let frange = FileRange { file_id: position.file_id, range: node.text_range() }; | 213 | let frange = FileRange { file_id: position.file_id, range: node.text_range() }; |
222 | res.extend(type_of(db, frange).map(rust_code_markup)); | 214 | res.extend(type_of(db, frange).map(rust_code_markup)); |
223 | range = Some(node.text_range()); | 215 | range = Some(node.text_range()); |
224 | } | 216 | }; |
225 | 217 | ||
226 | let range = range?; | 218 | let range = range?; |
227 | if res.is_empty() { | 219 | if res.is_empty() { |
228 | return None; | 220 | return None; |
229 | } | 221 | } |
230 | let res = RangeInfo::new(range, res); | 222 | Some(RangeInfo::new(range, res)) |
231 | return Some(res); | ||
232 | |||
233 | fn from_def_source<A, D>(db: &RootDatabase, def: D) -> Option<String> | ||
234 | where | ||
235 | D: HasSource<Ast = A>, | ||
236 | A: ast::DocCommentsOwner + ast::NameOwner + ShortLabel, | ||
237 | { | ||
238 | let src = def.source(db); | ||
239 | hover_text(src.ast.doc_comment_text(), src.ast.short_label()) | ||
240 | } | ||
241 | } | 223 | } |
242 | 224 | ||
243 | pub(crate) fn type_of(db: &RootDatabase, frange: FileRange) -> Option<String> { | 225 | pub(crate) fn type_of(db: &RootDatabase, frange: FileRange) -> Option<String> { |