diff options
Diffstat (limited to 'crates/ide')
-rw-r--r-- | crates/ide/src/completion/complete_postfix/format_like.rs | 2 | ||||
-rw-r--r-- | crates/ide/src/completion/complete_unqualified_path.rs | 20 | ||||
-rw-r--r-- | crates/ide/src/completion/completion_context.rs | 5 | ||||
-rw-r--r-- | crates/ide/src/hover.rs | 83 | ||||
-rw-r--r-- | crates/ide/src/inlay_hints.rs | 233 | ||||
-rw-r--r-- | crates/ide/src/lib.rs | 4 | ||||
-rw-r--r-- | crates/ide/src/link_rewrite.rs | 6 | ||||
-rw-r--r-- | crates/ide/src/markdown_remove.rs | 23 | ||||
-rw-r--r-- | crates/ide/src/status.rs | 2 | ||||
-rw-r--r-- | crates/ide/src/typing/on_enter.rs | 29 |
10 files changed, 350 insertions, 57 deletions
diff --git a/crates/ide/src/completion/complete_postfix/format_like.rs b/crates/ide/src/completion/complete_postfix/format_like.rs index 0287fc803..81c33bf3a 100644 --- a/crates/ide/src/completion/complete_postfix/format_like.rs +++ b/crates/ide/src/completion/complete_postfix/format_like.rs | |||
@@ -1,4 +1,4 @@ | |||
1 | // Feature: Postfix completion for `format`-like strings. | 1 | // Feature: Format String Completion. |
2 | // | 2 | // |
3 | // `"Result {result} is {2 + 2}"` is expanded to the `"Result {} is {}", result, 2 + 2`. | 3 | // `"Result {result} is {2 + 2}"` is expanded to the `"Result {} is {}", result, 2 + 2`. |
4 | // | 4 | // |
diff --git a/crates/ide/src/completion/complete_unqualified_path.rs b/crates/ide/src/completion/complete_unqualified_path.rs index 2010d9a2f..8b6757195 100644 --- a/crates/ide/src/completion/complete_unqualified_path.rs +++ b/crates/ide/src/completion/complete_unqualified_path.rs | |||
@@ -267,6 +267,26 @@ fn quux() { <|> } | |||
267 | ); | 267 | ); |
268 | } | 268 | } |
269 | 269 | ||
270 | /// Regression test for issue #6091. | ||
271 | #[test] | ||
272 | fn correctly_completes_module_items_prefixed_with_underscore() { | ||
273 | check_edit( | ||
274 | "_alpha", | ||
275 | r#" | ||
276 | fn main() { | ||
277 | _<|> | ||
278 | } | ||
279 | fn _alpha() {} | ||
280 | "#, | ||
281 | r#" | ||
282 | fn main() { | ||
283 | _alpha()$0 | ||
284 | } | ||
285 | fn _alpha() {} | ||
286 | "#, | ||
287 | ) | ||
288 | } | ||
289 | |||
270 | #[test] | 290 | #[test] |
271 | fn completes_extern_prelude() { | 291 | fn completes_extern_prelude() { |
272 | check( | 292 | check( |
diff --git a/crates/ide/src/completion/completion_context.rs b/crates/ide/src/completion/completion_context.rs index 101be8eb5..8dea8a4bf 100644 --- a/crates/ide/src/completion/completion_context.rs +++ b/crates/ide/src/completion/completion_context.rs | |||
@@ -221,10 +221,11 @@ impl<'a> CompletionContext<'a> { | |||
221 | Some(ctx) | 221 | Some(ctx) |
222 | } | 222 | } |
223 | 223 | ||
224 | // The range of the identifier that is being completed. | 224 | /// The range of the identifier that is being completed. |
225 | pub(crate) fn source_range(&self) -> TextRange { | 225 | pub(crate) fn source_range(&self) -> TextRange { |
226 | // check kind of macro-expanded token, but use range of original token | 226 | // check kind of macro-expanded token, but use range of original token |
227 | if self.token.kind() == IDENT || self.token.kind().is_keyword() { | 227 | let kind = self.token.kind(); |
228 | if kind == IDENT || kind == UNDERSCORE || kind.is_keyword() { | ||
228 | mark::hit!(completes_if_prefix_is_keyword); | 229 | mark::hit!(completes_if_prefix_is_keyword); |
229 | self.original_token.text_range() | 230 | self.original_token.text_range() |
230 | } else { | 231 | } else { |
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index 9cf02f0a3..53265488e 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs | |||
@@ -15,6 +15,7 @@ use test_utils::mark; | |||
15 | use crate::{ | 15 | use crate::{ |
16 | display::{macro_label, ShortLabel, ToNav, TryToNav}, | 16 | display::{macro_label, ShortLabel, ToNav, TryToNav}, |
17 | link_rewrite::{remove_links, rewrite_links}, | 17 | link_rewrite::{remove_links, rewrite_links}, |
18 | markdown_remove::remove_markdown, | ||
18 | markup::Markup, | 19 | markup::Markup, |
19 | runnables::runnable, | 20 | runnables::runnable, |
20 | FileId, FilePosition, NavigationTarget, RangeInfo, Runnable, | 21 | FileId, FilePosition, NavigationTarget, RangeInfo, Runnable, |
@@ -27,6 +28,7 @@ pub struct HoverConfig { | |||
27 | pub debug: bool, | 28 | pub debug: bool, |
28 | pub goto_type_def: bool, | 29 | pub goto_type_def: bool, |
29 | pub links_in_hover: bool, | 30 | pub links_in_hover: bool, |
31 | pub markdown: bool, | ||
30 | } | 32 | } |
31 | 33 | ||
32 | impl Default for HoverConfig { | 34 | impl Default for HoverConfig { |
@@ -37,6 +39,7 @@ impl Default for HoverConfig { | |||
37 | debug: true, | 39 | debug: true, |
38 | goto_type_def: true, | 40 | goto_type_def: true, |
39 | links_in_hover: true, | 41 | links_in_hover: true, |
42 | markdown: true, | ||
40 | } | 43 | } |
41 | } | 44 | } |
42 | } | 45 | } |
@@ -48,6 +51,7 @@ impl HoverConfig { | |||
48 | debug: false, | 51 | debug: false, |
49 | goto_type_def: false, | 52 | goto_type_def: false, |
50 | links_in_hover: true, | 53 | links_in_hover: true, |
54 | markdown: true, | ||
51 | }; | 55 | }; |
52 | 56 | ||
53 | pub fn any(&self) -> bool { | 57 | pub fn any(&self) -> bool { |
@@ -91,6 +95,7 @@ pub(crate) fn hover( | |||
91 | db: &RootDatabase, | 95 | db: &RootDatabase, |
92 | position: FilePosition, | 96 | position: FilePosition, |
93 | links_in_hover: bool, | 97 | links_in_hover: bool, |
98 | markdown: bool, | ||
94 | ) -> Option<RangeInfo<HoverResult>> { | 99 | ) -> Option<RangeInfo<HoverResult>> { |
95 | let sema = Semantics::new(db); | 100 | let sema = Semantics::new(db); |
96 | let file = sema.parse(position.file_id).syntax().clone(); | 101 | let file = sema.parse(position.file_id).syntax().clone(); |
@@ -109,7 +114,9 @@ pub(crate) fn hover( | |||
109 | }; | 114 | }; |
110 | if let Some(definition) = definition { | 115 | if let Some(definition) = definition { |
111 | if let Some(markup) = hover_for_definition(db, definition) { | 116 | if let Some(markup) = hover_for_definition(db, definition) { |
112 | let markup = if links_in_hover { | 117 | let markup = if !markdown { |
118 | remove_markdown(&markup.as_str()) | ||
119 | } else if links_in_hover { | ||
113 | rewrite_links(db, &markup.as_str(), &definition) | 120 | rewrite_links(db, &markup.as_str(), &definition) |
114 | } else { | 121 | } else { |
115 | remove_links(&markup.as_str()) | 122 | remove_links(&markup.as_str()) |
@@ -147,7 +154,11 @@ pub(crate) fn hover( | |||
147 | } | 154 | } |
148 | }; | 155 | }; |
149 | 156 | ||
150 | res.markup = Markup::fenced_block(&ty.display(db)); | 157 | res.markup = if markdown { |
158 | Markup::fenced_block(&ty.display(db)) | ||
159 | } else { | ||
160 | ty.display(db).to_string().into() | ||
161 | }; | ||
151 | let range = sema.original_range(&node).range; | 162 | let range = sema.original_range(&node).range; |
152 | Some(RangeInfo::new(range, res)) | 163 | Some(RangeInfo::new(range, res)) |
153 | } | 164 | } |
@@ -289,7 +300,7 @@ fn definition_owner_name(db: &RootDatabase, def: &Definition) -> Option<String> | |||
289 | 300 | ||
290 | fn render_path(db: &RootDatabase, module: Module, item_name: Option<String>) -> String { | 301 | fn render_path(db: &RootDatabase, module: Module, item_name: Option<String>) -> String { |
291 | let crate_name = | 302 | let crate_name = |
292 | db.crate_graph()[module.krate().into()].display_name.as_ref().map(ToString::to_string); | 303 | db.crate_graph()[module.krate().into()].declaration_name.as_ref().map(ToString::to_string); |
293 | let module_path = module | 304 | let module_path = module |
294 | .path_to_root(db) | 305 | .path_to_root(db) |
295 | .into_iter() | 306 | .into_iter() |
@@ -383,12 +394,12 @@ mod tests { | |||
383 | 394 | ||
384 | fn check_hover_no_result(ra_fixture: &str) { | 395 | fn check_hover_no_result(ra_fixture: &str) { |
385 | let (analysis, position) = fixture::position(ra_fixture); | 396 | let (analysis, position) = fixture::position(ra_fixture); |
386 | assert!(analysis.hover(position, true).unwrap().is_none()); | 397 | assert!(analysis.hover(position, true, true).unwrap().is_none()); |
387 | } | 398 | } |
388 | 399 | ||
389 | fn check(ra_fixture: &str, expect: Expect) { | 400 | fn check(ra_fixture: &str, expect: Expect) { |
390 | let (analysis, position) = fixture::position(ra_fixture); | 401 | let (analysis, position) = fixture::position(ra_fixture); |
391 | let hover = analysis.hover(position, true).unwrap().unwrap(); | 402 | let hover = analysis.hover(position, true, true).unwrap().unwrap(); |
392 | 403 | ||
393 | let content = analysis.db.file_text(position.file_id); | 404 | let content = analysis.db.file_text(position.file_id); |
394 | let hovered_element = &content[hover.range]; | 405 | let hovered_element = &content[hover.range]; |
@@ -399,7 +410,18 @@ mod tests { | |||
399 | 410 | ||
400 | fn check_hover_no_links(ra_fixture: &str, expect: Expect) { | 411 | fn check_hover_no_links(ra_fixture: &str, expect: Expect) { |
401 | let (analysis, position) = fixture::position(ra_fixture); | 412 | let (analysis, position) = fixture::position(ra_fixture); |
402 | let hover = analysis.hover(position, false).unwrap().unwrap(); | 413 | let hover = analysis.hover(position, false, true).unwrap().unwrap(); |
414 | |||
415 | let content = analysis.db.file_text(position.file_id); | ||
416 | let hovered_element = &content[hover.range]; | ||
417 | |||
418 | let actual = format!("*{}*\n{}\n", hovered_element, hover.info.markup); | ||
419 | expect.assert_eq(&actual) | ||
420 | } | ||
421 | |||
422 | fn check_hover_no_markdown(ra_fixture: &str, expect: Expect) { | ||
423 | let (analysis, position) = fixture::position(ra_fixture); | ||
424 | let hover = analysis.hover(position, true, false).unwrap().unwrap(); | ||
403 | 425 | ||
404 | let content = analysis.db.file_text(position.file_id); | 426 | let content = analysis.db.file_text(position.file_id); |
405 | let hovered_element = &content[hover.range]; | 427 | let hovered_element = &content[hover.range]; |
@@ -410,7 +432,7 @@ mod tests { | |||
410 | 432 | ||
411 | fn check_actions(ra_fixture: &str, expect: Expect) { | 433 | fn check_actions(ra_fixture: &str, expect: Expect) { |
412 | let (analysis, position) = fixture::position(ra_fixture); | 434 | let (analysis, position) = fixture::position(ra_fixture); |
413 | let hover = analysis.hover(position, true).unwrap().unwrap(); | 435 | let hover = analysis.hover(position, true, true).unwrap().unwrap(); |
414 | expect.assert_debug_eq(&hover.info.actions) | 436 | expect.assert_debug_eq(&hover.info.actions) |
415 | } | 437 | } |
416 | 438 | ||
@@ -434,6 +456,23 @@ fn main() { | |||
434 | } | 456 | } |
435 | 457 | ||
436 | #[test] | 458 | #[test] |
459 | fn hover_remove_markdown_if_configured() { | ||
460 | check_hover_no_markdown( | ||
461 | r#" | ||
462 | pub fn foo() -> u32 { 1 } | ||
463 | |||
464 | fn main() { | ||
465 | let foo_test = foo()<|>; | ||
466 | } | ||
467 | "#, | ||
468 | expect![[r#" | ||
469 | *foo()* | ||
470 | u32 | ||
471 | "#]], | ||
472 | ); | ||
473 | } | ||
474 | |||
475 | #[test] | ||
437 | fn hover_shows_long_type_of_an_expression() { | 476 | fn hover_shows_long_type_of_an_expression() { |
438 | check( | 477 | check( |
439 | r#" | 478 | r#" |
@@ -3163,4 +3202,34 @@ fn main() { let s<|>t = test().get(); } | |||
3163 | "#]], | 3202 | "#]], |
3164 | ); | 3203 | ); |
3165 | } | 3204 | } |
3205 | |||
3206 | #[test] | ||
3207 | fn hover_displays_normalized_crate_names() { | ||
3208 | check( | ||
3209 | r#" | ||
3210 | //- /lib.rs crate:name-with-dashes | ||
3211 | pub mod wrapper { | ||
3212 | pub struct Thing { x: u32 } | ||
3213 | |||
3214 | impl Thing { | ||
3215 | pub fn new() -> Thing { Thing { x: 0 } } | ||
3216 | } | ||
3217 | } | ||
3218 | |||
3219 | //- /main.rs crate:main deps:name-with-dashes | ||
3220 | fn main() { let foo_test = name_with_dashes::wrapper::Thing::new<|>(); } | ||
3221 | "#, | ||
3222 | expect![[r#" | ||
3223 | *new* | ||
3224 | |||
3225 | ```rust | ||
3226 | name_with_dashes::wrapper::Thing | ||
3227 | ``` | ||
3228 | |||
3229 | ```rust | ||
3230 | pub fn new() -> Thing | ||
3231 | ``` | ||
3232 | "#]], | ||
3233 | ) | ||
3234 | } | ||
3166 | } | 3235 | } |
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs index 1d7e8de56..7d716577e 100644 --- a/crates/ide/src/inlay_hints.rs +++ b/crates/ide/src/inlay_hints.rs | |||
@@ -1,4 +1,5 @@ | |||
1 | use hir::{Adt, Callable, HirDisplay, Semantics, Type}; | 1 | use assists::utils::FamousDefs; |
2 | use hir::{known, HirDisplay, Semantics}; | ||
2 | use ide_db::RootDatabase; | 3 | use ide_db::RootDatabase; |
3 | use stdx::to_lower_snake_case; | 4 | use stdx::to_lower_snake_case; |
4 | use syntax::{ | 5 | use syntax::{ |
@@ -119,17 +120,18 @@ fn get_chaining_hints( | |||
119 | return None; | 120 | return None; |
120 | } | 121 | } |
121 | if matches!(expr, ast::Expr::PathExpr(_)) { | 122 | if matches!(expr, ast::Expr::PathExpr(_)) { |
122 | if let Some(Adt::Struct(st)) = ty.as_adt() { | 123 | if let Some(hir::Adt::Struct(st)) = ty.as_adt() { |
123 | if st.fields(sema.db).is_empty() { | 124 | if st.fields(sema.db).is_empty() { |
124 | return None; | 125 | return None; |
125 | } | 126 | } |
126 | } | 127 | } |
127 | } | 128 | } |
128 | let label = ty.display_truncated(sema.db, config.max_length).to_string(); | ||
129 | acc.push(InlayHint { | 129 | acc.push(InlayHint { |
130 | range: expr.syntax().text_range(), | 130 | range: expr.syntax().text_range(), |
131 | kind: InlayKind::ChainingHint, | 131 | kind: InlayKind::ChainingHint, |
132 | label: label.into(), | 132 | label: hint_iterator(sema, config, &ty).unwrap_or_else(|| { |
133 | ty.display_truncated(sema.db, config.max_length).to_string().into() | ||
134 | }), | ||
133 | }); | 135 | }); |
134 | } | 136 | } |
135 | Some(()) | 137 | Some(()) |
@@ -192,17 +194,58 @@ fn get_bind_pat_hints( | |||
192 | if should_not_display_type_hint(sema, &pat, &ty) { | 194 | if should_not_display_type_hint(sema, &pat, &ty) { |
193 | return None; | 195 | return None; |
194 | } | 196 | } |
195 | |||
196 | acc.push(InlayHint { | 197 | acc.push(InlayHint { |
197 | range: pat.syntax().text_range(), | 198 | range: pat.syntax().text_range(), |
198 | kind: InlayKind::TypeHint, | 199 | kind: InlayKind::TypeHint, |
199 | label: ty.display_truncated(sema.db, config.max_length).to_string().into(), | 200 | label: hint_iterator(sema, config, &ty) |
201 | .unwrap_or_else(|| ty.display_truncated(sema.db, config.max_length).to_string().into()), | ||
200 | }); | 202 | }); |
203 | |||
201 | Some(()) | 204 | Some(()) |
202 | } | 205 | } |
203 | 206 | ||
204 | fn pat_is_enum_variant(db: &RootDatabase, bind_pat: &ast::IdentPat, pat_ty: &Type) -> bool { | 207 | /// Checks if the type is an Iterator from std::iter and replaces its hint with an `impl Iterator<Item = Ty>`. |
205 | if let Some(Adt::Enum(enum_data)) = pat_ty.as_adt() { | 208 | fn hint_iterator( |
209 | sema: &Semantics<RootDatabase>, | ||
210 | config: &InlayHintsConfig, | ||
211 | ty: &hir::Type, | ||
212 | ) -> Option<SmolStr> { | ||
213 | let db = sema.db; | ||
214 | let strukt = std::iter::successors(Some(ty.clone()), |ty| ty.remove_ref()) | ||
215 | .last() | ||
216 | .and_then(|strukt| strukt.as_adt())?; | ||
217 | let krate = strukt.krate(db)?; | ||
218 | if krate.declaration_name(db).as_deref() != Some("core") { | ||
219 | return None; | ||
220 | } | ||
221 | let iter_trait = FamousDefs(sema, krate).core_iter_Iterator()?; | ||
222 | let iter_mod = FamousDefs(sema, krate).core_iter()?; | ||
223 | // assert this type comes from `core::iter` | ||
224 | iter_mod.visibility_of(db, &iter_trait.into()).filter(|&vis| vis == hir::Visibility::Public)?; | ||
225 | if ty.impls_trait(db, iter_trait, &[]) { | ||
226 | let assoc_type_item = iter_trait.items(db).into_iter().find_map(|item| match item { | ||
227 | hir::AssocItem::TypeAlias(alias) if alias.name(db) == known::Item => Some(alias), | ||
228 | _ => None, | ||
229 | })?; | ||
230 | if let Some(ty) = ty.normalize_trait_assoc_type(db, iter_trait, &[], assoc_type_item) { | ||
231 | const LABEL_START: &str = "impl Iterator<Item = "; | ||
232 | const LABEL_END: &str = ">"; | ||
233 | |||
234 | let ty_display = ty.display_truncated( | ||
235 | db, | ||
236 | config | ||
237 | .max_length | ||
238 | .map(|len| len.saturating_sub(LABEL_START.len() + LABEL_END.len())), | ||
239 | ); | ||
240 | return Some(format!("{}{}{}", LABEL_START, ty_display, LABEL_END).into()); | ||
241 | } | ||
242 | } | ||
243 | |||
244 | None | ||
245 | } | ||
246 | |||
247 | fn pat_is_enum_variant(db: &RootDatabase, bind_pat: &ast::IdentPat, pat_ty: &hir::Type) -> bool { | ||
248 | if let Some(hir::Adt::Enum(enum_data)) = pat_ty.as_adt() { | ||
206 | let pat_text = bind_pat.to_string(); | 249 | let pat_text = bind_pat.to_string(); |
207 | enum_data | 250 | enum_data |
208 | .variants(db) | 251 | .variants(db) |
@@ -217,7 +260,7 @@ fn pat_is_enum_variant(db: &RootDatabase, bind_pat: &ast::IdentPat, pat_ty: &Typ | |||
217 | fn should_not_display_type_hint( | 260 | fn should_not_display_type_hint( |
218 | sema: &Semantics<RootDatabase>, | 261 | sema: &Semantics<RootDatabase>, |
219 | bind_pat: &ast::IdentPat, | 262 | bind_pat: &ast::IdentPat, |
220 | pat_ty: &Type, | 263 | pat_ty: &hir::Type, |
221 | ) -> bool { | 264 | ) -> bool { |
222 | let db = sema.db; | 265 | let db = sema.db; |
223 | 266 | ||
@@ -225,7 +268,7 @@ fn should_not_display_type_hint( | |||
225 | return true; | 268 | return true; |
226 | } | 269 | } |
227 | 270 | ||
228 | if let Some(Adt::Struct(s)) = pat_ty.as_adt() { | 271 | if let Some(hir::Adt::Struct(s)) = pat_ty.as_adt() { |
229 | if s.fields(db).is_empty() && s.name(db).to_string() == bind_pat.to_string() { | 272 | if s.fields(db).is_empty() && s.name(db).to_string() == bind_pat.to_string() { |
230 | return true; | 273 | return true; |
231 | } | 274 | } |
@@ -269,7 +312,7 @@ fn should_not_display_type_hint( | |||
269 | 312 | ||
270 | fn should_show_param_name_hint( | 313 | fn should_show_param_name_hint( |
271 | sema: &Semantics<RootDatabase>, | 314 | sema: &Semantics<RootDatabase>, |
272 | callable: &Callable, | 315 | callable: &hir::Callable, |
273 | param_name: &str, | 316 | param_name: &str, |
274 | argument: &ast::Expr, | 317 | argument: &ast::Expr, |
275 | ) -> bool { | 318 | ) -> bool { |
@@ -316,7 +359,7 @@ fn is_enum_name_similar_to_param_name( | |||
316 | param_name: &str, | 359 | param_name: &str, |
317 | ) -> bool { | 360 | ) -> bool { |
318 | match sema.type_of_expr(argument).and_then(|t| t.as_adt()) { | 361 | match sema.type_of_expr(argument).and_then(|t| t.as_adt()) { |
319 | Some(Adt::Enum(e)) => to_lower_snake_case(&e.name(sema.db).to_string()) == param_name, | 362 | Some(hir::Adt::Enum(e)) => to_lower_snake_case(&e.name(sema.db).to_string()) == param_name, |
320 | _ => false, | 363 | _ => false, |
321 | } | 364 | } |
322 | } | 365 | } |
@@ -337,7 +380,7 @@ fn is_obvious_param(param_name: &str) -> bool { | |||
337 | param_name.len() == 1 || is_obvious_param_name | 380 | param_name.len() == 1 || is_obvious_param_name |
338 | } | 381 | } |
339 | 382 | ||
340 | fn get_callable(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<Callable> { | 383 | fn get_callable(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<hir::Callable> { |
341 | match expr { | 384 | match expr { |
342 | ast::Expr::CallExpr(expr) => sema.type_of_expr(&expr.expr()?)?.as_callable(sema.db), | 385 | ast::Expr::CallExpr(expr) => sema.type_of_expr(&expr.expr()?)?.as_callable(sema.db), |
343 | ast::Expr::MethodCallExpr(expr) => sema.resolve_method_call_as_callable(expr), | 386 | ast::Expr::MethodCallExpr(expr) => sema.resolve_method_call_as_callable(expr), |
@@ -347,6 +390,7 @@ fn get_callable(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<Call | |||
347 | 390 | ||
348 | #[cfg(test)] | 391 | #[cfg(test)] |
349 | mod tests { | 392 | mod tests { |
393 | use assists::utils::FamousDefs; | ||
350 | use expect_test::{expect, Expect}; | 394 | use expect_test::{expect, Expect}; |
351 | use test_utils::extract_annotations; | 395 | use test_utils::extract_annotations; |
352 | 396 | ||
@@ -357,7 +401,9 @@ mod tests { | |||
357 | } | 401 | } |
358 | 402 | ||
359 | fn check_with_config(config: InlayHintsConfig, ra_fixture: &str) { | 403 | fn check_with_config(config: InlayHintsConfig, ra_fixture: &str) { |
360 | let (analysis, file_id) = fixture::file(ra_fixture); | 404 | let ra_fixture = |
405 | format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE); | ||
406 | let (analysis, file_id) = fixture::file(&ra_fixture); | ||
361 | let expected = extract_annotations(&*analysis.file_text(file_id).unwrap()); | 407 | let expected = extract_annotations(&*analysis.file_text(file_id).unwrap()); |
362 | let inlay_hints = analysis.inlay_hints(file_id, &config).unwrap(); | 408 | let inlay_hints = analysis.inlay_hints(file_id, &config).unwrap(); |
363 | let actual = | 409 | let actual = |
@@ -366,7 +412,9 @@ mod tests { | |||
366 | } | 412 | } |
367 | 413 | ||
368 | fn check_expect(config: InlayHintsConfig, ra_fixture: &str, expect: Expect) { | 414 | fn check_expect(config: InlayHintsConfig, ra_fixture: &str, expect: Expect) { |
369 | let (analysis, file_id) = fixture::file(ra_fixture); | 415 | let ra_fixture = |
416 | format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE); | ||
417 | let (analysis, file_id) = fixture::file(&ra_fixture); | ||
370 | let inlay_hints = analysis.inlay_hints(file_id, &config).unwrap(); | 418 | let inlay_hints = analysis.inlay_hints(file_id, &config).unwrap(); |
371 | expect.assert_debug_eq(&inlay_hints) | 419 | expect.assert_debug_eq(&inlay_hints) |
372 | } | 420 | } |
@@ -798,12 +846,12 @@ fn main() { | |||
798 | expect![[r#" | 846 | expect![[r#" |
799 | [ | 847 | [ |
800 | InlayHint { | 848 | InlayHint { |
801 | range: 147..172, | 849 | range: 148..173, |
802 | kind: ChainingHint, | 850 | kind: ChainingHint, |
803 | label: "B", | 851 | label: "B", |
804 | }, | 852 | }, |
805 | InlayHint { | 853 | InlayHint { |
806 | range: 147..154, | 854 | range: 148..155, |
807 | kind: ChainingHint, | 855 | kind: ChainingHint, |
808 | label: "A", | 856 | label: "A", |
809 | }, | 857 | }, |
@@ -864,12 +912,12 @@ fn main() { | |||
864 | expect![[r#" | 912 | expect![[r#" |
865 | [ | 913 | [ |
866 | InlayHint { | 914 | InlayHint { |
867 | range: 143..190, | 915 | range: 144..191, |
868 | kind: ChainingHint, | 916 | kind: ChainingHint, |
869 | label: "C", | 917 | label: "C", |
870 | }, | 918 | }, |
871 | InlayHint { | 919 | InlayHint { |
872 | range: 143..179, | 920 | range: 144..180, |
873 | kind: ChainingHint, | 921 | kind: ChainingHint, |
874 | label: "B", | 922 | label: "B", |
875 | }, | 923 | }, |
@@ -909,12 +957,12 @@ fn main() { | |||
909 | expect![[r#" | 957 | expect![[r#" |
910 | [ | 958 | [ |
911 | InlayHint { | 959 | InlayHint { |
912 | range: 246..283, | 960 | range: 247..284, |
913 | kind: ChainingHint, | 961 | kind: ChainingHint, |
914 | label: "B<X<i32, bool>>", | 962 | label: "B<X<i32, bool>>", |
915 | }, | 963 | }, |
916 | InlayHint { | 964 | InlayHint { |
917 | range: 246..265, | 965 | range: 247..266, |
918 | kind: ChainingHint, | 966 | kind: ChainingHint, |
919 | label: "A<X<i32, bool>>", | 967 | label: "A<X<i32, bool>>", |
920 | }, | 968 | }, |
@@ -935,7 +983,6 @@ fn main() { | |||
935 | ); | 983 | ); |
936 | check( | 984 | check( |
937 | r#" | 985 | r#" |
938 | //- /main.rs crate:main deps:core | ||
939 | pub struct Vec<T> {} | 986 | pub struct Vec<T> {} |
940 | 987 | ||
941 | impl<T> Vec<T> { | 988 | impl<T> Vec<T> { |
@@ -956,13 +1003,6 @@ fn main() { | |||
956 | println!("Unit expr"); | 1003 | println!("Unit expr"); |
957 | } | 1004 | } |
958 | 1005 | ||
959 | //- /core.rs crate:core | ||
960 | #[prelude_import] use iter::*; | ||
961 | mod iter { | ||
962 | trait IntoIterator { | ||
963 | type Item; | ||
964 | } | ||
965 | } | ||
966 | //- /alloc.rs crate:alloc deps:core | 1006 | //- /alloc.rs crate:alloc deps:core |
967 | mod collections { | 1007 | mod collections { |
968 | struct Vec<T> {} | 1008 | struct Vec<T> {} |
@@ -982,7 +1022,6 @@ mod collections { | |||
982 | fn complete_for_hint() { | 1022 | fn complete_for_hint() { |
983 | check( | 1023 | check( |
984 | r#" | 1024 | r#" |
985 | //- /main.rs crate:main deps:core | ||
986 | pub struct Vec<T> {} | 1025 | pub struct Vec<T> {} |
987 | 1026 | ||
988 | impl<T> Vec<T> { | 1027 | impl<T> Vec<T> { |
@@ -1004,14 +1043,6 @@ fn main() { | |||
1004 | //^ &str | 1043 | //^ &str |
1005 | } | 1044 | } |
1006 | } | 1045 | } |
1007 | |||
1008 | //- /core.rs crate:core | ||
1009 | #[prelude_import] use iter::*; | ||
1010 | mod iter { | ||
1011 | trait IntoIterator { | ||
1012 | type Item; | ||
1013 | } | ||
1014 | } | ||
1015 | //- /alloc.rs crate:alloc deps:core | 1046 | //- /alloc.rs crate:alloc deps:core |
1016 | mod collections { | 1047 | mod collections { |
1017 | struct Vec<T> {} | 1048 | struct Vec<T> {} |
@@ -1026,4 +1057,130 @@ mod collections { | |||
1026 | "#, | 1057 | "#, |
1027 | ); | 1058 | ); |
1028 | } | 1059 | } |
1060 | |||
1061 | #[test] | ||
1062 | fn multi_dyn_trait_bounds() { | ||
1063 | check_with_config( | ||
1064 | InlayHintsConfig { | ||
1065 | type_hints: true, | ||
1066 | parameter_hints: false, | ||
1067 | chaining_hints: false, | ||
1068 | max_length: None, | ||
1069 | }, | ||
1070 | r#" | ||
1071 | pub struct Vec<T> {} | ||
1072 | |||
1073 | impl<T> Vec<T> { | ||
1074 | pub fn new() -> Self { Vec {} } | ||
1075 | } | ||
1076 | |||
1077 | pub struct Box<T> {} | ||
1078 | |||
1079 | trait Display {} | ||
1080 | trait Sync {} | ||
1081 | |||
1082 | fn main() { | ||
1083 | let _v = Vec::<Box<&(dyn Display + Sync)>>::new(); | ||
1084 | //^^ Vec<Box<&(dyn Display + Sync)>> | ||
1085 | let _v = Vec::<Box<*const (dyn Display + Sync)>>::new(); | ||
1086 | //^^ Vec<Box<*const (dyn Display + Sync)>> | ||
1087 | let _v = Vec::<Box<dyn Display + Sync>>::new(); | ||
1088 | //^^ Vec<Box<dyn Display + Sync>> | ||
1089 | } | ||
1090 | "#, | ||
1091 | ); | ||
1092 | } | ||
1093 | |||
1094 | #[test] | ||
1095 | fn shorten_iterator_hints() { | ||
1096 | check_with_config( | ||
1097 | InlayHintsConfig { | ||
1098 | parameter_hints: false, | ||
1099 | type_hints: true, | ||
1100 | chaining_hints: false, | ||
1101 | max_length: None, | ||
1102 | }, | ||
1103 | r#" | ||
1104 | use core::iter; | ||
1105 | |||
1106 | struct MyIter; | ||
1107 | |||
1108 | impl Iterator for MyIter { | ||
1109 | type Item = (); | ||
1110 | fn next(&mut self) -> Option<Self::Item> { | ||
1111 | None | ||
1112 | } | ||
1113 | } | ||
1114 | |||
1115 | fn main() { | ||
1116 | let _x = MyIter; | ||
1117 | //^^ MyIter | ||
1118 | let _x = iter::repeat(0); | ||
1119 | //^^ impl Iterator<Item = i32> | ||
1120 | fn generic<T: Clone>(t: T) { | ||
1121 | let _x = iter::repeat(t); | ||
1122 | //^^ impl Iterator<Item = T> | ||
1123 | let _chained = iter::repeat(t).take(10); | ||
1124 | //^^^^^^^^ impl Iterator<Item = T> | ||
1125 | } | ||
1126 | } | ||
1127 | "#, | ||
1128 | ); | ||
1129 | } | ||
1130 | |||
1131 | #[test] | ||
1132 | fn shorten_iterator_chaining_hints() { | ||
1133 | check_expect( | ||
1134 | InlayHintsConfig { | ||
1135 | parameter_hints: false, | ||
1136 | type_hints: false, | ||
1137 | chaining_hints: true, | ||
1138 | max_length: None, | ||
1139 | }, | ||
1140 | r#" | ||
1141 | use core::iter; | ||
1142 | |||
1143 | struct MyIter; | ||
1144 | |||
1145 | impl Iterator for MyIter { | ||
1146 | type Item = (); | ||
1147 | fn next(&mut self) -> Option<Self::Item> { | ||
1148 | None | ||
1149 | } | ||
1150 | } | ||
1151 | |||
1152 | fn main() { | ||
1153 | let _x = MyIter.by_ref() | ||
1154 | .take(5) | ||
1155 | .by_ref() | ||
1156 | .take(5) | ||
1157 | .by_ref(); | ||
1158 | } | ||
1159 | "#, | ||
1160 | expect![[r#" | ||
1161 | [ | ||
1162 | InlayHint { | ||
1163 | range: 175..242, | ||
1164 | kind: ChainingHint, | ||
1165 | label: "impl Iterator<Item = ()>", | ||
1166 | }, | ||
1167 | InlayHint { | ||
1168 | range: 175..225, | ||
1169 | kind: ChainingHint, | ||
1170 | label: "impl Iterator<Item = ()>", | ||
1171 | }, | ||
1172 | InlayHint { | ||
1173 | range: 175..207, | ||
1174 | kind: ChainingHint, | ||
1175 | label: "impl Iterator<Item = ()>", | ||
1176 | }, | ||
1177 | InlayHint { | ||
1178 | range: 175..190, | ||
1179 | kind: ChainingHint, | ||
1180 | label: "&mut MyIter", | ||
1181 | }, | ||
1182 | ] | ||
1183 | "#]], | ||
1184 | ); | ||
1185 | } | ||
1029 | } | 1186 | } |
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 1aa673cf8..57f3581b6 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs | |||
@@ -46,6 +46,7 @@ mod syntax_highlighting; | |||
46 | mod syntax_tree; | 46 | mod syntax_tree; |
47 | mod typing; | 47 | mod typing; |
48 | mod link_rewrite; | 48 | mod link_rewrite; |
49 | mod markdown_remove; | ||
49 | 50 | ||
50 | use std::sync::Arc; | 51 | use std::sync::Arc; |
51 | 52 | ||
@@ -376,8 +377,9 @@ impl Analysis { | |||
376 | &self, | 377 | &self, |
377 | position: FilePosition, | 378 | position: FilePosition, |
378 | links_in_hover: bool, | 379 | links_in_hover: bool, |
380 | markdown: bool, | ||
379 | ) -> Cancelable<Option<RangeInfo<HoverResult>>> { | 381 | ) -> Cancelable<Option<RangeInfo<HoverResult>>> { |
380 | self.with_db(|db| hover::hover(db, position, links_in_hover)) | 382 | self.with_db(|db| hover::hover(db, position, links_in_hover, markdown)) |
381 | } | 383 | } |
382 | 384 | ||
383 | /// Computes parameter information for the given call expression. | 385 | /// Computes parameter information for the given call expression. |
diff --git a/crates/ide/src/link_rewrite.rs b/crates/ide/src/link_rewrite.rs index a16f90e17..c317a2379 100644 --- a/crates/ide/src/link_rewrite.rs +++ b/crates/ide/src/link_rewrite.rs | |||
@@ -107,7 +107,7 @@ fn rewrite_intra_doc_link( | |||
107 | let krate = resolved.module(db)?.krate(); | 107 | let krate = resolved.module(db)?.krate(); |
108 | let canonical_path = resolved.canonical_path(db)?; | 108 | let canonical_path = resolved.canonical_path(db)?; |
109 | let new_target = get_doc_url(db, &krate)? | 109 | let new_target = get_doc_url(db, &krate)? |
110 | .join(&format!("{}/", krate.display_name(db)?)) | 110 | .join(&format!("{}/", krate.declaration_name(db)?)) |
111 | .ok()? | 111 | .ok()? |
112 | .join(&canonical_path.replace("::", "/")) | 112 | .join(&canonical_path.replace("::", "/")) |
113 | .ok()? | 113 | .ok()? |
@@ -127,7 +127,7 @@ fn rewrite_url_link(db: &RootDatabase, def: ModuleDef, target: &str) -> Option<S | |||
127 | let module = def.module(db)?; | 127 | let module = def.module(db)?; |
128 | let krate = module.krate(); | 128 | let krate = module.krate(); |
129 | let canonical_path = def.canonical_path(db)?; | 129 | let canonical_path = def.canonical_path(db)?; |
130 | let base = format!("{}/{}", krate.display_name(db)?, canonical_path.replace("::", "/")); | 130 | let base = format!("{}/{}", krate.declaration_name(db)?, canonical_path.replace("::", "/")); |
131 | 131 | ||
132 | get_doc_url(db, &krate) | 132 | get_doc_url(db, &krate) |
133 | .and_then(|url| url.join(&base).ok()) | 133 | .and_then(|url| url.join(&base).ok()) |
@@ -248,7 +248,7 @@ fn get_doc_url(db: &RootDatabase, krate: &Crate) -> Option<Url> { | |||
248 | // | 248 | // |
249 | // FIXME: clicking on the link should just open the file in the editor, | 249 | // FIXME: clicking on the link should just open the file in the editor, |
250 | // instead of falling back to external urls. | 250 | // instead of falling back to external urls. |
251 | Some(format!("https://docs.rs/{}/*/", krate.display_name(db)?)) | 251 | Some(format!("https://docs.rs/{}/*/", krate.declaration_name(db)?)) |
252 | }) | 252 | }) |
253 | .and_then(|s| Url::parse(&s).ok()) | 253 | .and_then(|s| Url::parse(&s).ok()) |
254 | } | 254 | } |
diff --git a/crates/ide/src/markdown_remove.rs b/crates/ide/src/markdown_remove.rs new file mode 100644 index 000000000..02ad39dfb --- /dev/null +++ b/crates/ide/src/markdown_remove.rs | |||
@@ -0,0 +1,23 @@ | |||
1 | //! Removes markdown from strings. | ||
2 | |||
3 | use pulldown_cmark::{Event, Parser, Tag}; | ||
4 | |||
5 | /// Removes all markdown, keeping the text and code blocks | ||
6 | /// | ||
7 | /// Currently limited in styling, i.e. no ascii tables or lists | ||
8 | pub fn remove_markdown(markdown: &str) -> String { | ||
9 | let mut out = String::new(); | ||
10 | let parser = Parser::new(markdown); | ||
11 | |||
12 | for event in parser { | ||
13 | match event { | ||
14 | Event::Text(text) | Event::Code(text) => out.push_str(&text), | ||
15 | Event::SoftBreak | Event::HardBreak | Event::Rule | Event::End(Tag::CodeBlock(_)) => { | ||
16 | out.push('\n') | ||
17 | } | ||
18 | _ => {} | ||
19 | } | ||
20 | } | ||
21 | |||
22 | out | ||
23 | } | ||
diff --git a/crates/ide/src/status.rs b/crates/ide/src/status.rs index 0af84daa0..f67f10491 100644 --- a/crates/ide/src/status.rs +++ b/crates/ide/src/status.rs | |||
@@ -45,7 +45,7 @@ pub(crate) fn status(db: &RootDatabase, file_id: Option<FileId>) -> String { | |||
45 | match krate { | 45 | match krate { |
46 | Some(krate) => { | 46 | Some(krate) => { |
47 | let crate_graph = db.crate_graph(); | 47 | let crate_graph = db.crate_graph(); |
48 | let display_crate = |krate: CrateId| match &crate_graph[krate].display_name { | 48 | let display_crate = |krate: CrateId| match &crate_graph[krate].declaration_name { |
49 | Some(it) => format!("{}({:?})", it, krate), | 49 | Some(it) => format!("{}({:?})", it, krate), |
50 | None => format!("{:?}", krate), | 50 | None => format!("{:?}", krate), |
51 | }; | 51 | }; |
diff --git a/crates/ide/src/typing/on_enter.rs b/crates/ide/src/typing/on_enter.rs index a0dc4b9df..98adef1d6 100644 --- a/crates/ide/src/typing/on_enter.rs +++ b/crates/ide/src/typing/on_enter.rs | |||
@@ -51,12 +51,12 @@ pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<Text | |||
51 | return None; | 51 | return None; |
52 | } | 52 | } |
53 | 53 | ||
54 | let mut remove_last_space = false; | 54 | let mut remove_trailing_whitespace = false; |
55 | // Continuing single-line non-doc comments (like this one :) ) is annoying | 55 | // Continuing single-line non-doc comments (like this one :) ) is annoying |
56 | if prefix == "//" && comment_range.end() == position.offset { | 56 | if prefix == "//" && comment_range.end() == position.offset { |
57 | if comment.text().ends_with(' ') { | 57 | if comment.text().ends_with(' ') { |
58 | mark::hit!(continues_end_of_line_comment_with_space); | 58 | mark::hit!(continues_end_of_line_comment_with_space); |
59 | remove_last_space = true; | 59 | remove_trailing_whitespace = true; |
60 | } else if !followed_by_comment(&comment) { | 60 | } else if !followed_by_comment(&comment) { |
61 | return None; | 61 | return None; |
62 | } | 62 | } |
@@ -64,8 +64,10 @@ pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<Text | |||
64 | 64 | ||
65 | let indent = node_indent(&file, comment.syntax())?; | 65 | let indent = node_indent(&file, comment.syntax())?; |
66 | let inserted = format!("\n{}{} $0", indent, prefix); | 66 | let inserted = format!("\n{}{} $0", indent, prefix); |
67 | let delete = if remove_last_space { | 67 | let delete = if remove_trailing_whitespace { |
68 | TextRange::new(position.offset - TextSize::of(' '), position.offset) | 68 | let trimmed_len = comment.text().trim_end().len() as u32; |
69 | let trailing_whitespace_len = comment.text().len() as u32 - trimmed_len; | ||
70 | TextRange::new(position.offset - TextSize::from(trailing_whitespace_len), position.offset) | ||
69 | } else { | 71 | } else { |
70 | TextRange::empty(position.offset) | 72 | TextRange::empty(position.offset) |
71 | }; | 73 | }; |
@@ -253,4 +255,23 @@ fn main() { | |||
253 | "#, | 255 | "#, |
254 | ); | 256 | ); |
255 | } | 257 | } |
258 | |||
259 | #[test] | ||
260 | fn trims_all_trailing_whitespace() { | ||
261 | do_check( | ||
262 | " | ||
263 | fn main() { | ||
264 | // Fix me \t\t <|> | ||
265 | let x = 1 + 1; | ||
266 | } | ||
267 | ", | ||
268 | " | ||
269 | fn main() { | ||
270 | // Fix me | ||
271 | // $0 | ||
272 | let x = 1 + 1; | ||
273 | } | ||
274 | ", | ||
275 | ); | ||
276 | } | ||
256 | } | 277 | } |