aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/assists/src/ast_transform.rs17
-rw-r--r--crates/assists/src/handlers/add_missing_impl_members.rs25
-rw-r--r--crates/assists/src/handlers/auto_import.rs7
-rw-r--r--crates/assists/src/handlers/extract_struct_from_enum_variant.rs8
-rw-r--r--crates/assists/src/handlers/fill_match_arms.rs4
-rw-r--r--crates/assists/src/utils.rs21
-rw-r--r--crates/hir/src/lib.rs7
-rw-r--r--crates/hir_def/src/find_path.rs2
-rw-r--r--crates/hir_def/src/import_map.rs36
-rw-r--r--crates/hir_def/src/path.rs22
-rw-r--r--crates/hir_ty/src/display.rs29
-rw-r--r--crates/ide/src/hover.rs51
-rw-r--r--crates/ide/src/inlay_hints.rs34
-rw-r--r--crates/ide/src/lib.rs4
-rw-r--r--crates/ide/src/markdown_remove.rs23
-rw-r--r--crates/ide/src/typing/on_enter.rs29
-rw-r--r--crates/rust-analyzer/src/config.rs6
-rw-r--r--crates/rust-analyzer/src/handlers.rs6
-rw-r--r--crates/syntax/src/ptr.rs2
-rw-r--r--docs/user/manual.adoc17
-rw-r--r--xtask/tests/tidy.rs7
21 files changed, 282 insertions, 75 deletions
diff --git a/crates/assists/src/ast_transform.rs b/crates/assists/src/ast_transform.rs
index 4307e0191..ac72f3f02 100644
--- a/crates/assists/src/ast_transform.rs
+++ b/crates/assists/src/ast_transform.rs
@@ -1,13 +1,14 @@
1//! `AstTransformer`s are functions that replace nodes in an AST and can be easily combined. 1//! `AstTransformer`s are functions that replace nodes in an AST and can be easily combined.
2use rustc_hash::FxHashMap;
3
4use hir::{HirDisplay, PathResolution, SemanticsScope}; 2use hir::{HirDisplay, PathResolution, SemanticsScope};
3use rustc_hash::FxHashMap;
5use syntax::{ 4use syntax::{
6 algo::SyntaxRewriter, 5 algo::SyntaxRewriter,
7 ast::{self, AstNode}, 6 ast::{self, AstNode},
8 SyntaxNode, 7 SyntaxNode,
9}; 8};
10 9
10use crate::utils::mod_path_to_ast;
11
11pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: N) -> N { 12pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: N) -> N {
12 SyntaxRewriter::from_fn(|element| match element { 13 SyntaxRewriter::from_fn(|element| match element {
13 syntax::SyntaxElement::Node(n) => { 14 syntax::SyntaxElement::Node(n) => {
@@ -189,7 +190,7 @@ impl<'a> AstTransform<'a> for QualifyPaths<'a> {
189 match resolution { 190 match resolution {
190 PathResolution::Def(def) => { 191 PathResolution::Def(def) => {
191 let found_path = from.find_use_path(self.source_scope.db.upcast(), def)?; 192 let found_path = from.find_use_path(self.source_scope.db.upcast(), def)?;
192 let mut path = path_to_ast(found_path); 193 let mut path = mod_path_to_ast(&found_path);
193 194
194 let type_args = p 195 let type_args = p
195 .segment() 196 .segment()
@@ -210,13 +211,3 @@ impl<'a> AstTransform<'a> for QualifyPaths<'a> {
210 } 211 }
211 } 212 }
212} 213}
213
214pub(crate) fn path_to_ast(path: hir::ModPath) -> ast::Path {
215 let parse = ast::SourceFile::parse(&path.to_string());
216 parse
217 .tree()
218 .syntax()
219 .descendants()
220 .find_map(ast::Path::cast)
221 .unwrap_or_else(|| panic!("failed to parse path {:?}, `{}`", path, path))
222}
diff --git a/crates/assists/src/handlers/add_missing_impl_members.rs b/crates/assists/src/handlers/add_missing_impl_members.rs
index 51b5a2eb0..4c400f287 100644
--- a/crates/assists/src/handlers/add_missing_impl_members.rs
+++ b/crates/assists/src/handlers/add_missing_impl_members.rs
@@ -820,4 +820,29 @@ impl Tr for () {
820}"#, 820}"#,
821 ) 821 )
822 } 822 }
823
824 #[test]
825 fn weird_path() {
826 check_assist(
827 add_missing_impl_members,
828 r#"
829trait Test {
830 fn foo(&self, x: crate)
831}
832impl Test for () {
833 <|>
834}
835"#,
836 r#"
837trait Test {
838 fn foo(&self, x: crate)
839}
840impl Test for () {
841 fn foo(&self, x: crate) {
842 ${0:todo!()}
843 }
844}
845"#,
846 )
847 }
823} 848}
diff --git a/crates/assists/src/handlers/auto_import.rs b/crates/assists/src/handlers/auto_import.rs
index 357ff6392..d3ee98e5f 100644
--- a/crates/assists/src/handlers/auto_import.rs
+++ b/crates/assists/src/handlers/auto_import.rs
@@ -13,7 +13,10 @@ use syntax::{
13 SyntaxNode, 13 SyntaxNode,
14}; 14};
15 15
16use crate::{utils::insert_use, AssistContext, AssistId, AssistKind, Assists, GroupLabel}; 16use crate::{
17 utils::insert_use, utils::mod_path_to_ast, AssistContext, AssistId, AssistKind, Assists,
18 GroupLabel,
19};
17 20
18// Assist: auto_import 21// Assist: auto_import
19// 22//
@@ -54,7 +57,7 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
54 range, 57 range,
55 |builder| { 58 |builder| {
56 let new_syntax = 59 let new_syntax =
57 insert_use(&scope, import.to_ast_path(), ctx.config.insert_use.merge); 60 insert_use(&scope, mod_path_to_ast(&import), ctx.config.insert_use.merge);
58 builder.replace(syntax.text_range(), new_syntax.to_string()) 61 builder.replace(syntax.text_range(), new_syntax.to_string())
59 }, 62 },
60 ); 63 );
diff --git a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
index f5f03ef36..7f4f80b23 100644
--- a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
+++ b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
@@ -10,9 +10,10 @@ use syntax::{
10}; 10};
11 11
12use crate::{ 12use crate::{
13 assist_context::AssistBuilder, utils::insert_use, AssistContext, AssistId, AssistKind, Assists, 13 assist_context::AssistBuilder,
14 utils::{insert_use, mod_path_to_ast, ImportScope},
15 AssistContext, AssistId, AssistKind, Assists,
14}; 16};
15use insert_use::ImportScope;
16 17
17// Assist: extract_struct_from_enum_variant 18// Assist: extract_struct_from_enum_variant
18// 19//
@@ -111,7 +112,8 @@ fn insert_import(
111 let scope = ImportScope::find_insert_use_container(path.syntax(), ctx)?; 112 let scope = ImportScope::find_insert_use_container(path.syntax(), ctx)?;
112 let syntax = scope.as_syntax_node(); 113 let syntax = scope.as_syntax_node();
113 114
114 let new_syntax = insert_use(&scope, mod_path.to_ast_path(), ctx.config.insert_use.merge); 115 let new_syntax =
116 insert_use(&scope, mod_path_to_ast(&mod_path), ctx.config.insert_use.merge);
115 // FIXME: this will currently panic as multiple imports will have overlapping text ranges 117 // FIXME: this will currently panic as multiple imports will have overlapping text ranges
116 builder.replace(syntax.text_range(), new_syntax.to_string()) 118 builder.replace(syntax.text_range(), new_syntax.to_string())
117 } 119 }
diff --git a/crates/assists/src/handlers/fill_match_arms.rs b/crates/assists/src/handlers/fill_match_arms.rs
index 3d9bdb2bf..676f5ad92 100644
--- a/crates/assists/src/handlers/fill_match_arms.rs
+++ b/crates/assists/src/handlers/fill_match_arms.rs
@@ -7,7 +7,7 @@ use syntax::ast::{self, make, AstNode, MatchArm, NameOwner, Pat};
7use test_utils::mark; 7use test_utils::mark;
8 8
9use crate::{ 9use crate::{
10 utils::{render_snippet, Cursor, FamousDefs}, 10 utils::{mod_path_to_ast, render_snippet, Cursor, FamousDefs},
11 AssistContext, AssistId, AssistKind, Assists, 11 AssistContext, AssistId, AssistKind, Assists,
12}; 12};
13 13
@@ -192,7 +192,7 @@ fn resolve_tuple_of_enum_def(
192} 192}
193 193
194fn build_pat(db: &RootDatabase, module: hir::Module, var: hir::EnumVariant) -> Option<ast::Pat> { 194fn build_pat(db: &RootDatabase, module: hir::Module, var: hir::EnumVariant) -> Option<ast::Pat> {
195 let path = crate::ast_transform::path_to_ast(module.find_use_path(db, ModuleDef::from(var))?); 195 let path = mod_path_to_ast(&module.find_use_path(db, ModuleDef::from(var))?);
196 196
197 // FIXME: use HIR for this; it doesn't currently expose struct vs. tuple vs. unit variants though 197 // FIXME: use HIR for this; it doesn't currently expose struct vs. tuple vs. unit variants though
198 let pat: ast::Pat = match var.source(db).value.kind() { 198 let pat: ast::Pat = match var.source(db).value.kind() {
diff --git a/crates/assists/src/utils.rs b/crates/assists/src/utils.rs
index b0511ceb6..eb69c49a4 100644
--- a/crates/assists/src/utils.rs
+++ b/crates/assists/src/utils.rs
@@ -19,6 +19,27 @@ use crate::assist_config::SnippetCap;
19pub use insert_use::MergeBehaviour; 19pub use insert_use::MergeBehaviour;
20pub(crate) use insert_use::{insert_use, ImportScope}; 20pub(crate) use insert_use::{insert_use, ImportScope};
21 21
22pub fn mod_path_to_ast(path: &hir::ModPath) -> ast::Path {
23 let mut segments = Vec::new();
24 let mut is_abs = false;
25 match path.kind {
26 hir::PathKind::Plain => {}
27 hir::PathKind::Super(0) => segments.push(make::path_segment_self()),
28 hir::PathKind::Super(n) => segments.extend((0..n).map(|_| make::path_segment_super())),
29 hir::PathKind::DollarCrate(_) | hir::PathKind::Crate => {
30 segments.push(make::path_segment_crate())
31 }
32 hir::PathKind::Abs => is_abs = true,
33 }
34
35 segments.extend(
36 path.segments
37 .iter()
38 .map(|segment| make::path_segment(make::name_ref(&segment.to_string()))),
39 );
40 make::path_from_segments(segments, is_abs)
41}
42
22pub(crate) fn unwrap_trivial_block(block: ast::BlockExpr) -> ast::Expr { 43pub(crate) fn unwrap_trivial_block(block: ast::BlockExpr) -> ast::Expr {
23 extract_trivial_expression(&block) 44 extract_trivial_expression(&block)
24 .filter(|expr| !expr.syntax().text().contains_char('\n')) 45 .filter(|expr| !expr.syntax().text().contains_char('\n'))
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 87084fa13..171118d98 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -51,7 +51,7 @@ pub use hir_def::{
51 find_path::PrefixKind, 51 find_path::PrefixKind,
52 item_scope::ItemInNs, 52 item_scope::ItemInNs,
53 nameres::ModuleSource, 53 nameres::ModuleSource,
54 path::ModPath, 54 path::{ModPath, PathKind},
55 type_ref::{Mutability, TypeRef}, 55 type_ref::{Mutability, TypeRef},
56}; 56};
57pub use hir_expand::{ 57pub use hir_expand::{
@@ -63,7 +63,4 @@ pub use hir_ty::display::HirDisplay;
63// These are negative re-exports: pub using these names is forbidden, they 63// These are negative re-exports: pub using these names is forbidden, they
64// should remain private to hir internals. 64// should remain private to hir internals.
65#[allow(unused)] 65#[allow(unused)]
66use { 66use {hir_def::path::Path, hir_expand::hygiene::Hygiene};
67 hir_def::path::{Path, PathKind},
68 hir_expand::hygiene::Hygiene,
69};
diff --git a/crates/hir_def/src/find_path.rs b/crates/hir_def/src/find_path.rs
index 9106ed45f..02613c4c4 100644
--- a/crates/hir_def/src/find_path.rs
+++ b/crates/hir_def/src/find_path.rs
@@ -222,6 +222,7 @@ fn find_path_inner(
222 best_path_len - 1, 222 best_path_len - 1,
223 prefixed, 223 prefixed,
224 )?; 224 )?;
225 mark::hit!(partially_imported);
225 path.segments.push(info.path.segments.last().unwrap().clone()); 226 path.segments.push(info.path.segments.last().unwrap().clone());
226 Some(path) 227 Some(path)
227 }) 228 })
@@ -515,6 +516,7 @@ mod tests {
515 516
516 #[test] 517 #[test]
517 fn partially_imported() { 518 fn partially_imported() {
519 mark::check!(partially_imported);
518 // Tests that short paths are used even for external items, when parts of the path are 520 // Tests that short paths are used even for external items, when parts of the path are
519 // already in scope. 521 // already in scope.
520 let code = r#" 522 let code = r#"
diff --git a/crates/hir_def/src/import_map.rs b/crates/hir_def/src/import_map.rs
index 44bfe1593..028cae2e7 100644
--- a/crates/hir_def/src/import_map.rs
+++ b/crates/hir_def/src/import_map.rs
@@ -4,17 +4,16 @@ use std::{cmp::Ordering, fmt, hash::BuildHasherDefault, sync::Arc};
4 4
5use base_db::CrateId; 5use base_db::CrateId;
6use fst::{self, Streamer}; 6use fst::{self, Streamer};
7use hir_expand::name::Name;
7use indexmap::{map::Entry, IndexMap}; 8use indexmap::{map::Entry, IndexMap};
9use itertools::Itertools;
8use rustc_hash::{FxHashMap, FxHasher}; 10use rustc_hash::{FxHashMap, FxHasher};
9use smallvec::SmallVec; 11use smallvec::SmallVec;
10use syntax::SmolStr; 12use syntax::SmolStr;
11 13
12use crate::{ 14use crate::{
13 db::DefDatabase, 15 db::DefDatabase, item_scope::ItemInNs, visibility::Visibility, AssocItemId, ModuleDefId,
14 item_scope::ItemInNs, 16 ModuleId, TraitId,
15 path::{ModPath, PathKind},
16 visibility::Visibility,
17 AssocItemId, ModuleDefId, ModuleId, TraitId,
18}; 17};
19 18
20type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<FxHasher>>; 19type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<FxHasher>>;
@@ -23,11 +22,28 @@ type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<FxHasher>>;
23#[derive(Debug, Clone, Eq, PartialEq)] 22#[derive(Debug, Clone, Eq, PartialEq)]
24pub struct ImportInfo { 23pub struct ImportInfo {
25 /// A path that can be used to import the item, relative to the crate's root. 24 /// A path that can be used to import the item, relative to the crate's root.
26 pub path: ModPath, 25 pub path: ImportPath,
27 /// The module containing this item. 26 /// The module containing this item.
28 pub container: ModuleId, 27 pub container: ModuleId,
29} 28}
30 29
30#[derive(Debug, Clone, Eq, PartialEq)]
31pub struct ImportPath {
32 pub segments: Vec<Name>,
33}
34
35impl fmt::Display for ImportPath {
36 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37 fmt::Display::fmt(&self.segments.iter().format("::"), f)
38 }
39}
40
41impl ImportPath {
42 fn len(&self) -> usize {
43 self.segments.len()
44 }
45}
46
31/// A map from publicly exported items to the path needed to import/name them from a downstream 47/// A map from publicly exported items to the path needed to import/name them from a downstream
32/// crate. 48/// crate.
33/// 49///
@@ -61,7 +77,7 @@ impl ImportMap {
61 let mut import_map = Self::default(); 77 let mut import_map = Self::default();
62 78
63 // We look only into modules that are public(ly reexported), starting with the crate root. 79 // We look only into modules that are public(ly reexported), starting with the crate root.
64 let empty = ModPath { kind: PathKind::Plain, segments: vec![] }; 80 let empty = ImportPath { segments: vec![] };
65 let root = ModuleId { krate, local_id: def_map.root }; 81 let root = ModuleId { krate, local_id: def_map.root };
66 let mut worklist = vec![(root, empty)]; 82 let mut worklist = vec![(root, empty)];
67 while let Some((module, mod_path)) = worklist.pop() { 83 while let Some((module, mod_path)) = worklist.pop() {
@@ -152,8 +168,8 @@ impl ImportMap {
152 } 168 }
153 169
154 /// Returns the `ModPath` needed to import/mention `item`, relative to this crate's root. 170 /// Returns the `ModPath` needed to import/mention `item`, relative to this crate's root.
155 pub fn path_of(&self, item: ItemInNs) -> Option<&ModPath> { 171 pub fn path_of(&self, item: ItemInNs) -> Option<&ImportPath> {
156 Some(&self.map.get(&item)?.path) 172 self.import_info_for(item).map(|it| &it.path)
157 } 173 }
158 174
159 pub fn import_info_for(&self, item: ItemInNs) -> Option<&ImportInfo> { 175 pub fn import_info_for(&self, item: ItemInNs) -> Option<&ImportInfo> {
@@ -197,7 +213,7 @@ impl fmt::Debug for ImportMap {
197 } 213 }
198} 214}
199 215
200fn fst_path(path: &ModPath) -> String { 216fn fst_path(path: &ImportPath) -> String {
201 let mut s = path.to_string(); 217 let mut s = path.to_string();
202 s.make_ascii_lowercase(); 218 s.make_ascii_lowercase();
203 s 219 s
diff --git a/crates/hir_def/src/path.rs b/crates/hir_def/src/path.rs
index 209b18e78..5b8c1e449 100644
--- a/crates/hir_def/src/path.rs
+++ b/crates/hir_def/src/path.rs
@@ -13,7 +13,7 @@ use hir_expand::{
13 hygiene::Hygiene, 13 hygiene::Hygiene,
14 name::{AsName, Name}, 14 name::{AsName, Name},
15}; 15};
16use syntax::ast::{self, make}; 16use syntax::ast::{self};
17 17
18use crate::{ 18use crate::{
19 type_ref::{TypeBound, TypeRef}, 19 type_ref::{TypeBound, TypeRef},
@@ -100,26 +100,6 @@ impl ModPath {
100 } 100 }
101 self.segments.first() 101 self.segments.first()
102 } 102 }
103
104 pub fn to_ast_path(&self) -> ast::Path {
105 let mut segments = Vec::new();
106 let mut is_abs = false;
107 match self.kind {
108 PathKind::Plain => {}
109 PathKind::Super(0) => segments.push(make::path_segment_self()),
110 PathKind::Super(n) => segments.extend((0..n).map(|_| make::path_segment_super())),
111 PathKind::Crate => segments.push(make::path_segment_crate()),
112 PathKind::Abs => is_abs = true,
113 PathKind::DollarCrate(_) => (),
114 }
115
116 segments.extend(
117 self.segments
118 .iter()
119 .map(|segment| make::path_segment(make::name_ref(&segment.to_string()))),
120 );
121 make::path_from_segments(segments, is_abs)
122 }
123} 103}
124 104
125#[derive(Debug, Clone, PartialEq, Eq, Hash)] 105#[derive(Debug, Clone, PartialEq, Eq, Hash)]
diff --git a/crates/hir_ty/src/display.rs b/crates/hir_ty/src/display.rs
index f389c5a4b..d2e151f25 100644
--- a/crates/hir_ty/src/display.rs
+++ b/crates/hir_ty/src/display.rs
@@ -221,7 +221,16 @@ impl HirDisplay for ApplicationTy {
221 } 221 }
222 TypeCtor::RawPtr(m) => { 222 TypeCtor::RawPtr(m) => {
223 let t = self.parameters.as_single(); 223 let t = self.parameters.as_single();
224 write!(f, "*{}{}", m.as_keyword_for_ptr(), t.display(f.db))?; 224 let ty_display = t.display(f.db);
225
226 write!(f, "*{}", m.as_keyword_for_ptr())?;
227 if matches!(t, Ty::Dyn(predicates) if predicates.len() > 1) {
228 write!(f, "(")?;
229 write!(f, "{}", ty_display)?;
230 write!(f, ")")?;
231 } else {
232 write!(f, "{}", ty_display)?;
233 }
225 } 234 }
226 TypeCtor::Ref(m) => { 235 TypeCtor::Ref(m) => {
227 let t = self.parameters.as_single(); 236 let t = self.parameters.as_single();
@@ -230,7 +239,15 @@ impl HirDisplay for ApplicationTy {
230 } else { 239 } else {
231 t.display(f.db) 240 t.display(f.db)
232 }; 241 };
233 write!(f, "&{}{}", m.as_keyword_for_ref(), ty_display)?; 242
243 write!(f, "&{}", m.as_keyword_for_ref())?;
244 if matches!(t, Ty::Dyn(predicates) if predicates.len() > 1) {
245 write!(f, "(")?;
246 write!(f, "{}", ty_display)?;
247 write!(f, ")")?;
248 } else {
249 write!(f, "{}", ty_display)?;
250 }
234 } 251 }
235 TypeCtor::Never => write!(f, "!")?, 252 TypeCtor::Never => write!(f, "!")?,
236 TypeCtor::Tuple { .. } => { 253 TypeCtor::Tuple { .. } => {
@@ -636,14 +653,14 @@ impl HirDisplay for GenericPredicate {
636 653
637impl HirDisplay for Obligation { 654impl HirDisplay for Obligation {
638 fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> { 655 fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> {
639 Ok(match self { 656 match self {
640 Obligation::Trait(tr) => write!(f, "Implements({})", tr.display(f.db))?, 657 Obligation::Trait(tr) => write!(f, "Implements({})", tr.display(f.db)),
641 Obligation::Projection(proj) => write!( 658 Obligation::Projection(proj) => write!(
642 f, 659 f,
643 "Normalize({} => {})", 660 "Normalize({} => {})",
644 proj.projection_ty.display(f.db), 661 proj.projection_ty.display(f.db),
645 proj.ty.display(f.db) 662 proj.ty.display(f.db)
646 )?, 663 ),
647 }) 664 }
648 } 665 }
649} 666}
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index 4521d72cc..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}
@@ -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#"
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs
index 1d7e8de56..3a4dc6a84 100644
--- a/crates/ide/src/inlay_hints.rs
+++ b/crates/ide/src/inlay_hints.rs
@@ -1026,4 +1026,38 @@ mod collections {
1026"#, 1026"#,
1027 ); 1027 );
1028 } 1028 }
1029
1030 #[test]
1031 fn multi_dyn_trait_bounds() {
1032 check_with_config(
1033 InlayHintsConfig {
1034 type_hints: true,
1035 parameter_hints: false,
1036 chaining_hints: false,
1037 max_length: None,
1038 },
1039 r#"
1040//- /main.rs crate:main
1041pub struct Vec<T> {}
1042
1043impl<T> Vec<T> {
1044 pub fn new() -> Self { Vec {} }
1045}
1046
1047pub struct Box<T> {}
1048
1049trait Display {}
1050trait Sync {}
1051
1052fn main() {
1053 let _v = Vec::<Box<&(dyn Display + Sync)>>::new();
1054 //^^ Vec<Box<&(dyn Display + Sync)>>
1055 let _v = Vec::<Box<*const (dyn Display + Sync)>>::new();
1056 //^^ Vec<Box<*const (dyn Display + Sync)>>
1057 let _v = Vec::<Box<dyn Display + Sync>>::new();
1058 //^^ Vec<Box<dyn Display + Sync>>
1059}
1060"#,
1061 );
1062 }
1029} 1063}
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/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/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}
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index dcbc11c14..1b9b24698 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -15,7 +15,7 @@ use ide::{
15 AssistConfig, CompletionConfig, DiagnosticsConfig, HoverConfig, InlayHintsConfig, 15 AssistConfig, CompletionConfig, DiagnosticsConfig, HoverConfig, InlayHintsConfig,
16 MergeBehaviour, 16 MergeBehaviour,
17}; 17};
18use lsp_types::ClientCapabilities; 18use lsp_types::{ClientCapabilities, MarkupKind};
19use project_model::{CargoConfig, ProjectJson, ProjectJsonData, ProjectManifest}; 19use project_model::{CargoConfig, ProjectJson, ProjectJsonData, ProjectManifest};
20use rustc_hash::FxHashSet; 20use rustc_hash::FxHashSet;
21use serde::Deserialize; 21use serde::Deserialize;
@@ -333,6 +333,7 @@ impl Config {
333 debug: data.hoverActions_enable && data.hoverActions_debug, 333 debug: data.hoverActions_enable && data.hoverActions_debug,
334 goto_type_def: data.hoverActions_enable && data.hoverActions_gotoTypeDef, 334 goto_type_def: data.hoverActions_enable && data.hoverActions_gotoTypeDef,
335 links_in_hover: data.hoverActions_linksInHover, 335 links_in_hover: data.hoverActions_linksInHover,
336 markdown: true,
336 }; 337 };
337 338
338 log::info!("Config::update() = {:#?}", self); 339 log::info!("Config::update() = {:#?}", self);
@@ -340,6 +341,9 @@ impl Config {
340 341
341 pub fn update_caps(&mut self, caps: &ClientCapabilities) { 342 pub fn update_caps(&mut self, caps: &ClientCapabilities) {
342 if let Some(doc_caps) = caps.text_document.as_ref() { 343 if let Some(doc_caps) = caps.text_document.as_ref() {
344 if let Some(value) = doc_caps.hover.as_ref().and_then(|it| it.content_format.as_ref()) {
345 self.hover.markdown = value.contains(&MarkupKind::Markdown)
346 }
343 if let Some(value) = doc_caps.definition.as_ref().and_then(|it| it.link_support) { 347 if let Some(value) = doc_caps.definition.as_ref().and_then(|it| it.link_support) {
344 self.client_caps.location_link = value; 348 self.client_caps.location_link = value;
345 } 349 }
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index e970abb7c..468655f9c 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -618,7 +618,11 @@ pub(crate) fn handle_hover(
618) -> Result<Option<lsp_ext::Hover>> { 618) -> Result<Option<lsp_ext::Hover>> {
619 let _p = profile::span("handle_hover"); 619 let _p = profile::span("handle_hover");
620 let position = from_proto::file_position(&snap, params.text_document_position_params)?; 620 let position = from_proto::file_position(&snap, params.text_document_position_params)?;
621 let info = match snap.analysis.hover(position, snap.config.hover.links_in_hover)? { 621 let info = match snap.analysis.hover(
622 position,
623 snap.config.hover.links_in_hover,
624 snap.config.hover.markdown,
625 )? {
622 None => return Ok(None), 626 None => return Ok(None),
623 Some(info) => info, 627 Some(info) => info,
624 }; 628 };
diff --git a/crates/syntax/src/ptr.rs b/crates/syntax/src/ptr.rs
index ca7957747..d3fb7a5d9 100644
--- a/crates/syntax/src/ptr.rs
+++ b/crates/syntax/src/ptr.rs
@@ -12,6 +12,8 @@ use crate::{AstNode, SyntaxKind, SyntaxNode, TextRange};
12/// specific node across reparses of the same file. 12/// specific node across reparses of the same file.
13#[derive(Debug, Clone, PartialEq, Eq, Hash)] 13#[derive(Debug, Clone, PartialEq, Eq, Hash)]
14pub struct SyntaxNodePtr { 14pub struct SyntaxNodePtr {
15 // Don't expose this field further. At some point, we might want to replace
16 // range with node id.
15 pub(crate) range: TextRange, 17 pub(crate) range: TextRange,
16 kind: SyntaxKind, 18 kind: SyntaxKind,
17} 19}
diff --git a/docs/user/manual.adoc b/docs/user/manual.adoc
index c1a778852..46e7bd091 100644
--- a/docs/user/manual.adoc
+++ b/docs/user/manual.adoc
@@ -397,6 +397,23 @@ It is possible to change the foreground/background color of inlay hints. Just ad
397} 397}
398---- 398----
399 399
400==== Semantic style customizations
401
402You can customize the look of different semantic elements in the source code. For example, mutable bindings are underlined by default and you can override this behavior by adding the following section to your `settings.json`:
403
404[source,jsonc]
405----
406{
407 "editor.semanticTokenColorCustomizations": {
408 "rules": {
409 "*.mutable": {
410 "fontStyle": "", // underline is the default
411 },
412 }
413 },
414}
415----
416
400==== Special `when` clause context for keybindings. 417==== Special `when` clause context for keybindings.
401You may use `inRustProject` context to configure keybindings for rust projects only. For example: 418You may use `inRustProject` context to configure keybindings for rust projects only. For example:
402[source,json] 419[source,json]
diff --git a/xtask/tests/tidy.rs b/xtask/tests/tidy.rs
index 02b3afc96..01f04a17c 100644
--- a/xtask/tests/tidy.rs
+++ b/xtask/tests/tidy.rs
@@ -38,6 +38,13 @@ fn check_code_formatting() {
38} 38}
39 39
40#[test] 40#[test]
41fn smoke_test_docs_generation() {
42 // We don't commit docs to the repo, so we can just overwrite in tests.
43 codegen::generate_assists_docs(Mode::Overwrite).unwrap();
44 codegen::generate_feature_docs(Mode::Overwrite).unwrap();
45}
46
47#[test]
41fn rust_files_are_tidy() { 48fn rust_files_are_tidy() {
42 let mut tidy_docs = TidyDocs::default(); 49 let mut tidy_docs = TidyDocs::default();
43 for path in rust_files(&project_root().join("crates")) { 50 for path in rust_files(&project_root().join("crates")) {