diff options
-rw-r--r-- | crates/assists/src/ast_transform.rs | 17 | ||||
-rw-r--r-- | crates/assists/src/handlers/add_missing_impl_members.rs | 25 | ||||
-rw-r--r-- | crates/assists/src/handlers/auto_import.rs | 7 | ||||
-rw-r--r-- | crates/assists/src/handlers/extract_struct_from_enum_variant.rs | 8 | ||||
-rw-r--r-- | crates/assists/src/handlers/fill_match_arms.rs | 4 | ||||
-rw-r--r-- | crates/assists/src/utils.rs | 21 | ||||
-rw-r--r-- | crates/hir/src/lib.rs | 7 | ||||
-rw-r--r-- | crates/hir_def/src/find_path.rs | 2 | ||||
-rw-r--r-- | crates/hir_def/src/import_map.rs | 36 | ||||
-rw-r--r-- | crates/hir_def/src/path.rs | 22 | ||||
-rw-r--r-- | crates/hir_ty/src/display.rs | 29 | ||||
-rw-r--r-- | crates/ide/src/hover.rs | 51 | ||||
-rw-r--r-- | crates/ide/src/inlay_hints.rs | 34 | ||||
-rw-r--r-- | crates/ide/src/lib.rs | 4 | ||||
-rw-r--r-- | crates/ide/src/markdown_remove.rs | 23 | ||||
-rw-r--r-- | crates/ide/src/typing/on_enter.rs | 29 | ||||
-rw-r--r-- | crates/rust-analyzer/src/config.rs | 6 | ||||
-rw-r--r-- | crates/rust-analyzer/src/handlers.rs | 6 | ||||
-rw-r--r-- | crates/syntax/src/ptr.rs | 2 | ||||
-rw-r--r-- | docs/user/manual.adoc | 17 | ||||
-rw-r--r-- | xtask/tests/tidy.rs | 7 |
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. |
2 | use rustc_hash::FxHashMap; | ||
3 | |||
4 | use hir::{HirDisplay, PathResolution, SemanticsScope}; | 2 | use hir::{HirDisplay, PathResolution, SemanticsScope}; |
3 | use rustc_hash::FxHashMap; | ||
5 | use syntax::{ | 4 | use syntax::{ |
6 | algo::SyntaxRewriter, | 5 | algo::SyntaxRewriter, |
7 | ast::{self, AstNode}, | 6 | ast::{self, AstNode}, |
8 | SyntaxNode, | 7 | SyntaxNode, |
9 | }; | 8 | }; |
10 | 9 | ||
10 | use crate::utils::mod_path_to_ast; | ||
11 | |||
11 | pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: N) -> N { | 12 | pub 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 | |||
214 | pub(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#" | ||
829 | trait Test { | ||
830 | fn foo(&self, x: crate) | ||
831 | } | ||
832 | impl Test for () { | ||
833 | <|> | ||
834 | } | ||
835 | "#, | ||
836 | r#" | ||
837 | trait Test { | ||
838 | fn foo(&self, x: crate) | ||
839 | } | ||
840 | impl 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 | ||
16 | use crate::{utils::insert_use, AssistContext, AssistId, AssistKind, Assists, GroupLabel}; | 16 | use 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 | ||
12 | use crate::{ | 12 | use 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 | }; |
15 | use 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}; | |||
7 | use test_utils::mark; | 7 | use test_utils::mark; |
8 | 8 | ||
9 | use crate::{ | 9 | use 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 | ||
194 | fn build_pat(db: &RootDatabase, module: hir::Module, var: hir::EnumVariant) -> Option<ast::Pat> { | 194 | fn 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; | |||
19 | pub use insert_use::MergeBehaviour; | 19 | pub use insert_use::MergeBehaviour; |
20 | pub(crate) use insert_use::{insert_use, ImportScope}; | 20 | pub(crate) use insert_use::{insert_use, ImportScope}; |
21 | 21 | ||
22 | pub 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 | |||
22 | pub(crate) fn unwrap_trivial_block(block: ast::BlockExpr) -> ast::Expr { | 43 | pub(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 | }; |
57 | pub use hir_expand::{ | 57 | pub 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)] |
66 | use { | 66 | use {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 | ||
5 | use base_db::CrateId; | 5 | use base_db::CrateId; |
6 | use fst::{self, Streamer}; | 6 | use fst::{self, Streamer}; |
7 | use hir_expand::name::Name; | ||
7 | use indexmap::{map::Entry, IndexMap}; | 8 | use indexmap::{map::Entry, IndexMap}; |
9 | use itertools::Itertools; | ||
8 | use rustc_hash::{FxHashMap, FxHasher}; | 10 | use rustc_hash::{FxHashMap, FxHasher}; |
9 | use smallvec::SmallVec; | 11 | use smallvec::SmallVec; |
10 | use syntax::SmolStr; | 12 | use syntax::SmolStr; |
11 | 13 | ||
12 | use crate::{ | 14 | use 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 | ||
20 | type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<FxHasher>>; | 19 | type 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)] |
24 | pub struct ImportInfo { | 23 | pub 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)] | ||
31 | pub struct ImportPath { | ||
32 | pub segments: Vec<Name>, | ||
33 | } | ||
34 | |||
35 | impl 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 | |||
41 | impl 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 | ||
200 | fn fst_path(path: &ModPath) -> String { | 216 | fn 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 | }; |
16 | use syntax::ast::{self, make}; | 16 | use syntax::ast::{self}; |
17 | 17 | ||
18 | use crate::{ | 18 | use 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 | ||
637 | impl HirDisplay for Obligation { | 654 | impl 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; | |||
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 | } |
@@ -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#" |
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 | ||
1041 | pub struct Vec<T> {} | ||
1042 | |||
1043 | impl<T> Vec<T> { | ||
1044 | pub fn new() -> Self { Vec {} } | ||
1045 | } | ||
1046 | |||
1047 | pub struct Box<T> {} | ||
1048 | |||
1049 | trait Display {} | ||
1050 | trait Sync {} | ||
1051 | |||
1052 | fn 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; | |||
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/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/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 | } |
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 | }; |
18 | use lsp_types::ClientCapabilities; | 18 | use lsp_types::{ClientCapabilities, MarkupKind}; |
19 | use project_model::{CargoConfig, ProjectJson, ProjectJsonData, ProjectManifest}; | 19 | use project_model::{CargoConfig, ProjectJson, ProjectJsonData, ProjectManifest}; |
20 | use rustc_hash::FxHashSet; | 20 | use rustc_hash::FxHashSet; |
21 | use serde::Deserialize; | 21 | use 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)] |
14 | pub struct SyntaxNodePtr { | 14 | pub 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 | |||
402 | You 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. |
401 | You may use `inRustProject` context to configure keybindings for rust projects only. For example: | 418 | You 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] |
41 | fn 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] | ||
41 | fn rust_files_are_tidy() { | 48 | fn 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")) { |