aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide/src')
-rw-r--r--crates/ide/src/completion/complete_postfix/format_like.rs2
-rw-r--r--crates/ide/src/completion/complete_unqualified_path.rs20
-rw-r--r--crates/ide/src/completion/completion_context.rs5
-rw-r--r--crates/ide/src/hover.rs83
-rw-r--r--crates/ide/src/inlay_hints.rs233
-rw-r--r--crates/ide/src/lib.rs4
-rw-r--r--crates/ide/src/link_rewrite.rs6
-rw-r--r--crates/ide/src/markdown_remove.rs23
-rw-r--r--crates/ide/src/status.rs2
-rw-r--r--crates/ide/src/typing/on_enter.rs29
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#"
276fn main() {
277 _<|>
278}
279fn _alpha() {}
280"#,
281 r#"
282fn main() {
283 _alpha()$0
284}
285fn _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;
15use crate::{ 15use 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
32impl Default for HoverConfig { 34impl 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
290fn render_path(db: &RootDatabase, module: Module, item_name: Option<String>) -> String { 301fn 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#"
462pub fn foo() -> u32 { 1 }
463
464fn 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
3211pub 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
3220fn 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 @@
1use hir::{Adt, Callable, HirDisplay, Semantics, Type}; 1use assists::utils::FamousDefs;
2use hir::{known, HirDisplay, Semantics};
2use ide_db::RootDatabase; 3use ide_db::RootDatabase;
3use stdx::to_lower_snake_case; 4use stdx::to_lower_snake_case;
4use syntax::{ 5use 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
204fn 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() { 208fn 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
247fn 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
217fn should_not_display_type_hint( 260fn 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
270fn should_show_param_name_hint( 313fn 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
340fn get_callable(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<Callable> { 383fn 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)]
349mod tests { 392mod 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
939pub struct Vec<T> {} 986pub struct Vec<T> {}
940 987
941impl<T> Vec<T> { 988impl<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::*;
961mod iter {
962 trait IntoIterator {
963 type Item;
964 }
965}
966//- /alloc.rs crate:alloc deps:core 1006//- /alloc.rs crate:alloc deps:core
967mod collections { 1007mod 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
986pub struct Vec<T> {} 1025pub struct Vec<T> {}
987 1026
988impl<T> Vec<T> { 1027impl<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::*;
1010mod iter {
1011 trait IntoIterator {
1012 type Item;
1013 }
1014}
1015//- /alloc.rs crate:alloc deps:core 1046//- /alloc.rs crate:alloc deps:core
1016mod collections { 1047mod 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#"
1071pub struct Vec<T> {}
1072
1073impl<T> Vec<T> {
1074 pub fn new() -> Self { Vec {} }
1075}
1076
1077pub struct Box<T> {}
1078
1079trait Display {}
1080trait Sync {}
1081
1082fn 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#"
1104use core::iter;
1105
1106struct MyIter;
1107
1108impl Iterator for MyIter {
1109 type Item = ();
1110 fn next(&mut self) -> Option<Self::Item> {
1111 None
1112 }
1113}
1114
1115fn 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#"
1141use core::iter;
1142
1143struct MyIter;
1144
1145impl Iterator for MyIter {
1146 type Item = ();
1147 fn next(&mut self) -> Option<Self::Item> {
1148 None
1149 }
1150}
1151
1152fn 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;
46mod syntax_tree; 46mod syntax_tree;
47mod typing; 47mod typing;
48mod link_rewrite; 48mod link_rewrite;
49mod markdown_remove;
49 50
50use std::sync::Arc; 51use 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
3use 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
8pub 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 "
263fn main() {
264 // Fix me \t\t <|>
265 let x = 1 + 1;
266}
267",
268 "
269fn main() {
270 // Fix me
271 // $0
272 let x = 1 + 1;
273}
274",
275 );
276 }
256} 277}