diff options
23 files changed, 923 insertions, 329 deletions
diff --git a/Cargo.lock b/Cargo.lock index 2e86b3fee..df79334c9 100644 --- a/Cargo.lock +++ b/Cargo.lock | |||
@@ -884,9 +884,9 @@ dependencies = [ | |||
884 | 884 | ||
885 | [[package]] | 885 | [[package]] |
886 | name = "quote" | 886 | name = "quote" |
887 | version = "1.0.6" | 887 | version = "1.0.7" |
888 | source = "registry+https://github.com/rust-lang/crates.io-index" | 888 | source = "registry+https://github.com/rust-lang/crates.io-index" |
889 | checksum = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea" | 889 | checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" |
890 | dependencies = [ | 890 | dependencies = [ |
891 | "proc-macro2", | 891 | "proc-macro2", |
892 | ] | 892 | ] |
diff --git a/crates/ra_hir_ty/src/infer.rs b/crates/ra_hir_ty/src/infer.rs index 2e16e5120..f965eb2b5 100644 --- a/crates/ra_hir_ty/src/infer.rs +++ b/crates/ra_hir_ty/src/infer.rs | |||
@@ -39,8 +39,7 @@ use ra_syntax::SmolStr; | |||
39 | use super::{ | 39 | use super::{ |
40 | primitive::{FloatTy, IntTy}, | 40 | primitive::{FloatTy, IntTy}, |
41 | traits::{Guidance, Obligation, ProjectionPredicate, Solution}, | 41 | traits::{Guidance, Obligation, ProjectionPredicate, Solution}, |
42 | ApplicationTy, InEnvironment, ProjectionTy, Substs, TraitEnvironment, TraitRef, Ty, TypeCtor, | 42 | InEnvironment, ProjectionTy, Substs, TraitEnvironment, TraitRef, Ty, TypeCtor, TypeWalk, |
43 | TypeWalk, Uncertain, | ||
44 | }; | 43 | }; |
45 | use crate::{ | 44 | use crate::{ |
46 | db::HirDatabase, infer::diagnostics::InferenceDiagnostic, lower::ImplTraitLoweringMode, | 45 | db::HirDatabase, infer::diagnostics::InferenceDiagnostic, lower::ImplTraitLoweringMode, |
@@ -312,12 +311,6 @@ impl<'a> InferenceContext<'a> { | |||
312 | fn insert_type_vars_shallow(&mut self, ty: Ty) -> Ty { | 311 | fn insert_type_vars_shallow(&mut self, ty: Ty) -> Ty { |
313 | match ty { | 312 | match ty { |
314 | Ty::Unknown => self.table.new_type_var(), | 313 | Ty::Unknown => self.table.new_type_var(), |
315 | Ty::Apply(ApplicationTy { ctor: TypeCtor::Int(Uncertain::Unknown), .. }) => { | ||
316 | self.table.new_integer_var() | ||
317 | } | ||
318 | Ty::Apply(ApplicationTy { ctor: TypeCtor::Float(Uncertain::Unknown), .. }) => { | ||
319 | self.table.new_float_var() | ||
320 | } | ||
321 | _ => ty, | 314 | _ => ty, |
322 | } | 315 | } |
323 | } | 316 | } |
@@ -664,8 +657,8 @@ impl InferTy { | |||
664 | fn fallback_value(self) -> Ty { | 657 | fn fallback_value(self) -> Ty { |
665 | match self { | 658 | match self { |
666 | InferTy::TypeVar(..) => Ty::Unknown, | 659 | InferTy::TypeVar(..) => Ty::Unknown, |
667 | InferTy::IntVar(..) => Ty::simple(TypeCtor::Int(Uncertain::Known(IntTy::i32()))), | 660 | InferTy::IntVar(..) => Ty::simple(TypeCtor::Int(IntTy::i32())), |
668 | InferTy::FloatVar(..) => Ty::simple(TypeCtor::Float(Uncertain::Known(FloatTy::f64()))), | 661 | InferTy::FloatVar(..) => Ty::simple(TypeCtor::Float(FloatTy::f64())), |
669 | InferTy::MaybeNeverTypeVar(..) => Ty::simple(TypeCtor::Never), | 662 | InferTy::MaybeNeverTypeVar(..) => Ty::simple(TypeCtor::Never), |
670 | } | 663 | } |
671 | } | 664 | } |
diff --git a/crates/ra_hir_ty/src/infer/expr.rs b/crates/ra_hir_ty/src/infer/expr.rs index 4a98e2deb..9fd310f69 100644 --- a/crates/ra_hir_ty/src/infer/expr.rs +++ b/crates/ra_hir_ty/src/infer/expr.rs | |||
@@ -18,7 +18,7 @@ use crate::{ | |||
18 | traits::InEnvironment, | 18 | traits::InEnvironment, |
19 | utils::{generics, variant_data, Generics}, | 19 | utils::{generics, variant_data, Generics}, |
20 | ApplicationTy, Binders, CallableDef, InferTy, IntTy, Mutability, Obligation, Rawness, Substs, | 20 | ApplicationTy, Binders, CallableDef, InferTy, IntTy, Mutability, Obligation, Rawness, Substs, |
21 | TraitRef, Ty, TypeCtor, Uncertain, | 21 | TraitRef, Ty, TypeCtor, |
22 | }; | 22 | }; |
23 | 23 | ||
24 | use super::{ | 24 | use super::{ |
@@ -426,15 +426,7 @@ impl<'a> InferenceContext<'a> { | |||
426 | match &inner_ty { | 426 | match &inner_ty { |
427 | // Fast path for builtins | 427 | // Fast path for builtins |
428 | Ty::Apply(ApplicationTy { | 428 | Ty::Apply(ApplicationTy { |
429 | ctor: | 429 | ctor: TypeCtor::Int(IntTy { signedness: Signedness::Signed, .. }), |
430 | TypeCtor::Int(Uncertain::Known(IntTy { | ||
431 | signedness: Signedness::Signed, | ||
432 | .. | ||
433 | })), | ||
434 | .. | ||
435 | }) | ||
436 | | Ty::Apply(ApplicationTy { | ||
437 | ctor: TypeCtor::Int(Uncertain::Unknown), | ||
438 | .. | 430 | .. |
439 | }) | 431 | }) |
440 | | Ty::Apply(ApplicationTy { ctor: TypeCtor::Float(_), .. }) | 432 | | Ty::Apply(ApplicationTy { ctor: TypeCtor::Float(_), .. }) |
@@ -577,9 +569,7 @@ impl<'a> InferenceContext<'a> { | |||
577 | ); | 569 | ); |
578 | self.infer_expr( | 570 | self.infer_expr( |
579 | *repeat, | 571 | *repeat, |
580 | &Expectation::has_type(Ty::simple(TypeCtor::Int(Uncertain::Known( | 572 | &Expectation::has_type(Ty::simple(TypeCtor::Int(IntTy::usize()))), |
581 | IntTy::usize(), | ||
582 | )))), | ||
583 | ); | 573 | ); |
584 | } | 574 | } |
585 | } | 575 | } |
@@ -592,13 +582,19 @@ impl<'a> InferenceContext<'a> { | |||
592 | Ty::apply_one(TypeCtor::Ref(Mutability::Shared), Ty::simple(TypeCtor::Str)) | 582 | Ty::apply_one(TypeCtor::Ref(Mutability::Shared), Ty::simple(TypeCtor::Str)) |
593 | } | 583 | } |
594 | Literal::ByteString(..) => { | 584 | Literal::ByteString(..) => { |
595 | let byte_type = Ty::simple(TypeCtor::Int(Uncertain::Known(IntTy::u8()))); | 585 | let byte_type = Ty::simple(TypeCtor::Int(IntTy::u8())); |
596 | let array_type = Ty::apply_one(TypeCtor::Array, byte_type); | 586 | let array_type = Ty::apply_one(TypeCtor::Array, byte_type); |
597 | Ty::apply_one(TypeCtor::Ref(Mutability::Shared), array_type) | 587 | Ty::apply_one(TypeCtor::Ref(Mutability::Shared), array_type) |
598 | } | 588 | } |
599 | Literal::Char(..) => Ty::simple(TypeCtor::Char), | 589 | Literal::Char(..) => Ty::simple(TypeCtor::Char), |
600 | Literal::Int(_v, ty) => Ty::simple(TypeCtor::Int((*ty).into())), | 590 | Literal::Int(_v, ty) => match ty { |
601 | Literal::Float(_v, ty) => Ty::simple(TypeCtor::Float((*ty).into())), | 591 | Some(int_ty) => Ty::simple(TypeCtor::Int((*int_ty).into())), |
592 | None => self.table.new_integer_var(), | ||
593 | }, | ||
594 | Literal::Float(_v, ty) => match ty { | ||
595 | Some(float_ty) => Ty::simple(TypeCtor::Float((*float_ty).into())), | ||
596 | None => self.table.new_float_var(), | ||
597 | }, | ||
602 | }, | 598 | }, |
603 | }; | 599 | }; |
604 | // use a new type variable if we got Ty::Unknown here | 600 | // use a new type variable if we got Ty::Unknown here |
diff --git a/crates/ra_hir_ty/src/lib.rs b/crates/ra_hir_ty/src/lib.rs index 135976fcd..2b9372b4b 100644 --- a/crates/ra_hir_ty/src/lib.rs +++ b/crates/ra_hir_ty/src/lib.rs | |||
@@ -58,7 +58,7 @@ use ra_db::{impl_intern_key, salsa, CrateId}; | |||
58 | 58 | ||
59 | use crate::{ | 59 | use crate::{ |
60 | db::HirDatabase, | 60 | db::HirDatabase, |
61 | primitive::{FloatTy, IntTy, Uncertain}, | 61 | primitive::{FloatTy, IntTy}, |
62 | utils::{generics, make_mut_slice, Generics}, | 62 | utils::{generics, make_mut_slice, Generics}, |
63 | }; | 63 | }; |
64 | use display::HirDisplay; | 64 | use display::HirDisplay; |
@@ -87,10 +87,10 @@ pub enum TypeCtor { | |||
87 | Char, | 87 | Char, |
88 | 88 | ||
89 | /// A primitive integer type. For example, `i32`. | 89 | /// A primitive integer type. For example, `i32`. |
90 | Int(Uncertain<IntTy>), | 90 | Int(IntTy), |
91 | 91 | ||
92 | /// A primitive floating-point type. For example, `f64`. | 92 | /// A primitive floating-point type. For example, `f64`. |
93 | Float(Uncertain<FloatTy>), | 93 | Float(FloatTy), |
94 | 94 | ||
95 | /// Structures, enumerations and unions. | 95 | /// Structures, enumerations and unions. |
96 | Adt(AdtId), | 96 | Adt(AdtId), |
diff --git a/crates/ra_hir_ty/src/method_resolution.rs b/crates/ra_hir_ty/src/method_resolution.rs index e19628fdf..e83b39456 100644 --- a/crates/ra_hir_ty/src/method_resolution.rs +++ b/crates/ra_hir_ty/src/method_resolution.rs | |||
@@ -16,12 +16,8 @@ use rustc_hash::{FxHashMap, FxHashSet}; | |||
16 | 16 | ||
17 | use super::Substs; | 17 | use super::Substs; |
18 | use crate::{ | 18 | use crate::{ |
19 | autoderef, | 19 | autoderef, db::HirDatabase, primitive::FloatBitness, utils::all_super_traits, ApplicationTy, |
20 | db::HirDatabase, | 20 | Canonical, DebruijnIndex, InEnvironment, TraitEnvironment, TraitRef, Ty, TypeCtor, TypeWalk, |
21 | primitive::{FloatBitness, Uncertain}, | ||
22 | utils::all_super_traits, | ||
23 | ApplicationTy, Canonical, DebruijnIndex, InEnvironment, TraitEnvironment, TraitRef, Ty, | ||
24 | TypeCtor, TypeWalk, | ||
25 | }; | 21 | }; |
26 | 22 | ||
27 | /// This is used as a key for indexing impls. | 23 | /// This is used as a key for indexing impls. |
@@ -147,12 +143,12 @@ impl Ty { | |||
147 | } | 143 | } |
148 | TypeCtor::Bool => lang_item_crate!("bool"), | 144 | TypeCtor::Bool => lang_item_crate!("bool"), |
149 | TypeCtor::Char => lang_item_crate!("char"), | 145 | TypeCtor::Char => lang_item_crate!("char"), |
150 | TypeCtor::Float(Uncertain::Known(f)) => match f.bitness { | 146 | TypeCtor::Float(f) => match f.bitness { |
151 | // There are two lang items: one in libcore (fXX) and one in libstd (fXX_runtime) | 147 | // There are two lang items: one in libcore (fXX) and one in libstd (fXX_runtime) |
152 | FloatBitness::X32 => lang_item_crate!("f32", "f32_runtime"), | 148 | FloatBitness::X32 => lang_item_crate!("f32", "f32_runtime"), |
153 | FloatBitness::X64 => lang_item_crate!("f64", "f64_runtime"), | 149 | FloatBitness::X64 => lang_item_crate!("f64", "f64_runtime"), |
154 | }, | 150 | }, |
155 | TypeCtor::Int(Uncertain::Known(i)) => lang_item_crate!(i.ty_to_string()), | 151 | TypeCtor::Int(i) => lang_item_crate!(i.ty_to_string()), |
156 | TypeCtor::Str => lang_item_crate!("str_alloc", "str"), | 152 | TypeCtor::Str => lang_item_crate!("str_alloc", "str"), |
157 | TypeCtor::Slice => lang_item_crate!("slice_alloc", "slice"), | 153 | TypeCtor::Slice => lang_item_crate!("slice_alloc", "slice"), |
158 | TypeCtor::RawPtr(Mutability::Shared) => lang_item_crate!("const_ptr"), | 154 | TypeCtor::RawPtr(Mutability::Shared) => lang_item_crate!("const_ptr"), |
diff --git a/crates/ra_hir_ty/src/primitive.rs b/crates/ra_hir_ty/src/primitive.rs index 02a8179d9..37966b709 100644 --- a/crates/ra_hir_ty/src/primitive.rs +++ b/crates/ra_hir_ty/src/primitive.rs | |||
@@ -7,42 +7,6 @@ use std::fmt; | |||
7 | 7 | ||
8 | pub use hir_def::builtin_type::{BuiltinFloat, BuiltinInt, FloatBitness, IntBitness, Signedness}; | 8 | pub use hir_def::builtin_type::{BuiltinFloat, BuiltinInt, FloatBitness, IntBitness, Signedness}; |
9 | 9 | ||
10 | #[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)] | ||
11 | pub enum Uncertain<T> { | ||
12 | Unknown, | ||
13 | Known(T), | ||
14 | } | ||
15 | |||
16 | impl From<IntTy> for Uncertain<IntTy> { | ||
17 | fn from(ty: IntTy) -> Self { | ||
18 | Uncertain::Known(ty) | ||
19 | } | ||
20 | } | ||
21 | |||
22 | impl fmt::Display for Uncertain<IntTy> { | ||
23 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
24 | match *self { | ||
25 | Uncertain::Unknown => write!(f, "{{integer}}"), | ||
26 | Uncertain::Known(ty) => write!(f, "{}", ty), | ||
27 | } | ||
28 | } | ||
29 | } | ||
30 | |||
31 | impl From<FloatTy> for Uncertain<FloatTy> { | ||
32 | fn from(ty: FloatTy) -> Self { | ||
33 | Uncertain::Known(ty) | ||
34 | } | ||
35 | } | ||
36 | |||
37 | impl fmt::Display for Uncertain<FloatTy> { | ||
38 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
39 | match *self { | ||
40 | Uncertain::Unknown => write!(f, "{{float}}"), | ||
41 | Uncertain::Known(ty) => write!(f, "{}", ty), | ||
42 | } | ||
43 | } | ||
44 | } | ||
45 | |||
46 | #[derive(Copy, Clone, Eq, PartialEq, Hash)] | 10 | #[derive(Copy, Clone, Eq, PartialEq, Hash)] |
47 | pub struct IntTy { | 11 | pub struct IntTy { |
48 | pub signedness: Signedness, | 12 | pub signedness: Signedness, |
@@ -173,21 +137,3 @@ impl From<BuiltinFloat> for FloatTy { | |||
173 | FloatTy { bitness: t.bitness } | 137 | FloatTy { bitness: t.bitness } |
174 | } | 138 | } |
175 | } | 139 | } |
176 | |||
177 | impl From<Option<BuiltinInt>> for Uncertain<IntTy> { | ||
178 | fn from(t: Option<BuiltinInt>) -> Self { | ||
179 | match t { | ||
180 | None => Uncertain::Unknown, | ||
181 | Some(t) => Uncertain::Known(t.into()), | ||
182 | } | ||
183 | } | ||
184 | } | ||
185 | |||
186 | impl From<Option<BuiltinFloat>> for Uncertain<FloatTy> { | ||
187 | fn from(t: Option<BuiltinFloat>) -> Self { | ||
188 | match t { | ||
189 | None => Uncertain::Unknown, | ||
190 | Some(t) => Uncertain::Known(t.into()), | ||
191 | } | ||
192 | } | ||
193 | } | ||
diff --git a/crates/ra_hir_ty/src/traits/chalk/mapping.rs b/crates/ra_hir_ty/src/traits/chalk/mapping.rs index 28a5fbe3e..18e5c9c16 100644 --- a/crates/ra_hir_ty/src/traits/chalk/mapping.rs +++ b/crates/ra_hir_ty/src/traits/chalk/mapping.rs | |||
@@ -14,7 +14,7 @@ use ra_db::salsa::InternKey; | |||
14 | 14 | ||
15 | use crate::{ | 15 | use crate::{ |
16 | db::HirDatabase, | 16 | db::HirDatabase, |
17 | primitive::{FloatBitness, FloatTy, IntBitness, IntTy, Signedness, Uncertain}, | 17 | primitive::{FloatBitness, FloatTy, IntBitness, IntTy, Signedness}, |
18 | traits::{builtin, AssocTyValue, Canonical, Impl, Obligation}, | 18 | traits::{builtin, AssocTyValue, Canonical, Impl, Obligation}, |
19 | ApplicationTy, CallableDef, GenericPredicate, InEnvironment, OpaqueTy, OpaqueTyId, | 19 | ApplicationTy, CallableDef, GenericPredicate, InEnvironment, OpaqueTy, OpaqueTyId, |
20 | ProjectionPredicate, ProjectionTy, Substs, TraitEnvironment, TraitRef, Ty, TypeCtor, | 20 | ProjectionPredicate, ProjectionTy, Substs, TraitEnvironment, TraitRef, Ty, TypeCtor, |
@@ -249,11 +249,11 @@ impl ToChalk for TypeCtor { | |||
249 | 249 | ||
250 | TypeCtor::Bool => TypeName::Scalar(Scalar::Bool), | 250 | TypeCtor::Bool => TypeName::Scalar(Scalar::Bool), |
251 | TypeCtor::Char => TypeName::Scalar(Scalar::Char), | 251 | TypeCtor::Char => TypeName::Scalar(Scalar::Char), |
252 | TypeCtor::Int(Uncertain::Known(int_ty)) => TypeName::Scalar(int_ty_to_chalk(int_ty)), | 252 | TypeCtor::Int(int_ty) => TypeName::Scalar(int_ty_to_chalk(int_ty)), |
253 | TypeCtor::Float(Uncertain::Known(FloatTy { bitness: FloatBitness::X32 })) => { | 253 | TypeCtor::Float(FloatTy { bitness: FloatBitness::X32 }) => { |
254 | TypeName::Scalar(Scalar::Float(chalk_ir::FloatTy::F32)) | 254 | TypeName::Scalar(Scalar::Float(chalk_ir::FloatTy::F32)) |
255 | } | 255 | } |
256 | TypeCtor::Float(Uncertain::Known(FloatTy { bitness: FloatBitness::X64 })) => { | 256 | TypeCtor::Float(FloatTy { bitness: FloatBitness::X64 }) => { |
257 | TypeName::Scalar(Scalar::Float(chalk_ir::FloatTy::F64)) | 257 | TypeName::Scalar(Scalar::Float(chalk_ir::FloatTy::F64)) |
258 | } | 258 | } |
259 | 259 | ||
@@ -268,9 +268,7 @@ impl ToChalk for TypeCtor { | |||
268 | } | 268 | } |
269 | TypeCtor::Never => TypeName::Never, | 269 | TypeCtor::Never => TypeName::Never, |
270 | 270 | ||
271 | TypeCtor::Int(Uncertain::Unknown) | 271 | TypeCtor::Adt(_) |
272 | | TypeCtor::Float(Uncertain::Unknown) | ||
273 | | TypeCtor::Adt(_) | ||
274 | | TypeCtor::Array | 272 | | TypeCtor::Array |
275 | | TypeCtor::FnPtr { .. } | 273 | | TypeCtor::FnPtr { .. } |
276 | | TypeCtor::Closure { .. } => { | 274 | | TypeCtor::Closure { .. } => { |
@@ -291,19 +289,19 @@ impl ToChalk for TypeCtor { | |||
291 | 289 | ||
292 | TypeName::Scalar(Scalar::Bool) => TypeCtor::Bool, | 290 | TypeName::Scalar(Scalar::Bool) => TypeCtor::Bool, |
293 | TypeName::Scalar(Scalar::Char) => TypeCtor::Char, | 291 | TypeName::Scalar(Scalar::Char) => TypeCtor::Char, |
294 | TypeName::Scalar(Scalar::Int(int_ty)) => TypeCtor::Int(Uncertain::Known(IntTy { | 292 | TypeName::Scalar(Scalar::Int(int_ty)) => TypeCtor::Int(IntTy { |
295 | signedness: Signedness::Signed, | 293 | signedness: Signedness::Signed, |
296 | bitness: bitness_from_chalk_int(int_ty), | 294 | bitness: bitness_from_chalk_int(int_ty), |
297 | })), | 295 | }), |
298 | TypeName::Scalar(Scalar::Uint(uint_ty)) => TypeCtor::Int(Uncertain::Known(IntTy { | 296 | TypeName::Scalar(Scalar::Uint(uint_ty)) => TypeCtor::Int(IntTy { |
299 | signedness: Signedness::Unsigned, | 297 | signedness: Signedness::Unsigned, |
300 | bitness: bitness_from_chalk_uint(uint_ty), | 298 | bitness: bitness_from_chalk_uint(uint_ty), |
301 | })), | 299 | }), |
302 | TypeName::Scalar(Scalar::Float(chalk_ir::FloatTy::F32)) => { | 300 | TypeName::Scalar(Scalar::Float(chalk_ir::FloatTy::F32)) => { |
303 | TypeCtor::Float(Uncertain::Known(FloatTy { bitness: FloatBitness::X32 })) | 301 | TypeCtor::Float(FloatTy { bitness: FloatBitness::X32 }) |
304 | } | 302 | } |
305 | TypeName::Scalar(Scalar::Float(chalk_ir::FloatTy::F64)) => { | 303 | TypeName::Scalar(Scalar::Float(chalk_ir::FloatTy::F64)) => { |
306 | TypeCtor::Float(Uncertain::Known(FloatTy { bitness: FloatBitness::X64 })) | 304 | TypeCtor::Float(FloatTy { bitness: FloatBitness::X64 }) |
307 | } | 305 | } |
308 | TypeName::Tuple(cardinality) => TypeCtor::Tuple { cardinality: cardinality as u16 }, | 306 | TypeName::Tuple(cardinality) => TypeCtor::Tuple { cardinality: cardinality as u16 }, |
309 | TypeName::Raw(mutability) => TypeCtor::RawPtr(from_chalk(db, mutability)), | 307 | TypeName::Raw(mutability) => TypeCtor::RawPtr(from_chalk(db, mutability)), |
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index 846d8c69b..ad78b7671 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs | |||
@@ -14,34 +14,43 @@ use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffs | |||
14 | 14 | ||
15 | use crate::{ | 15 | use crate::{ |
16 | display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel, ToNav}, | 16 | display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel, ToNav}, |
17 | FilePosition, NavigationTarget, RangeInfo, | 17 | runnables::runnable, |
18 | FileId, FilePosition, NavigationTarget, RangeInfo, Runnable, | ||
18 | }; | 19 | }; |
20 | use test_utils::mark; | ||
19 | 21 | ||
20 | #[derive(Clone, Debug, PartialEq, Eq)] | 22 | #[derive(Clone, Debug, PartialEq, Eq)] |
21 | pub struct HoverConfig { | 23 | pub struct HoverConfig { |
22 | pub implementations: bool, | 24 | pub implementations: bool, |
25 | pub run: bool, | ||
26 | pub debug: bool, | ||
23 | } | 27 | } |
24 | 28 | ||
25 | impl Default for HoverConfig { | 29 | impl Default for HoverConfig { |
26 | fn default() -> Self { | 30 | fn default() -> Self { |
27 | Self { implementations: true } | 31 | Self { implementations: true, run: true, debug: true } |
28 | } | 32 | } |
29 | } | 33 | } |
30 | 34 | ||
31 | impl HoverConfig { | 35 | impl HoverConfig { |
32 | pub const NO_ACTIONS: Self = Self { implementations: false }; | 36 | pub const NO_ACTIONS: Self = Self { implementations: false, run: false, debug: false }; |
33 | 37 | ||
34 | pub fn any(&self) -> bool { | 38 | pub fn any(&self) -> bool { |
35 | self.implementations | 39 | self.implementations || self.runnable() |
36 | } | 40 | } |
37 | 41 | ||
38 | pub fn none(&self) -> bool { | 42 | pub fn none(&self) -> bool { |
39 | !self.any() | 43 | !self.any() |
40 | } | 44 | } |
45 | |||
46 | pub fn runnable(&self) -> bool { | ||
47 | self.run || self.debug | ||
48 | } | ||
41 | } | 49 | } |
42 | 50 | ||
43 | #[derive(Debug, Clone)] | 51 | #[derive(Debug, Clone)] |
44 | pub enum HoverAction { | 52 | pub enum HoverAction { |
53 | Runnable(Runnable), | ||
45 | Implementaion(FilePosition), | 54 | Implementaion(FilePosition), |
46 | } | 55 | } |
47 | 56 | ||
@@ -125,6 +134,10 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn | |||
125 | res.push_action(action); | 134 | res.push_action(action); |
126 | } | 135 | } |
127 | 136 | ||
137 | if let Some(action) = runnable_action(&sema, name_kind, position.file_id) { | ||
138 | res.push_action(action); | ||
139 | } | ||
140 | |||
128 | return Some(RangeInfo::new(range, res)); | 141 | return Some(RangeInfo::new(range, res)); |
129 | } | 142 | } |
130 | } | 143 | } |
@@ -175,6 +188,36 @@ fn show_implementations_action(db: &RootDatabase, def: Definition) -> Option<Hov | |||
175 | } | 188 | } |
176 | } | 189 | } |
177 | 190 | ||
191 | fn runnable_action( | ||
192 | sema: &Semantics<RootDatabase>, | ||
193 | def: Definition, | ||
194 | file_id: FileId, | ||
195 | ) -> Option<HoverAction> { | ||
196 | match def { | ||
197 | Definition::ModuleDef(it) => match it { | ||
198 | ModuleDef::Module(it) => match it.definition_source(sema.db).value { | ||
199 | ModuleSource::Module(it) => runnable(&sema, it.syntax().clone(), file_id) | ||
200 | .map(|it| HoverAction::Runnable(it)), | ||
201 | _ => None, | ||
202 | }, | ||
203 | ModuleDef::Function(it) => { | ||
204 | let src = it.source(sema.db); | ||
205 | if src.file_id != file_id.into() { | ||
206 | mark::hit!(hover_macro_generated_struct_fn_doc_comment); | ||
207 | mark::hit!(hover_macro_generated_struct_fn_doc_attr); | ||
208 | |||
209 | return None; | ||
210 | } | ||
211 | |||
212 | runnable(&sema, src.value.syntax().clone(), file_id) | ||
213 | .map(|it| HoverAction::Runnable(it)) | ||
214 | } | ||
215 | _ => None, | ||
216 | }, | ||
217 | _ => None, | ||
218 | } | ||
219 | } | ||
220 | |||
178 | fn hover_text( | 221 | fn hover_text( |
179 | docs: Option<String>, | 222 | docs: Option<String>, |
180 | desc: Option<String>, | 223 | desc: Option<String>, |
@@ -292,6 +335,7 @@ fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { | |||
292 | #[cfg(test)] | 335 | #[cfg(test)] |
293 | mod tests { | 336 | mod tests { |
294 | use super::*; | 337 | use super::*; |
338 | use insta::assert_debug_snapshot; | ||
295 | 339 | ||
296 | use ra_db::FileLoader; | 340 | use ra_db::FileLoader; |
297 | use ra_syntax::TextRange; | 341 | use ra_syntax::TextRange; |
@@ -309,6 +353,7 @@ mod tests { | |||
309 | fn assert_impl_action(action: &HoverAction, position: u32) { | 353 | fn assert_impl_action(action: &HoverAction, position: u32) { |
310 | let offset = match action { | 354 | let offset = match action { |
311 | HoverAction::Implementaion(pos) => pos.offset, | 355 | HoverAction::Implementaion(pos) => pos.offset, |
356 | it => panic!("Unexpected hover action: {:#?}", it), | ||
312 | }; | 357 | }; |
313 | assert_eq!(offset, position.into()); | 358 | assert_eq!(offset, position.into()); |
314 | } | 359 | } |
@@ -1076,6 +1121,8 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
1076 | 1121 | ||
1077 | #[test] | 1122 | #[test] |
1078 | fn test_hover_macro_generated_struct_fn_doc_comment() { | 1123 | fn test_hover_macro_generated_struct_fn_doc_comment() { |
1124 | mark::check!(hover_macro_generated_struct_fn_doc_comment); | ||
1125 | |||
1079 | check_hover_result( | 1126 | check_hover_result( |
1080 | r#" | 1127 | r#" |
1081 | //- /lib.rs | 1128 | //- /lib.rs |
@@ -1102,6 +1149,8 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
1102 | 1149 | ||
1103 | #[test] | 1150 | #[test] |
1104 | fn test_hover_macro_generated_struct_fn_doc_attr() { | 1151 | fn test_hover_macro_generated_struct_fn_doc_attr() { |
1152 | mark::check!(hover_macro_generated_struct_fn_doc_attr); | ||
1153 | |||
1105 | check_hover_result( | 1154 | check_hover_result( |
1106 | r#" | 1155 | r#" |
1107 | //- /lib.rs | 1156 | //- /lib.rs |
@@ -1176,4 +1225,89 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
1176 | ); | 1225 | ); |
1177 | assert_impl_action(&actions[0], 5); | 1226 | assert_impl_action(&actions[0], 5); |
1178 | } | 1227 | } |
1228 | |||
1229 | #[test] | ||
1230 | fn test_hover_test_has_action() { | ||
1231 | let (_, actions) = check_hover_result( | ||
1232 | " | ||
1233 | //- /lib.rs | ||
1234 | #[test] | ||
1235 | fn foo_<|>test() {} | ||
1236 | ", | ||
1237 | &["fn foo_test()"], | ||
1238 | ); | ||
1239 | assert_debug_snapshot!(actions, | ||
1240 | @r###" | ||
1241 | [ | ||
1242 | Runnable( | ||
1243 | Runnable { | ||
1244 | nav: NavigationTarget { | ||
1245 | file_id: FileId( | ||
1246 | 1, | ||
1247 | ), | ||
1248 | full_range: 0..24, | ||
1249 | name: "foo_test", | ||
1250 | kind: FN_DEF, | ||
1251 | focus_range: Some( | ||
1252 | 11..19, | ||
1253 | ), | ||
1254 | container_name: None, | ||
1255 | description: None, | ||
1256 | docs: None, | ||
1257 | }, | ||
1258 | kind: Test { | ||
1259 | test_id: Path( | ||
1260 | "foo_test", | ||
1261 | ), | ||
1262 | attr: TestAttr { | ||
1263 | ignore: false, | ||
1264 | }, | ||
1265 | }, | ||
1266 | cfg_exprs: [], | ||
1267 | }, | ||
1268 | ), | ||
1269 | ] | ||
1270 | "###); | ||
1271 | } | ||
1272 | |||
1273 | #[test] | ||
1274 | fn test_hover_test_mod_has_action() { | ||
1275 | let (_, actions) = check_hover_result( | ||
1276 | " | ||
1277 | //- /lib.rs | ||
1278 | mod tests<|> { | ||
1279 | #[test] | ||
1280 | fn foo_test() {} | ||
1281 | } | ||
1282 | ", | ||
1283 | &["mod tests"], | ||
1284 | ); | ||
1285 | assert_debug_snapshot!(actions, | ||
1286 | @r###" | ||
1287 | [ | ||
1288 | Runnable( | ||
1289 | Runnable { | ||
1290 | nav: NavigationTarget { | ||
1291 | file_id: FileId( | ||
1292 | 1, | ||
1293 | ), | ||
1294 | full_range: 0..46, | ||
1295 | name: "tests", | ||
1296 | kind: MODULE, | ||
1297 | focus_range: Some( | ||
1298 | 4..9, | ||
1299 | ), | ||
1300 | container_name: None, | ||
1301 | description: None, | ||
1302 | docs: None, | ||
1303 | }, | ||
1304 | kind: TestMod { | ||
1305 | path: "tests", | ||
1306 | }, | ||
1307 | cfg_exprs: [], | ||
1308 | }, | ||
1309 | ), | ||
1310 | ] | ||
1311 | "###); | ||
1312 | } | ||
1179 | } | 1313 | } |
diff --git a/crates/ra_ide/src/runnables.rs b/crates/ra_ide/src/runnables.rs index f32ce0d22..fc57dc33d 100644 --- a/crates/ra_ide/src/runnables.rs +++ b/crates/ra_ide/src/runnables.rs | |||
@@ -11,14 +11,14 @@ use ra_syntax::{ | |||
11 | 11 | ||
12 | use crate::{display::ToNav, FileId, NavigationTarget}; | 12 | use crate::{display::ToNav, FileId, NavigationTarget}; |
13 | 13 | ||
14 | #[derive(Debug)] | 14 | #[derive(Debug, Clone)] |
15 | pub struct Runnable { | 15 | pub struct Runnable { |
16 | pub nav: NavigationTarget, | 16 | pub nav: NavigationTarget, |
17 | pub kind: RunnableKind, | 17 | pub kind: RunnableKind, |
18 | pub cfg_exprs: Vec<CfgExpr>, | 18 | pub cfg_exprs: Vec<CfgExpr>, |
19 | } | 19 | } |
20 | 20 | ||
21 | #[derive(Debug)] | 21 | #[derive(Debug, Clone)] |
22 | pub enum TestId { | 22 | pub enum TestId { |
23 | Name(String), | 23 | Name(String), |
24 | Path(String), | 24 | Path(String), |
@@ -33,7 +33,7 @@ impl fmt::Display for TestId { | |||
33 | } | 33 | } |
34 | } | 34 | } |
35 | 35 | ||
36 | #[derive(Debug)] | 36 | #[derive(Debug, Clone)] |
37 | pub enum RunnableKind { | 37 | pub enum RunnableKind { |
38 | Test { test_id: TestId, attr: TestAttr }, | 38 | Test { test_id: TestId, attr: TestAttr }, |
39 | TestMod { path: String }, | 39 | TestMod { path: String }, |
@@ -42,6 +42,42 @@ pub enum RunnableKind { | |||
42 | Bin, | 42 | Bin, |
43 | } | 43 | } |
44 | 44 | ||
45 | #[derive(Debug, Eq, PartialEq)] | ||
46 | pub struct RunnableAction { | ||
47 | pub run_title: &'static str, | ||
48 | pub debugee: bool, | ||
49 | } | ||
50 | |||
51 | const TEST: RunnableAction = RunnableAction { run_title: "▶\u{fe0e} Run Test", debugee: true }; | ||
52 | const DOCTEST: RunnableAction = | ||
53 | RunnableAction { run_title: "▶\u{fe0e} Run Doctest", debugee: false }; | ||
54 | const BENCH: RunnableAction = RunnableAction { run_title: "▶\u{fe0e} Run Bench", debugee: true }; | ||
55 | const BIN: RunnableAction = RunnableAction { run_title: "▶\u{fe0e} Run", debugee: true }; | ||
56 | |||
57 | impl Runnable { | ||
58 | // test package::module::testname | ||
59 | pub fn label(&self, target: Option<String>) -> String { | ||
60 | match &self.kind { | ||
61 | RunnableKind::Test { test_id, .. } => format!("test {}", test_id), | ||
62 | RunnableKind::TestMod { path } => format!("test-mod {}", path), | ||
63 | RunnableKind::Bench { test_id } => format!("bench {}", test_id), | ||
64 | RunnableKind::DocTest { test_id, .. } => format!("doctest {}", test_id), | ||
65 | RunnableKind::Bin => { | ||
66 | target.map_or_else(|| "run binary".to_string(), |t| format!("run {}", t)) | ||
67 | } | ||
68 | } | ||
69 | } | ||
70 | |||
71 | pub fn action(&self) -> &'static RunnableAction { | ||
72 | match &self.kind { | ||
73 | RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => &TEST, | ||
74 | RunnableKind::DocTest { .. } => &DOCTEST, | ||
75 | RunnableKind::Bench { .. } => &BENCH, | ||
76 | RunnableKind::Bin => &BIN, | ||
77 | } | ||
78 | } | ||
79 | } | ||
80 | |||
45 | // Feature: Run | 81 | // Feature: Run |
46 | // | 82 | // |
47 | // Shows a popup suggesting to run a test/benchmark/binary **at the current cursor | 83 | // Shows a popup suggesting to run a test/benchmark/binary **at the current cursor |
@@ -59,7 +95,11 @@ pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> { | |||
59 | source_file.syntax().descendants().filter_map(|i| runnable(&sema, i, file_id)).collect() | 95 | source_file.syntax().descendants().filter_map(|i| runnable(&sema, i, file_id)).collect() |
60 | } | 96 | } |
61 | 97 | ||
62 | fn runnable(sema: &Semantics<RootDatabase>, item: SyntaxNode, file_id: FileId) -> Option<Runnable> { | 98 | pub(crate) fn runnable( |
99 | sema: &Semantics<RootDatabase>, | ||
100 | item: SyntaxNode, | ||
101 | file_id: FileId, | ||
102 | ) -> Option<Runnable> { | ||
63 | match_ast! { | 103 | match_ast! { |
64 | match item { | 104 | match item { |
65 | ast::FnDef(it) => runnable_fn(sema, it, file_id), | 105 | ast::FnDef(it) => runnable_fn(sema, it, file_id), |
@@ -135,7 +175,7 @@ fn runnable_fn( | |||
135 | Some(Runnable { nav, kind, cfg_exprs }) | 175 | Some(Runnable { nav, kind, cfg_exprs }) |
136 | } | 176 | } |
137 | 177 | ||
138 | #[derive(Debug)] | 178 | #[derive(Debug, Copy, Clone)] |
139 | pub struct TestAttr { | 179 | pub struct TestAttr { |
140 | pub ignore: bool, | 180 | pub ignore: bool, |
141 | } | 181 | } |
@@ -207,6 +247,15 @@ mod tests { | |||
207 | 247 | ||
208 | use crate::mock_analysis::analysis_and_position; | 248 | use crate::mock_analysis::analysis_and_position; |
209 | 249 | ||
250 | use super::{Runnable, RunnableAction, BENCH, BIN, DOCTEST, TEST}; | ||
251 | |||
252 | fn assert_actions(runnables: &[Runnable], actions: &[&RunnableAction]) { | ||
253 | assert_eq!( | ||
254 | actions, | ||
255 | runnables.into_iter().map(|it| it.action()).collect::<Vec<_>>().as_slice() | ||
256 | ); | ||
257 | } | ||
258 | |||
210 | #[test] | 259 | #[test] |
211 | fn test_runnables() { | 260 | fn test_runnables() { |
212 | let (analysis, pos) = analysis_and_position( | 261 | let (analysis, pos) = analysis_and_position( |
@@ -221,6 +270,9 @@ mod tests { | |||
221 | #[test] | 270 | #[test] |
222 | #[ignore] | 271 | #[ignore] |
223 | fn test_foo() {} | 272 | fn test_foo() {} |
273 | |||
274 | #[bench] | ||
275 | fn bench() {} | ||
224 | "#, | 276 | "#, |
225 | ); | 277 | ); |
226 | let runnables = analysis.runnables(pos.file_id).unwrap(); | 278 | let runnables = analysis.runnables(pos.file_id).unwrap(); |
@@ -295,9 +347,32 @@ mod tests { | |||
295 | }, | 347 | }, |
296 | cfg_exprs: [], | 348 | cfg_exprs: [], |
297 | }, | 349 | }, |
350 | Runnable { | ||
351 | nav: NavigationTarget { | ||
352 | file_id: FileId( | ||
353 | 1, | ||
354 | ), | ||
355 | full_range: 82..104, | ||
356 | name: "bench", | ||
357 | kind: FN_DEF, | ||
358 | focus_range: Some( | ||
359 | 94..99, | ||
360 | ), | ||
361 | container_name: None, | ||
362 | description: None, | ||
363 | docs: None, | ||
364 | }, | ||
365 | kind: Bench { | ||
366 | test_id: Path( | ||
367 | "bench", | ||
368 | ), | ||
369 | }, | ||
370 | cfg_exprs: [], | ||
371 | }, | ||
298 | ] | 372 | ] |
299 | "### | 373 | "### |
300 | ); | 374 | ); |
375 | assert_actions(&runnables, &[&BIN, &TEST, &TEST, &BENCH]); | ||
301 | } | 376 | } |
302 | 377 | ||
303 | #[test] | 378 | #[test] |
@@ -361,6 +436,7 @@ mod tests { | |||
361 | ] | 436 | ] |
362 | "### | 437 | "### |
363 | ); | 438 | ); |
439 | assert_actions(&runnables, &[&BIN, &DOCTEST]); | ||
364 | } | 440 | } |
365 | 441 | ||
366 | #[test] | 442 | #[test] |
@@ -427,6 +503,7 @@ mod tests { | |||
427 | ] | 503 | ] |
428 | "### | 504 | "### |
429 | ); | 505 | ); |
506 | assert_actions(&runnables, &[&BIN, &DOCTEST]); | ||
430 | } | 507 | } |
431 | 508 | ||
432 | #[test] | 509 | #[test] |
@@ -493,6 +570,7 @@ mod tests { | |||
493 | ] | 570 | ] |
494 | "### | 571 | "### |
495 | ); | 572 | ); |
573 | assert_actions(&runnables, &[&TEST, &TEST]); | ||
496 | } | 574 | } |
497 | 575 | ||
498 | #[test] | 576 | #[test] |
@@ -561,6 +639,7 @@ mod tests { | |||
561 | ] | 639 | ] |
562 | "### | 640 | "### |
563 | ); | 641 | ); |
642 | assert_actions(&runnables, &[&TEST, &TEST]); | ||
564 | } | 643 | } |
565 | 644 | ||
566 | #[test] | 645 | #[test] |
@@ -631,6 +710,7 @@ mod tests { | |||
631 | ] | 710 | ] |
632 | "### | 711 | "### |
633 | ); | 712 | ); |
713 | assert_actions(&runnables, &[&TEST, &TEST]); | ||
634 | } | 714 | } |
635 | 715 | ||
636 | #[test] | 716 | #[test] |
@@ -681,6 +761,7 @@ mod tests { | |||
681 | ] | 761 | ] |
682 | "### | 762 | "### |
683 | ); | 763 | ); |
764 | assert_actions(&runnables, &[&TEST]); | ||
684 | } | 765 | } |
685 | 766 | ||
686 | #[test] | 767 | #[test] |
@@ -739,6 +820,7 @@ mod tests { | |||
739 | ] | 820 | ] |
740 | "### | 821 | "### |
741 | ); | 822 | ); |
823 | assert_actions(&runnables, &[&TEST]); | ||
742 | } | 824 | } |
743 | 825 | ||
744 | #[test] | 826 | #[test] |
diff --git a/crates/ra_ide/src/snapshots/highlight_doctest.html b/crates/ra_ide/src/snapshots/highlight_doctest.html new file mode 100644 index 000000000..2f2d8c900 --- /dev/null +++ b/crates/ra_ide/src/snapshots/highlight_doctest.html | |||
@@ -0,0 +1,70 @@ | |||
1 | |||
2 | <style> | ||
3 | body { margin: 0; } | ||
4 | pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; } | ||
5 | |||
6 | .lifetime { color: #DFAF8F; font-style: italic; } | ||
7 | .comment { color: #7F9F7F; } | ||
8 | .struct, .enum { color: #7CB8BB; } | ||
9 | .enum_variant { color: #BDE0F3; } | ||
10 | .string_literal { color: #CC9393; } | ||
11 | .field { color: #94BFF3; } | ||
12 | .function { color: #93E0E3; } | ||
13 | .operator.unsafe { color: #E28C14; } | ||
14 | .parameter { color: #94BFF3; } | ||
15 | .text { color: #DCDCCC; } | ||
16 | .type { color: #7CB8BB; } | ||
17 | .builtin_type { color: #8CD0D3; } | ||
18 | .type_param { color: #DFAF8F; } | ||
19 | .attribute { color: #94BFF3; } | ||
20 | .numeric_literal { color: #BFEBBF; } | ||
21 | .bool_literal { color: #BFE6EB; } | ||
22 | .macro { color: #94BFF3; } | ||
23 | .module { color: #AFD8AF; } | ||
24 | .variable { color: #DCDCCC; } | ||
25 | .format_specifier { color: #CC696B; } | ||
26 | .mutable { text-decoration: underline; } | ||
27 | |||
28 | .keyword { color: #F0DFAF; font-weight: bold; } | ||
29 | .keyword.unsafe { color: #BC8383; font-weight: bold; } | ||
30 | .control { font-style: italic; } | ||
31 | </style> | ||
32 | <pre><code><span class="keyword">impl</span> <span class="unresolved_reference">Foo</span> { | ||
33 | <span class="comment">/// Constructs a new `Foo`.</span> | ||
34 | <span class="comment">///</span> | ||
35 | <span class="comment">/// # Examples</span> | ||
36 | <span class="comment">///</span> | ||
37 | <span class="comment">/// ```</span> | ||
38 | <span class="comment">/// #</span> <span class="attribute">#![</span><span class="function attribute">allow</span><span class="attribute">(unused_mut)]</span> | ||
39 | <span class="comment">/// </span><span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">foo</span>: <span class="unresolved_reference">Foo</span> = <span class="unresolved_reference">Foo</span>::<span class="unresolved_reference">new</span>(); | ||
40 | <span class="comment">/// ```</span> | ||
41 | <span class="keyword">pub</span> <span class="keyword">const</span> <span class="keyword">fn</span> <span class="function declaration">new</span>() -> <span class="unresolved_reference">Foo</span> { | ||
42 | <span class="unresolved_reference">Foo</span> { } | ||
43 | } | ||
44 | |||
45 | <span class="comment">/// `bar` method on `Foo`.</span> | ||
46 | <span class="comment">///</span> | ||
47 | <span class="comment">/// # Examples</span> | ||
48 | <span class="comment">///</span> | ||
49 | <span class="comment">/// ```</span> | ||
50 | <span class="comment">/// </span><span class="keyword">let</span> <span class="variable declaration">foo</span> = <span class="unresolved_reference">Foo</span>::<span class="unresolved_reference">new</span>(); | ||
51 | <span class="comment">///</span> | ||
52 | <span class="comment">/// </span><span class="comment">// calls bar on foo</span> | ||
53 | <span class="comment">/// </span><span class="macro">assert!</span>(foo.bar()); | ||
54 | <span class="comment">///</span> | ||
55 | <span class="comment">/// </span><span class="comment">/* multi-line | ||
56 | </span><span class="comment">/// </span><span class="comment"> comment */</span> | ||
57 | <span class="comment">///</span> | ||
58 | <span class="comment">/// </span><span class="keyword">let</span> <span class="variable declaration">multi_line_string</span> = <span class="string_literal">"Foo | ||
59 | </span><span class="comment">/// </span><span class="string_literal"> bar | ||
60 | </span><span class="comment">/// </span><span class="string_literal"> "</span>; | ||
61 | <span class="comment">///</span> | ||
62 | <span class="comment">/// ```</span> | ||
63 | <span class="comment">///</span> | ||
64 | <span class="comment">/// ```</span> | ||
65 | <span class="comment">/// </span><span class="keyword">let</span> <span class="variable declaration">foobar</span> = <span class="unresolved_reference">Foo</span>::<span class="unresolved_reference">new</span>().<span class="unresolved_reference">bar</span>(); | ||
66 | <span class="comment">/// ```</span> | ||
67 | <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration">foo</span>(&<span class="self_keyword">self</span>) -> <span class="builtin_type">bool</span> { | ||
68 | <span class="bool_literal">true</span> | ||
69 | } | ||
70 | }</code></pre> \ No newline at end of file | ||
diff --git a/crates/ra_ide/src/snapshots/highlight_strings.html b/crates/ra_ide/src/snapshots/highlight_strings.html index e97192b61..6a5cf0e74 100644 --- a/crates/ra_ide/src/snapshots/highlight_strings.html +++ b/crates/ra_ide/src/snapshots/highlight_strings.html | |||
@@ -63,7 +63,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
63 | <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">^</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>); | 63 | <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">^</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>); |
64 | <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">></span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>); | 64 | <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">></span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>); |
65 | <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">+</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">5</span>); | 65 | <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">+</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">5</span>); |
66 | <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">#</span><span class="variable">x</span><span class="string_literal">}!"</span>, <span class="numeric_literal">27</span>); | 66 | <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">#</span><span class="variable">x</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">27</span>); |
67 | <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">0</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">5</span>); | 67 | <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">0</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">5</span>); |
68 | <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">0</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, -<span class="numeric_literal">5</span>); | 68 | <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">0</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, -<span class="numeric_literal">5</span>); |
69 | <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">#</span><span class="numeric_literal">0</span><span class="numeric_literal">10</span><span class="variable">x</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">27</span>); | 69 | <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">#</span><span class="numeric_literal">0</span><span class="numeric_literal">10</span><span class="variable">x</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">27</span>); |
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs index f6b52c35d..9ff7356c9 100644 --- a/crates/ra_ide/src/syntax_highlighting.rs +++ b/crates/ra_ide/src/syntax_highlighting.rs | |||
@@ -1,5 +1,6 @@ | |||
1 | mod tags; | 1 | mod tags; |
2 | mod html; | 2 | mod html; |
3 | mod injection; | ||
3 | #[cfg(test)] | 4 | #[cfg(test)] |
4 | mod tests; | 5 | mod tests; |
5 | 6 | ||
@@ -10,14 +11,14 @@ use ra_ide_db::{ | |||
10 | }; | 11 | }; |
11 | use ra_prof::profile; | 12 | use ra_prof::profile; |
12 | use ra_syntax::{ | 13 | use ra_syntax::{ |
13 | ast::{self, HasFormatSpecifier, HasQuotes, HasStringValue}, | 14 | ast::{self, HasFormatSpecifier}, |
14 | AstNode, AstToken, Direction, NodeOrToken, SyntaxElement, | 15 | AstNode, AstToken, Direction, NodeOrToken, SyntaxElement, |
15 | SyntaxKind::*, | 16 | SyntaxKind::*, |
16 | SyntaxToken, TextRange, WalkEvent, T, | 17 | TextRange, WalkEvent, T, |
17 | }; | 18 | }; |
18 | use rustc_hash::FxHashMap; | 19 | use rustc_hash::FxHashMap; |
19 | 20 | ||
20 | use crate::{call_info::ActiveParameter, Analysis, FileId}; | 21 | use crate::FileId; |
21 | 22 | ||
22 | use ast::FormatSpecifier; | 23 | use ast::FormatSpecifier; |
23 | pub(crate) use html::highlight_as_html; | 24 | pub(crate) use html::highlight_as_html; |
@@ -123,6 +124,23 @@ pub(crate) fn highlight( | |||
123 | _ => (), | 124 | _ => (), |
124 | } | 125 | } |
125 | 126 | ||
127 | // Check for Rust code in documentation | ||
128 | match &event { | ||
129 | WalkEvent::Leave(NodeOrToken::Node(node)) => { | ||
130 | if let Some((doctest, range_mapping, new_comments)) = | ||
131 | injection::extract_doc_comments(node) | ||
132 | { | ||
133 | injection::highlight_doc_comment( | ||
134 | doctest, | ||
135 | range_mapping, | ||
136 | new_comments, | ||
137 | &mut stack, | ||
138 | ); | ||
139 | } | ||
140 | } | ||
141 | _ => (), | ||
142 | } | ||
143 | |||
126 | let element = match event { | 144 | let element = match event { |
127 | WalkEvent::Enter(it) => it, | 145 | WalkEvent::Enter(it) => it, |
128 | WalkEvent::Leave(_) => continue, | 146 | WalkEvent::Leave(_) => continue, |
@@ -173,7 +191,7 @@ pub(crate) fn highlight( | |||
173 | 191 | ||
174 | if let Some(token) = element.as_token().cloned().and_then(ast::RawString::cast) { | 192 | if let Some(token) = element.as_token().cloned().and_then(ast::RawString::cast) { |
175 | let expanded = element_to_highlight.as_token().unwrap().clone(); | 193 | let expanded = element_to_highlight.as_token().unwrap().clone(); |
176 | if highlight_injection(&mut stack, &sema, token, expanded).is_some() { | 194 | if injection::highlight_injection(&mut stack, &sema, token, expanded).is_some() { |
177 | continue; | 195 | continue; |
178 | } | 196 | } |
179 | } | 197 | } |
@@ -259,9 +277,8 @@ impl HighlightedRangeStack { | |||
259 | let mut parent = prev.pop().unwrap(); | 277 | let mut parent = prev.pop().unwrap(); |
260 | for ele in children { | 278 | for ele in children { |
261 | assert!(parent.range.contains_range(ele.range)); | 279 | assert!(parent.range.contains_range(ele.range)); |
262 | let mut cloned = parent.clone(); | 280 | |
263 | parent.range = TextRange::new(parent.range.start(), ele.range.start()); | 281 | let cloned = Self::intersect(&mut parent, &ele); |
264 | cloned.range = TextRange::new(ele.range.end(), cloned.range.end()); | ||
265 | if !parent.range.is_empty() { | 282 | if !parent.range.is_empty() { |
266 | prev.push(parent); | 283 | prev.push(parent); |
267 | } | 284 | } |
@@ -274,6 +291,62 @@ impl HighlightedRangeStack { | |||
274 | } | 291 | } |
275 | } | 292 | } |
276 | 293 | ||
294 | /// Intersects the `HighlightedRange` `parent` with `child`. | ||
295 | /// `parent` is mutated in place, becoming the range before `child`. | ||
296 | /// Returns the range (of the same type as `parent`) *after* `child`. | ||
297 | fn intersect(parent: &mut HighlightedRange, child: &HighlightedRange) -> HighlightedRange { | ||
298 | assert!(parent.range.contains_range(child.range)); | ||
299 | |||
300 | let mut cloned = parent.clone(); | ||
301 | parent.range = TextRange::new(parent.range.start(), child.range.start()); | ||
302 | cloned.range = TextRange::new(child.range.end(), cloned.range.end()); | ||
303 | |||
304 | cloned | ||
305 | } | ||
306 | |||
307 | /// Similar to `pop`, but can modify arbitrary prior ranges (where `pop`) | ||
308 | /// can only modify the last range currently on the stack. | ||
309 | /// Can be used to do injections that span multiple ranges, like the | ||
310 | /// doctest injection below. | ||
311 | /// If `delete` is set to true, the parent range is deleted instead of | ||
312 | /// intersected. | ||
313 | /// | ||
314 | /// Note that `pop` can be simulated by `pop_and_inject(false)` but the | ||
315 | /// latter is computationally more expensive. | ||
316 | fn pop_and_inject(&mut self, delete: bool) { | ||
317 | let mut children = self.stack.pop().unwrap(); | ||
318 | let prev = self.stack.last_mut().unwrap(); | ||
319 | children.sort_by_key(|range| range.range.start()); | ||
320 | prev.sort_by_key(|range| range.range.start()); | ||
321 | |||
322 | for child in children { | ||
323 | if let Some(idx) = | ||
324 | prev.iter().position(|parent| parent.range.contains_range(child.range)) | ||
325 | { | ||
326 | let cloned = Self::intersect(&mut prev[idx], &child); | ||
327 | let insert_idx = if delete || prev[idx].range.is_empty() { | ||
328 | prev.remove(idx); | ||
329 | idx | ||
330 | } else { | ||
331 | idx + 1 | ||
332 | }; | ||
333 | prev.insert(insert_idx, child); | ||
334 | if !delete && !cloned.range.is_empty() { | ||
335 | prev.insert(insert_idx + 1, cloned); | ||
336 | } | ||
337 | } else if let Some(_idx) = | ||
338 | prev.iter().position(|parent| parent.range.contains(child.range.start())) | ||
339 | { | ||
340 | unreachable!("child range should be completely contained in parent range"); | ||
341 | } else { | ||
342 | let idx = prev | ||
343 | .binary_search_by_key(&child.range.start(), |range| range.range.start()) | ||
344 | .unwrap_or_else(|x| x); | ||
345 | prev.insert(idx, child); | ||
346 | } | ||
347 | } | ||
348 | } | ||
349 | |||
277 | fn add(&mut self, range: HighlightedRange) { | 350 | fn add(&mut self, range: HighlightedRange) { |
278 | self.stack | 351 | self.stack |
279 | .last_mut() | 352 | .last_mut() |
@@ -540,42 +613,3 @@ fn highlight_name_by_syntax(name: ast::Name) -> Highlight { | |||
540 | 613 | ||
541 | tag.into() | 614 | tag.into() |
542 | } | 615 | } |
543 | |||
544 | fn highlight_injection( | ||
545 | acc: &mut HighlightedRangeStack, | ||
546 | sema: &Semantics<RootDatabase>, | ||
547 | literal: ast::RawString, | ||
548 | expanded: SyntaxToken, | ||
549 | ) -> Option<()> { | ||
550 | let active_parameter = ActiveParameter::at_token(&sema, expanded)?; | ||
551 | if !active_parameter.name.starts_with("ra_fixture") { | ||
552 | return None; | ||
553 | } | ||
554 | let value = literal.value()?; | ||
555 | let (analysis, tmp_file_id) = Analysis::from_single_file(value); | ||
556 | |||
557 | if let Some(range) = literal.open_quote_text_range() { | ||
558 | acc.add(HighlightedRange { | ||
559 | range, | ||
560 | highlight: HighlightTag::StringLiteral.into(), | ||
561 | binding_hash: None, | ||
562 | }) | ||
563 | } | ||
564 | |||
565 | for mut h in analysis.highlight(tmp_file_id).unwrap() { | ||
566 | if let Some(r) = literal.map_range_up(h.range) { | ||
567 | h.range = r; | ||
568 | acc.add(h) | ||
569 | } | ||
570 | } | ||
571 | |||
572 | if let Some(range) = literal.close_quote_text_range() { | ||
573 | acc.add(HighlightedRange { | ||
574 | range, | ||
575 | highlight: HighlightTag::StringLiteral.into(), | ||
576 | binding_hash: None, | ||
577 | }) | ||
578 | } | ||
579 | |||
580 | Some(()) | ||
581 | } | ||
diff --git a/crates/ra_ide/src/syntax_highlighting/injection.rs b/crates/ra_ide/src/syntax_highlighting/injection.rs new file mode 100644 index 000000000..3575a0fc6 --- /dev/null +++ b/crates/ra_ide/src/syntax_highlighting/injection.rs | |||
@@ -0,0 +1,168 @@ | |||
1 | //! Syntax highlighting injections such as highlighting of documentation tests. | ||
2 | |||
3 | use std::{collections::BTreeMap, convert::TryFrom}; | ||
4 | |||
5 | use ast::{HasQuotes, HasStringValue}; | ||
6 | use hir::Semantics; | ||
7 | use ra_syntax::{ast, AstToken, SyntaxNode, SyntaxToken, TextRange, TextSize}; | ||
8 | use stdx::SepBy; | ||
9 | |||
10 | use crate::{call_info::ActiveParameter, Analysis, HighlightTag, HighlightedRange, RootDatabase}; | ||
11 | |||
12 | use super::HighlightedRangeStack; | ||
13 | |||
14 | pub(super) fn highlight_injection( | ||
15 | acc: &mut HighlightedRangeStack, | ||
16 | sema: &Semantics<RootDatabase>, | ||
17 | literal: ast::RawString, | ||
18 | expanded: SyntaxToken, | ||
19 | ) -> Option<()> { | ||
20 | let active_parameter = ActiveParameter::at_token(&sema, expanded)?; | ||
21 | if !active_parameter.name.starts_with("ra_fixture") { | ||
22 | return None; | ||
23 | } | ||
24 | let value = literal.value()?; | ||
25 | let (analysis, tmp_file_id) = Analysis::from_single_file(value); | ||
26 | |||
27 | if let Some(range) = literal.open_quote_text_range() { | ||
28 | acc.add(HighlightedRange { | ||
29 | range, | ||
30 | highlight: HighlightTag::StringLiteral.into(), | ||
31 | binding_hash: None, | ||
32 | }) | ||
33 | } | ||
34 | |||
35 | for mut h in analysis.highlight(tmp_file_id).unwrap() { | ||
36 | if let Some(r) = literal.map_range_up(h.range) { | ||
37 | h.range = r; | ||
38 | acc.add(h) | ||
39 | } | ||
40 | } | ||
41 | |||
42 | if let Some(range) = literal.close_quote_text_range() { | ||
43 | acc.add(HighlightedRange { | ||
44 | range, | ||
45 | highlight: HighlightTag::StringLiteral.into(), | ||
46 | binding_hash: None, | ||
47 | }) | ||
48 | } | ||
49 | |||
50 | Some(()) | ||
51 | } | ||
52 | |||
53 | /// Mapping from extracted documentation code to original code | ||
54 | type RangesMap = BTreeMap<TextSize, TextSize>; | ||
55 | |||
56 | /// Extracts Rust code from documentation comments as well as a mapping from | ||
57 | /// the extracted source code back to the original source ranges. | ||
58 | /// Lastly, a vector of new comment highlight ranges (spanning only the | ||
59 | /// comment prefix) is returned which is used in the syntax highlighting | ||
60 | /// injection to replace the previous (line-spanning) comment ranges. | ||
61 | pub(super) fn extract_doc_comments( | ||
62 | node: &SyntaxNode, | ||
63 | ) -> Option<(String, RangesMap, Vec<HighlightedRange>)> { | ||
64 | // wrap the doctest into function body to get correct syntax highlighting | ||
65 | let prefix = "fn doctest() {\n"; | ||
66 | let suffix = "}\n"; | ||
67 | // Mapping from extracted documentation code to original code | ||
68 | let mut range_mapping: RangesMap = BTreeMap::new(); | ||
69 | let mut line_start = TextSize::try_from(prefix.len()).unwrap(); | ||
70 | let mut is_doctest = false; | ||
71 | // Replace the original, line-spanning comment ranges by new, only comment-prefix | ||
72 | // spanning comment ranges. | ||
73 | let mut new_comments = Vec::new(); | ||
74 | let doctest = node | ||
75 | .children_with_tokens() | ||
76 | .filter_map(|el| el.into_token().and_then(ast::Comment::cast)) | ||
77 | .filter(|comment| comment.kind().doc.is_some()) | ||
78 | .filter(|comment| { | ||
79 | if comment.text().contains("```") { | ||
80 | is_doctest = !is_doctest; | ||
81 | false | ||
82 | } else { | ||
83 | is_doctest | ||
84 | } | ||
85 | }) | ||
86 | .map(|comment| { | ||
87 | let prefix_len = comment.prefix().len(); | ||
88 | let line: &str = comment.text().as_str(); | ||
89 | let range = comment.syntax().text_range(); | ||
90 | |||
91 | // whitespace after comment is ignored | ||
92 | let pos = if let Some(ws) = line.chars().nth(prefix_len).filter(|c| c.is_whitespace()) { | ||
93 | prefix_len + ws.len_utf8() | ||
94 | } else { | ||
95 | prefix_len | ||
96 | }; | ||
97 | |||
98 | // lines marked with `#` should be ignored in output, we skip the `#` char | ||
99 | let pos = if let Some(ws) = line.chars().nth(pos).filter(|&c| c == '#') { | ||
100 | pos + ws.len_utf8() | ||
101 | } else { | ||
102 | pos | ||
103 | }; | ||
104 | |||
105 | range_mapping.insert(line_start, range.start() + TextSize::try_from(pos).unwrap()); | ||
106 | new_comments.push(HighlightedRange { | ||
107 | range: TextRange::new( | ||
108 | range.start(), | ||
109 | range.start() + TextSize::try_from(pos).unwrap(), | ||
110 | ), | ||
111 | highlight: HighlightTag::Comment.into(), | ||
112 | binding_hash: None, | ||
113 | }); | ||
114 | line_start += range.len() - TextSize::try_from(pos).unwrap(); | ||
115 | line_start += TextSize::try_from('\n'.len_utf8()).unwrap(); | ||
116 | |||
117 | line[pos..].to_owned() | ||
118 | }) | ||
119 | .sep_by("\n") | ||
120 | .to_string(); | ||
121 | |||
122 | if doctest.is_empty() { | ||
123 | return None; | ||
124 | } | ||
125 | |||
126 | let doctest = format!("{}{}{}", prefix, doctest, suffix); | ||
127 | Some((doctest, range_mapping, new_comments)) | ||
128 | } | ||
129 | |||
130 | /// Injection of syntax highlighting of doctests. | ||
131 | pub(super) fn highlight_doc_comment( | ||
132 | text: String, | ||
133 | range_mapping: RangesMap, | ||
134 | new_comments: Vec<HighlightedRange>, | ||
135 | stack: &mut HighlightedRangeStack, | ||
136 | ) { | ||
137 | let (analysis, tmp_file_id) = Analysis::from_single_file(text); | ||
138 | |||
139 | stack.push(); | ||
140 | for mut h in analysis.highlight(tmp_file_id).unwrap() { | ||
141 | // Determine start offset and end offset in case of multi-line ranges | ||
142 | let mut start_offset = None; | ||
143 | let mut end_offset = None; | ||
144 | for (line_start, orig_line_start) in range_mapping.range(..h.range.end()).rev() { | ||
145 | if line_start <= &h.range.start() { | ||
146 | start_offset.get_or_insert(orig_line_start - line_start); | ||
147 | break; | ||
148 | } else { | ||
149 | end_offset.get_or_insert(orig_line_start - line_start); | ||
150 | } | ||
151 | } | ||
152 | if let Some(start_offset) = start_offset { | ||
153 | h.range = TextRange::new( | ||
154 | h.range.start() + start_offset, | ||
155 | h.range.end() + end_offset.unwrap_or(start_offset), | ||
156 | ); | ||
157 | stack.add(h); | ||
158 | } | ||
159 | } | ||
160 | |||
161 | // Inject the comment prefix highlight ranges | ||
162 | stack.push(); | ||
163 | for comment in new_comments { | ||
164 | stack.add(comment); | ||
165 | } | ||
166 | stack.pop_and_inject(false); | ||
167 | stack.pop_and_inject(true); | ||
168 | } | ||
diff --git a/crates/ra_ide/src/syntax_highlighting/tests.rs b/crates/ra_ide/src/syntax_highlighting/tests.rs index 36a1aa419..ba345d90a 100644 --- a/crates/ra_ide/src/syntax_highlighting/tests.rs +++ b/crates/ra_ide/src/syntax_highlighting/tests.rs | |||
@@ -7,9 +7,21 @@ use crate::{ | |||
7 | FileRange, TextRange, | 7 | FileRange, TextRange, |
8 | }; | 8 | }; |
9 | 9 | ||
10 | /// Highlights the code given by the `ra_fixture` argument, renders the | ||
11 | /// result as HTML, and compares it with the HTML file given as `snapshot`. | ||
12 | /// Note that the `snapshot` file is overwritten by the rendered HTML. | ||
13 | fn check_highlighting(ra_fixture: &str, snapshot: &str, rainbow: bool) { | ||
14 | let (analysis, file_id) = single_file(ra_fixture); | ||
15 | let dst_file = project_dir().join(snapshot); | ||
16 | let actual_html = &analysis.highlight_as_html(file_id, rainbow).unwrap(); | ||
17 | let expected_html = &read_text(&dst_file); | ||
18 | fs::write(dst_file, &actual_html).unwrap(); | ||
19 | assert_eq_text!(expected_html, actual_html); | ||
20 | } | ||
21 | |||
10 | #[test] | 22 | #[test] |
11 | fn test_highlighting() { | 23 | fn test_highlighting() { |
12 | let (analysis, file_id) = single_file( | 24 | check_highlighting( |
13 | r#" | 25 | r#" |
14 | #[derive(Clone, Debug)] | 26 | #[derive(Clone, Debug)] |
15 | struct Foo { | 27 | struct Foo { |
@@ -84,17 +96,14 @@ impl<T> Option<T> { | |||
84 | } | 96 | } |
85 | "# | 97 | "# |
86 | .trim(), | 98 | .trim(), |
99 | "crates/ra_ide/src/snapshots/highlighting.html", | ||
100 | false, | ||
87 | ); | 101 | ); |
88 | let dst_file = project_dir().join("crates/ra_ide/src/snapshots/highlighting.html"); | ||
89 | let actual_html = &analysis.highlight_as_html(file_id, false).unwrap(); | ||
90 | let expected_html = &read_text(&dst_file); | ||
91 | fs::write(dst_file, &actual_html).unwrap(); | ||
92 | assert_eq_text!(expected_html, actual_html); | ||
93 | } | 102 | } |
94 | 103 | ||
95 | #[test] | 104 | #[test] |
96 | fn test_rainbow_highlighting() { | 105 | fn test_rainbow_highlighting() { |
97 | let (analysis, file_id) = single_file( | 106 | check_highlighting( |
98 | r#" | 107 | r#" |
99 | fn main() { | 108 | fn main() { |
100 | let hello = "hello"; | 109 | let hello = "hello"; |
@@ -110,12 +119,9 @@ fn bar() { | |||
110 | } | 119 | } |
111 | "# | 120 | "# |
112 | .trim(), | 121 | .trim(), |
122 | "crates/ra_ide/src/snapshots/rainbow_highlighting.html", | ||
123 | true, | ||
113 | ); | 124 | ); |
114 | let dst_file = project_dir().join("crates/ra_ide/src/snapshots/rainbow_highlighting.html"); | ||
115 | let actual_html = &analysis.highlight_as_html(file_id, true).unwrap(); | ||
116 | let expected_html = &read_text(&dst_file); | ||
117 | fs::write(dst_file, &actual_html).unwrap(); | ||
118 | assert_eq_text!(expected_html, actual_html); | ||
119 | } | 125 | } |
120 | 126 | ||
121 | #[test] | 127 | #[test] |
@@ -153,7 +159,7 @@ fn test_ranges() { | |||
153 | 159 | ||
154 | #[test] | 160 | #[test] |
155 | fn test_flattening() { | 161 | fn test_flattening() { |
156 | let (analysis, file_id) = single_file( | 162 | check_highlighting( |
157 | r##" | 163 | r##" |
158 | fn fixture(ra_fixture: &str) {} | 164 | fn fixture(ra_fixture: &str) {} |
159 | 165 | ||
@@ -167,13 +173,9 @@ fn main() { | |||
167 | ); | 173 | ); |
168 | }"## | 174 | }"## |
169 | .trim(), | 175 | .trim(), |
176 | "crates/ra_ide/src/snapshots/highlight_injection.html", | ||
177 | false, | ||
170 | ); | 178 | ); |
171 | |||
172 | let dst_file = project_dir().join("crates/ra_ide/src/snapshots/highlight_injection.html"); | ||
173 | let actual_html = &analysis.highlight_as_html(file_id, false).unwrap(); | ||
174 | let expected_html = &read_text(&dst_file); | ||
175 | fs::write(dst_file, &actual_html).unwrap(); | ||
176 | assert_eq_text!(expected_html, actual_html); | ||
177 | } | 179 | } |
178 | 180 | ||
179 | #[test] | 181 | #[test] |
@@ -192,7 +194,7 @@ macro_rules! test {} | |||
192 | fn test_string_highlighting() { | 194 | fn test_string_highlighting() { |
193 | // The format string detection is based on macro-expansion, | 195 | // The format string detection is based on macro-expansion, |
194 | // thus, we have to copy the macro definition from `std` | 196 | // thus, we have to copy the macro definition from `std` |
195 | let (analysis, file_id) = single_file( | 197 | check_highlighting( |
196 | r#" | 198 | r#" |
197 | macro_rules! println { | 199 | macro_rules! println { |
198 | ($($arg:tt)*) => ({ | 200 | ($($arg:tt)*) => ({ |
@@ -250,18 +252,14 @@ fn main() { | |||
250 | println!("{ничоси}", ничоси = 92); | 252 | println!("{ничоси}", ничоси = 92); |
251 | }"# | 253 | }"# |
252 | .trim(), | 254 | .trim(), |
255 | "crates/ra_ide/src/snapshots/highlight_strings.html", | ||
256 | false, | ||
253 | ); | 257 | ); |
254 | |||
255 | let dst_file = project_dir().join("crates/ra_ide/src/snapshots/highlight_strings.html"); | ||
256 | let actual_html = &analysis.highlight_as_html(file_id, false).unwrap(); | ||
257 | let expected_html = &read_text(&dst_file); | ||
258 | fs::write(dst_file, &actual_html).unwrap(); | ||
259 | assert_eq_text!(expected_html, actual_html); | ||
260 | } | 258 | } |
261 | 259 | ||
262 | #[test] | 260 | #[test] |
263 | fn test_unsafe_highlighting() { | 261 | fn test_unsafe_highlighting() { |
264 | let (analysis, file_id) = single_file( | 262 | check_highlighting( |
265 | r#" | 263 | r#" |
266 | unsafe fn unsafe_fn() {} | 264 | unsafe fn unsafe_fn() {} |
267 | 265 | ||
@@ -282,10 +280,57 @@ fn main() { | |||
282 | } | 280 | } |
283 | "# | 281 | "# |
284 | .trim(), | 282 | .trim(), |
283 | "crates/ra_ide/src/snapshots/highlight_unsafe.html", | ||
284 | false, | ||
285 | ); | 285 | ); |
286 | let dst_file = project_dir().join("crates/ra_ide/src/snapshots/highlight_unsafe.html"); | 286 | } |
287 | let actual_html = &analysis.highlight_as_html(file_id, false).unwrap(); | 287 | |
288 | let expected_html = &read_text(&dst_file); | 288 | #[test] |
289 | fs::write(dst_file, &actual_html).unwrap(); | 289 | fn test_highlight_doctest() { |
290 | assert_eq_text!(expected_html, actual_html); | 290 | check_highlighting( |
291 | r#" | ||
292 | impl Foo { | ||
293 | /// Constructs a new `Foo`. | ||
294 | /// | ||
295 | /// # Examples | ||
296 | /// | ||
297 | /// ``` | ||
298 | /// # #![allow(unused_mut)] | ||
299 | /// let mut foo: Foo = Foo::new(); | ||
300 | /// ``` | ||
301 | pub const fn new() -> Foo { | ||
302 | Foo { } | ||
303 | } | ||
304 | |||
305 | /// `bar` method on `Foo`. | ||
306 | /// | ||
307 | /// # Examples | ||
308 | /// | ||
309 | /// ``` | ||
310 | /// let foo = Foo::new(); | ||
311 | /// | ||
312 | /// // calls bar on foo | ||
313 | /// assert!(foo.bar()); | ||
314 | /// | ||
315 | /// /* multi-line | ||
316 | /// comment */ | ||
317 | /// | ||
318 | /// let multi_line_string = "Foo | ||
319 | /// bar | ||
320 | /// "; | ||
321 | /// | ||
322 | /// ``` | ||
323 | /// | ||
324 | /// ``` | ||
325 | /// let foobar = Foo::new().bar(); | ||
326 | /// ``` | ||
327 | pub fn foo(&self) -> bool { | ||
328 | true | ||
329 | } | ||
330 | } | ||
331 | "# | ||
332 | .trim(), | ||
333 | "crates/ra_ide/src/snapshots/highlight_doctest.html", | ||
334 | false, | ||
335 | ) | ||
291 | } | 336 | } |
diff --git a/crates/ra_syntax/src/ast/tokens.rs b/crates/ra_syntax/src/ast/tokens.rs index 04b0a4480..56378385a 100644 --- a/crates/ra_syntax/src/ast/tokens.rs +++ b/crates/ra_syntax/src/ast/tokens.rs | |||
@@ -335,16 +335,26 @@ pub trait HasFormatSpecifier: AstToken { | |||
335 | } | 335 | } |
336 | c if c == '_' || c.is_alphabetic() => { | 336 | c if c == '_' || c.is_alphabetic() => { |
337 | read_identifier(&mut chars, &mut callback); | 337 | read_identifier(&mut chars, &mut callback); |
338 | if chars.peek().and_then(|next| next.1.as_ref().ok()).copied() | 338 | // can be either width (indicated by dollar sign, or type in which case |
339 | != Some('$') | 339 | // the next sign has to be `}`) |
340 | { | 340 | let next = |
341 | continue; | 341 | chars.peek().and_then(|next| next.1.as_ref().ok()).copied(); |
342 | } | 342 | match next { |
343 | skip_char_and_emit( | 343 | Some('$') => skip_char_and_emit( |
344 | &mut chars, | 344 | &mut chars, |
345 | FormatSpecifier::DollarSign, | 345 | FormatSpecifier::DollarSign, |
346 | &mut callback, | 346 | &mut callback, |
347 | ); | 347 | ), |
348 | Some('}') => { | ||
349 | skip_char_and_emit( | ||
350 | &mut chars, | ||
351 | FormatSpecifier::Close, | ||
352 | &mut callback, | ||
353 | ); | ||
354 | continue; | ||
355 | } | ||
356 | _ => continue, | ||
357 | }; | ||
348 | } | 358 | } |
349 | _ => {} | 359 | _ => {} |
350 | } | 360 | } |
@@ -416,12 +426,11 @@ pub trait HasFormatSpecifier: AstToken { | |||
416 | } | 426 | } |
417 | } | 427 | } |
418 | 428 | ||
419 | let mut cloned = chars.clone().take(2); | 429 | if let Some((_, Ok('}'))) = chars.peek() { |
420 | let first = cloned.next().and_then(|next| next.1.as_ref().ok()).copied(); | 430 | skip_char_and_emit(&mut chars, FormatSpecifier::Close, &mut callback); |
421 | if first != Some('}') { | 431 | } else { |
422 | continue; | 432 | continue; |
423 | } | 433 | } |
424 | skip_char_and_emit(&mut chars, FormatSpecifier::Close, &mut callback); | ||
425 | } | 434 | } |
426 | _ => { | 435 | _ => { |
427 | while let Some((_, Ok(next_char))) = chars.peek() { | 436 | while let Some((_, Ok(next_char))) = chars.peek() { |
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 8d6efdbe8..17671f89e 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs | |||
@@ -285,6 +285,8 @@ impl Config { | |||
285 | set(value, "/hoverActions/enable", &mut use_hover_actions); | 285 | set(value, "/hoverActions/enable", &mut use_hover_actions); |
286 | if use_hover_actions { | 286 | if use_hover_actions { |
287 | set(value, "/hoverActions/implementations", &mut self.hover.implementations); | 287 | set(value, "/hoverActions/implementations", &mut self.hover.implementations); |
288 | set(value, "/hoverActions/run", &mut self.hover.run); | ||
289 | set(value, "/hoverActions/debug", &mut self.hover.debug); | ||
288 | } else { | 290 | } else { |
289 | self.hover = HoverConfig::NO_ACTIONS; | 291 | self.hover = HoverConfig::NO_ACTIONS; |
290 | } | 292 | } |
diff --git a/crates/rust-analyzer/src/main_loop/handlers.rs b/crates/rust-analyzer/src/main_loop/handlers.rs index 3ff779702..a41adf8b0 100644 --- a/crates/rust-analyzer/src/main_loop/handlers.rs +++ b/crates/rust-analyzer/src/main_loop/handlers.rs | |||
@@ -18,8 +18,8 @@ use lsp_types::{ | |||
18 | TextDocumentIdentifier, Url, WorkspaceEdit, | 18 | TextDocumentIdentifier, Url, WorkspaceEdit, |
19 | }; | 19 | }; |
20 | use ra_ide::{ | 20 | use ra_ide::{ |
21 | FileId, FilePosition, FileRange, HoverAction, Query, RangeInfo, RunnableKind, SearchScope, | 21 | FileId, FilePosition, FileRange, HoverAction, Query, RangeInfo, Runnable, RunnableKind, |
22 | TextEdit, | 22 | SearchScope, TextEdit, |
23 | }; | 23 | }; |
24 | use ra_prof::profile; | 24 | use ra_prof::profile; |
25 | use ra_project_model::TargetKind; | 25 | use ra_project_model::TargetKind; |
@@ -404,15 +404,10 @@ pub fn handle_runnables( | |||
404 | continue; | 404 | continue; |
405 | } | 405 | } |
406 | } | 406 | } |
407 | // Do not suggest binary run on other target than binary | 407 | if should_skip_target(&runnable, cargo_spec.as_ref()) { |
408 | if let RunnableKind::Bin = runnable.kind { | 408 | continue; |
409 | if let Some(spec) = &cargo_spec { | ||
410 | match spec.target_kind { | ||
411 | TargetKind::Bin => {} | ||
412 | _ => continue, | ||
413 | } | ||
414 | } | ||
415 | } | 409 | } |
410 | |||
416 | res.push(to_proto::runnable(&snap, file_id, runnable)?); | 411 | res.push(to_proto::runnable(&snap, file_id, runnable)?); |
417 | } | 412 | } |
418 | 413 | ||
@@ -555,7 +550,7 @@ pub fn handle_hover( | |||
555 | }), | 550 | }), |
556 | range: Some(range), | 551 | range: Some(range), |
557 | }, | 552 | }, |
558 | actions: prepare_hover_actions(&snap, info.info.actions()), | 553 | actions: prepare_hover_actions(&snap, position.file_id, info.info.actions()), |
559 | }; | 554 | }; |
560 | 555 | ||
561 | Ok(Some(hover)) | 556 | Ok(Some(hover)) |
@@ -817,55 +812,25 @@ pub fn handle_code_lens( | |||
817 | if snap.config.lens.runnable() { | 812 | if snap.config.lens.runnable() { |
818 | // Gather runnables | 813 | // Gather runnables |
819 | for runnable in snap.analysis().runnables(file_id)? { | 814 | for runnable in snap.analysis().runnables(file_id)? { |
820 | let (run_title, debugee) = match &runnable.kind { | 815 | if should_skip_target(&runnable, cargo_spec.as_ref()) { |
821 | RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => { | 816 | continue; |
822 | ("▶\u{fe0e} Run Test", true) | 817 | } |
823 | } | ||
824 | RunnableKind::DocTest { .. } => { | ||
825 | // cargo does not support -no-run for doctests | ||
826 | ("▶\u{fe0e} Run Doctest", false) | ||
827 | } | ||
828 | RunnableKind::Bench { .. } => { | ||
829 | // Nothing wrong with bench debugging | ||
830 | ("Run Bench", true) | ||
831 | } | ||
832 | RunnableKind::Bin => { | ||
833 | // Do not suggest binary run on other target than binary | ||
834 | match &cargo_spec { | ||
835 | Some(spec) => match spec.target_kind { | ||
836 | TargetKind::Bin => ("Run", true), | ||
837 | _ => continue, | ||
838 | }, | ||
839 | None => continue, | ||
840 | } | ||
841 | } | ||
842 | }; | ||
843 | 818 | ||
819 | let action = runnable.action(); | ||
844 | let range = to_proto::range(&line_index, runnable.nav.range()); | 820 | let range = to_proto::range(&line_index, runnable.nav.range()); |
845 | let r = to_proto::runnable(&snap, file_id, runnable)?; | 821 | let r = to_proto::runnable(&snap, file_id, runnable)?; |
846 | if snap.config.lens.run { | 822 | if snap.config.lens.run { |
847 | let lens = CodeLens { | 823 | let lens = CodeLens { |
848 | range, | 824 | range, |
849 | command: Some(Command { | 825 | command: Some(run_single_command(&r, action.run_title)), |
850 | title: run_title.to_string(), | ||
851 | command: "rust-analyzer.runSingle".into(), | ||
852 | arguments: Some(vec![to_value(&r).unwrap()]), | ||
853 | }), | ||
854 | data: None, | 826 | data: None, |
855 | }; | 827 | }; |
856 | lenses.push(lens); | 828 | lenses.push(lens); |
857 | } | 829 | } |
858 | 830 | ||
859 | if debugee && snap.config.lens.debug { | 831 | if action.debugee && snap.config.lens.debug { |
860 | let debug_lens = CodeLens { | 832 | let debug_lens = |
861 | range, | 833 | CodeLens { range, command: Some(debug_single_command(&r)), data: None }; |
862 | command: Some(Command { | ||
863 | title: "Debug".into(), | ||
864 | command: "rust-analyzer.debugSingle".into(), | ||
865 | arguments: Some(vec![to_value(r).unwrap()]), | ||
866 | }), | ||
867 | data: None, | ||
868 | }; | ||
869 | lenses.push(debug_lens); | 834 | lenses.push(debug_lens); |
870 | } | 835 | } |
871 | } | 836 | } |
@@ -1169,6 +1134,22 @@ fn show_references_command( | |||
1169 | } | 1134 | } |
1170 | } | 1135 | } |
1171 | 1136 | ||
1137 | fn run_single_command(runnable: &lsp_ext::Runnable, title: &str) -> Command { | ||
1138 | Command { | ||
1139 | title: title.to_string(), | ||
1140 | command: "rust-analyzer.runSingle".into(), | ||
1141 | arguments: Some(vec![to_value(runnable).unwrap()]), | ||
1142 | } | ||
1143 | } | ||
1144 | |||
1145 | fn debug_single_command(runnable: &lsp_ext::Runnable) -> Command { | ||
1146 | Command { | ||
1147 | title: "Debug".into(), | ||
1148 | command: "rust-analyzer.debugSingle".into(), | ||
1149 | arguments: Some(vec![to_value(runnable).unwrap()]), | ||
1150 | } | ||
1151 | } | ||
1152 | |||
1172 | fn to_command_link(command: Command, tooltip: String) -> lsp_ext::CommandLink { | 1153 | fn to_command_link(command: Command, tooltip: String) -> lsp_ext::CommandLink { |
1173 | lsp_ext::CommandLink { tooltip: Some(tooltip), command } | 1154 | lsp_ext::CommandLink { tooltip: Some(tooltip), command } |
1174 | } | 1155 | } |
@@ -1199,8 +1180,37 @@ fn show_impl_command_link( | |||
1199 | None | 1180 | None |
1200 | } | 1181 | } |
1201 | 1182 | ||
1183 | fn to_runnable_action( | ||
1184 | snap: &GlobalStateSnapshot, | ||
1185 | file_id: FileId, | ||
1186 | runnable: Runnable, | ||
1187 | ) -> Option<lsp_ext::CommandLinkGroup> { | ||
1188 | let cargo_spec = CargoTargetSpec::for_file(&snap, file_id).ok()?; | ||
1189 | if should_skip_target(&runnable, cargo_spec.as_ref()) { | ||
1190 | return None; | ||
1191 | } | ||
1192 | |||
1193 | let action: &'static _ = runnable.action(); | ||
1194 | to_proto::runnable(snap, file_id, runnable).ok().map(|r| { | ||
1195 | let mut group = lsp_ext::CommandLinkGroup::default(); | ||
1196 | |||
1197 | if snap.config.hover.run { | ||
1198 | let run_command = run_single_command(&r, action.run_title); | ||
1199 | group.commands.push(to_command_link(run_command, r.label.clone())); | ||
1200 | } | ||
1201 | |||
1202 | if snap.config.hover.debug { | ||
1203 | let dbg_command = debug_single_command(&r); | ||
1204 | group.commands.push(to_command_link(dbg_command, r.label)); | ||
1205 | } | ||
1206 | |||
1207 | group | ||
1208 | }) | ||
1209 | } | ||
1210 | |||
1202 | fn prepare_hover_actions( | 1211 | fn prepare_hover_actions( |
1203 | snap: &GlobalStateSnapshot, | 1212 | snap: &GlobalStateSnapshot, |
1213 | file_id: FileId, | ||
1204 | actions: &[HoverAction], | 1214 | actions: &[HoverAction], |
1205 | ) -> Vec<lsp_ext::CommandLinkGroup> { | 1215 | ) -> Vec<lsp_ext::CommandLinkGroup> { |
1206 | if snap.config.hover.none() || !snap.config.client_caps.hover_actions { | 1216 | if snap.config.hover.none() || !snap.config.client_caps.hover_actions { |
@@ -1211,6 +1221,20 @@ fn prepare_hover_actions( | |||
1211 | .iter() | 1221 | .iter() |
1212 | .filter_map(|it| match it { | 1222 | .filter_map(|it| match it { |
1213 | HoverAction::Implementaion(position) => show_impl_command_link(snap, position), | 1223 | HoverAction::Implementaion(position) => show_impl_command_link(snap, position), |
1224 | HoverAction::Runnable(r) => to_runnable_action(snap, file_id, r.clone()), | ||
1214 | }) | 1225 | }) |
1215 | .collect() | 1226 | .collect() |
1216 | } | 1227 | } |
1228 | |||
1229 | fn should_skip_target(runnable: &Runnable, cargo_spec: Option<&CargoTargetSpec>) -> bool { | ||
1230 | match runnable.kind { | ||
1231 | RunnableKind::Bin => { | ||
1232 | // Do not suggest binary run on other target than binary | ||
1233 | match &cargo_spec { | ||
1234 | Some(spec) => spec.target_kind != TargetKind::Bin, | ||
1235 | None => true, | ||
1236 | } | ||
1237 | } | ||
1238 | _ => false, | ||
1239 | } | ||
1240 | } | ||
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index 1da4d80ec..710df1fbd 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs | |||
@@ -4,7 +4,7 @@ use ra_ide::{ | |||
4 | Assist, CompletionItem, CompletionItemKind, Documentation, FileSystemEdit, Fold, FoldKind, | 4 | Assist, CompletionItem, CompletionItemKind, Documentation, FileSystemEdit, Fold, FoldKind, |
5 | FunctionSignature, Highlight, HighlightModifier, HighlightTag, HighlightedRange, Indel, | 5 | FunctionSignature, Highlight, HighlightModifier, HighlightTag, HighlightedRange, Indel, |
6 | InlayHint, InlayKind, InsertTextFormat, LineIndex, NavigationTarget, ReferenceAccess, | 6 | InlayHint, InlayKind, InsertTextFormat, LineIndex, NavigationTarget, ReferenceAccess, |
7 | ResolvedAssist, Runnable, RunnableKind, Severity, SourceChange, SourceFileEdit, TextEdit, | 7 | ResolvedAssist, Runnable, Severity, SourceChange, SourceFileEdit, TextEdit, |
8 | }; | 8 | }; |
9 | use ra_syntax::{SyntaxKind, TextRange, TextSize}; | 9 | use ra_syntax::{SyntaxKind, TextRange, TextSize}; |
10 | use ra_vfs::LineEndings; | 10 | use ra_vfs::LineEndings; |
@@ -662,15 +662,7 @@ pub(crate) fn runnable( | |||
662 | let target = spec.as_ref().map(|s| s.target.clone()); | 662 | let target = spec.as_ref().map(|s| s.target.clone()); |
663 | let (cargo_args, executable_args) = | 663 | let (cargo_args, executable_args) = |
664 | CargoTargetSpec::runnable_args(spec, &runnable.kind, &runnable.cfg_exprs)?; | 664 | CargoTargetSpec::runnable_args(spec, &runnable.kind, &runnable.cfg_exprs)?; |
665 | let label = match &runnable.kind { | 665 | let label = runnable.label(target); |
666 | RunnableKind::Test { test_id, .. } => format!("test {}", test_id), | ||
667 | RunnableKind::TestMod { path } => format!("test-mod {}", path), | ||
668 | RunnableKind::Bench { test_id } => format!("bench {}", test_id), | ||
669 | RunnableKind::DocTest { test_id, .. } => format!("doctest {}", test_id), | ||
670 | RunnableKind::Bin => { | ||
671 | target.map_or_else(|| "run binary".to_string(), |t| format!("run {}", t)) | ||
672 | } | ||
673 | }; | ||
674 | let location = location_link(snap, None, runnable.nav)?; | 666 | let location = location_link(snap, None, runnable.nav)?; |
675 | 667 | ||
676 | Ok(lsp_ext::Runnable { | 668 | Ok(lsp_ext::Runnable { |
diff --git a/docs/dev/README.md b/docs/dev/README.md index 194a40e15..46ee030fc 100644 --- a/docs/dev/README.md +++ b/docs/dev/README.md | |||
@@ -184,6 +184,27 @@ use crate::{} | |||
184 | use super::{} // but prefer `use crate::` | 184 | use super::{} // but prefer `use crate::` |
185 | ``` | 185 | ``` |
186 | 186 | ||
187 | ## Import Style | ||
188 | |||
189 | Items from `hir` and `ast` should be used qualified: | ||
190 | |||
191 | ```rust | ||
192 | // Good | ||
193 | use ra_syntax::ast; | ||
194 | |||
195 | fn frobnicate(func: hir::Function, strukt: ast::StructDef) {} | ||
196 | |||
197 | // Not as good | ||
198 | use hir::Function; | ||
199 | use ra_syntax::ast::StructDef; | ||
200 | |||
201 | fn frobnicate(func: Function, strukt: StructDef) {} | ||
202 | ``` | ||
203 | |||
204 | Avoid local `use MyEnum::*` imports. | ||
205 | |||
206 | Prefer `use crate::foo::bar` to `use super::bar`. | ||
207 | |||
187 | ## Order of Items | 208 | ## Order of Items |
188 | 209 | ||
189 | Optimize for the reader who sees the file for the first time, and wants to get the general idea about what's going on. | 210 | Optimize for the reader who sees the file for the first time, and wants to get the general idea about what's going on. |
@@ -220,6 +241,68 @@ struct Foo { | |||
220 | For `.md` and `.adoc` files, prefer a sentence-per-line format, don't wrap lines. | 241 | For `.md` and `.adoc` files, prefer a sentence-per-line format, don't wrap lines. |
221 | If the line is too long, you want to split the sentence in two :-) | 242 | If the line is too long, you want to split the sentence in two :-) |
222 | 243 | ||
244 | ## Preconditions | ||
245 | |||
246 | Function preconditions should generally be expressed in types and provided by the caller (rather than checked by callee): | ||
247 | |||
248 | ```rust | ||
249 | // Good | ||
250 | fn frbonicate(walrus: Walrus) { | ||
251 | ... | ||
252 | } | ||
253 | |||
254 | // Not as good | ||
255 | fn frobnicate(walrus: Option<Walrus>) { | ||
256 | let walrus = match walrus { | ||
257 | Some(it) => it, | ||
258 | None => return, | ||
259 | }; | ||
260 | ... | ||
261 | } | ||
262 | ``` | ||
263 | |||
264 | ## Commit Style | ||
265 | |||
266 | We don't have specific rules around git history hygiene. | ||
267 | Maintaining clean git history is encouraged, but not enforced. | ||
268 | We use rebase workflow, it's OK to rewrite history during PR review process. | ||
269 | |||
270 | Avoid @mentioning people in commit messages, as such messages create a lot of duplicate notification traffic during rebases. | ||
271 | |||
272 | # Architecture Invariants | ||
273 | |||
274 | This section tries to document high-level design constraints, which are not | ||
275 | always obvious from the low-level code. | ||
276 | |||
277 | ## Incomplete syntax trees | ||
278 | |||
279 | Syntax trees are by design incomplete and do not enforce well-formedness. | ||
280 | If ast method returns an `Option`, it *can* be `None` at runtime, even if this is forbidden by the grammar. | ||
281 | |||
282 | ## LSP indenpendence | ||
283 | |||
284 | rust-analyzer is independent from LSP. | ||
285 | It provides features for a hypothetical perfect Rust-specific IDE client. | ||
286 | Internal representations are lowered to LSP in the `rust-analyzer` crate (the only crate which is allowed to use LSP types). | ||
287 | |||
288 | ## IDE/Compiler split | ||
289 | |||
290 | There's a semi-hard split between "compiler" and "IDE", at the `ra_hir` crate. | ||
291 | Compiler derives new facts about source code. | ||
292 | It explicitly acknowledges that not all info is available (ie, you can't look at types during name resolution). | ||
293 | |||
294 | IDE assumes that all information is available at all times. | ||
295 | |||
296 | IDE should use only types from `ra_hir`, and should not depend on the underling compiler types. | ||
297 | `ra_hir` is a facade. | ||
298 | |||
299 | ## IDE API | ||
300 | |||
301 | The main IDE crate (`ra_ide`) uses "Plain Old Data" for the API. | ||
302 | Rather than talking in definitions and references, it talks in Strings and textual offsets. | ||
303 | In general, API is centered around UI concerns -- the result of the call is what the user sees in the editor, and not what the compiler sees underneath. | ||
304 | The results are 100% Rust specific though. | ||
305 | |||
223 | # Logging | 306 | # Logging |
224 | 307 | ||
225 | Logging is done by both rust-analyzer and VS Code, so it might be tricky to | 308 | Logging is done by both rust-analyzer and VS Code, so it might be tricky to |
diff --git a/editors/code/package.json b/editors/code/package.json index b9c57db3b..779d7e1b8 100644 --- a/editors/code/package.json +++ b/editors/code/package.json | |||
@@ -486,11 +486,18 @@ | |||
486 | "type": "boolean", | 486 | "type": "boolean", |
487 | "default": true | 487 | "default": true |
488 | }, | 488 | }, |
489 | "rust-analyzer.hoverActions.run": { | ||
490 | "markdownDescription": "Whether to show `Run` action. Only applies when `#rust-analyzer.hoverActions.enable#` is set.", | ||
491 | "type": "boolean", | ||
492 | "default": true | ||
493 | }, | ||
494 | "rust-analyzer.hoverActions.debug": { | ||
495 | "markdownDescription": "Whether to show `Debug` action. Only applies when `#rust-analyzer.hoverActions.enable#` is set.", | ||
496 | "type": "boolean", | ||
497 | "default": true | ||
498 | }, | ||
489 | "rust-analyzer.linkedProjects": { | 499 | "rust-analyzer.linkedProjects": { |
490 | "markdownDescription": [ | 500 | "markdownDescription": "Disable project auto-discovery in favor of explicitly specified set of projects. \nElements must be paths pointing to Cargo.toml, rust-project.json, or JSON objects in rust-project.json format", |
491 | "Disable project auto-discovery in favor of explicitly specified set of projects.", | ||
492 | "Elements must be paths pointing to Cargo.toml, rust-project.json, or JSON objects in rust-project.json format" | ||
493 | ], | ||
494 | "type": "array", | 501 | "type": "array", |
495 | "items": { | 502 | "items": { |
496 | "type": [ | 503 | "type": [ |
diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index 739f49f7b..747654c1f 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs | |||
@@ -4,6 +4,7 @@ | |||
4 | 4 | ||
5 | pub mod not_bash; | 5 | pub mod not_bash; |
6 | pub mod install; | 6 | pub mod install; |
7 | pub mod release; | ||
7 | pub mod dist; | 8 | pub mod dist; |
8 | pub mod pre_commit; | 9 | pub mod pre_commit; |
9 | 10 | ||
@@ -19,7 +20,7 @@ use walkdir::{DirEntry, WalkDir}; | |||
19 | 20 | ||
20 | use crate::{ | 21 | use crate::{ |
21 | codegen::Mode, | 22 | codegen::Mode, |
22 | not_bash::{date_iso, fs2, pushd, pushenv, rm_rf, run}, | 23 | not_bash::{fs2, pushd, pushenv, rm_rf, run}, |
23 | }; | 24 | }; |
24 | 25 | ||
25 | pub use anyhow::{bail, Context as _, Result}; | 26 | pub use anyhow::{bail, Context as _, Result}; |
@@ -153,60 +154,6 @@ pub fn run_pre_cache() -> Result<()> { | |||
153 | Ok(()) | 154 | Ok(()) |
154 | } | 155 | } |
155 | 156 | ||
156 | pub fn run_release(dry_run: bool) -> Result<()> { | ||
157 | if !dry_run { | ||
158 | run!("git switch release")?; | ||
159 | run!("git fetch upstream --tags --force")?; | ||
160 | run!("git reset --hard tags/nightly")?; | ||
161 | run!("git push")?; | ||
162 | } | ||
163 | codegen::generate_assists_docs(Mode::Overwrite)?; | ||
164 | codegen::generate_feature_docs(Mode::Overwrite)?; | ||
165 | |||
166 | let website_root = project_root().join("../rust-analyzer.github.io"); | ||
167 | let changelog_dir = website_root.join("./thisweek/_posts"); | ||
168 | |||
169 | let today = date_iso()?; | ||
170 | let commit = run!("git rev-parse HEAD")?; | ||
171 | let changelog_n = fs2::read_dir(changelog_dir.as_path())?.count(); | ||
172 | |||
173 | let contents = format!( | ||
174 | "\ | ||
175 | = Changelog #{} | ||
176 | :sectanchors: | ||
177 | :page-layout: post | ||
178 | |||
179 | Commit: commit:{}[] + | ||
180 | Release: release:{}[] | ||
181 | |||
182 | == New Features | ||
183 | |||
184 | * pr:[] . | ||
185 | |||
186 | == Fixes | ||
187 | |||
188 | == Internal Improvements | ||
189 | ", | ||
190 | changelog_n, commit, today | ||
191 | ); | ||
192 | |||
193 | let path = changelog_dir.join(format!("{}-changelog-{}.adoc", today, changelog_n)); | ||
194 | fs2::write(&path, &contents)?; | ||
195 | |||
196 | for &adoc in ["manual.adoc", "generated_features.adoc", "generated_assists.adoc"].iter() { | ||
197 | let src = project_root().join("./docs/user/").join(adoc); | ||
198 | let dst = website_root.join(adoc); | ||
199 | fs2::copy(src, dst)?; | ||
200 | } | ||
201 | |||
202 | let tags = run!("git tag --list"; echo = false)?; | ||
203 | let prev_tag = tags.lines().filter(|line| is_release_tag(line)).last().unwrap(); | ||
204 | |||
205 | println!("\n git log {}..HEAD --merges --reverse", prev_tag); | ||
206 | |||
207 | Ok(()) | ||
208 | } | ||
209 | |||
210 | fn is_release_tag(tag: &str) -> bool { | 157 | fn is_release_tag(tag: &str) -> bool { |
211 | tag.len() == "2020-02-24".len() && tag.starts_with(|c: char| c.is_ascii_digit()) | 158 | tag.len() == "2020-02-24".len() && tag.starts_with(|c: char| c.is_ascii_digit()) |
212 | } | 159 | } |
diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 81bb3a33f..f7a79362d 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs | |||
@@ -16,8 +16,9 @@ use xtask::{ | |||
16 | dist::run_dist, | 16 | dist::run_dist, |
17 | install::{ClientOpt, InstallCmd, ServerOpt}, | 17 | install::{ClientOpt, InstallCmd, ServerOpt}, |
18 | not_bash::pushd, | 18 | not_bash::pushd, |
19 | pre_commit, project_root, run_clippy, run_fuzzer, run_pre_cache, run_release, run_rustfmt, | 19 | pre_commit, project_root, |
20 | Result, | 20 | release::ReleaseCmd, |
21 | run_clippy, run_fuzzer, run_pre_cache, run_rustfmt, Result, | ||
21 | }; | 22 | }; |
22 | 23 | ||
23 | fn main() -> Result<()> { | 24 | fn main() -> Result<()> { |
@@ -102,7 +103,7 @@ FLAGS: | |||
102 | "release" => { | 103 | "release" => { |
103 | let dry_run = args.contains("--dry-run"); | 104 | let dry_run = args.contains("--dry-run"); |
104 | args.finish()?; | 105 | args.finish()?; |
105 | run_release(dry_run) | 106 | ReleaseCmd { dry_run }.run() |
106 | } | 107 | } |
107 | "dist" => { | 108 | "dist" => { |
108 | let nightly = args.contains("--nightly"); | 109 | let nightly = args.contains("--nightly"); |
diff --git a/xtask/src/release.rs b/xtask/src/release.rs new file mode 100644 index 000000000..36c912184 --- /dev/null +++ b/xtask/src/release.rs | |||
@@ -0,0 +1,67 @@ | |||
1 | use crate::{ | ||
2 | codegen, is_release_tag, | ||
3 | not_bash::{date_iso, fs2, run}, | ||
4 | project_root, Mode, Result, | ||
5 | }; | ||
6 | |||
7 | pub struct ReleaseCmd { | ||
8 | pub dry_run: bool, | ||
9 | } | ||
10 | |||
11 | impl ReleaseCmd { | ||
12 | pub fn run(self) -> Result<()> { | ||
13 | if !self.dry_run { | ||
14 | run!("git switch release")?; | ||
15 | run!("git fetch upstream --tags --force")?; | ||
16 | run!("git reset --hard tags/nightly")?; | ||
17 | run!("git push")?; | ||
18 | } | ||
19 | codegen::generate_assists_docs(Mode::Overwrite)?; | ||
20 | codegen::generate_feature_docs(Mode::Overwrite)?; | ||
21 | |||
22 | let website_root = project_root().join("../rust-analyzer.github.io"); | ||
23 | let changelog_dir = website_root.join("./thisweek/_posts"); | ||
24 | |||
25 | let today = date_iso()?; | ||
26 | let commit = run!("git rev-parse HEAD")?; | ||
27 | let changelog_n = fs2::read_dir(changelog_dir.as_path())?.count(); | ||
28 | |||
29 | let contents = format!( | ||
30 | "\ | ||
31 | = Changelog #{} | ||
32 | :sectanchors: | ||
33 | :page-layout: post | ||
34 | |||
35 | Commit: commit:{}[] + | ||
36 | Release: release:{}[] | ||
37 | |||
38 | == New Features | ||
39 | |||
40 | * pr:[] . | ||
41 | |||
42 | == Fixes | ||
43 | |||
44 | == Internal Improvements | ||
45 | ", | ||
46 | changelog_n, commit, today | ||
47 | ); | ||
48 | |||
49 | let path = changelog_dir.join(format!("{}-changelog-{}.adoc", today, changelog_n)); | ||
50 | fs2::write(&path, &contents)?; | ||
51 | |||
52 | for &adoc in ["manual.adoc", "generated_features.adoc", "generated_assists.adoc"].iter() { | ||
53 | let src = project_root().join("./docs/user/").join(adoc); | ||
54 | let dst = website_root.join(adoc); | ||
55 | fs2::copy(src, dst)?; | ||
56 | } | ||
57 | |||
58 | let tags = run!("git tag --list"; echo = false)?; | ||
59 | let prev_tag = tags.lines().filter(|line| is_release_tag(line)).last().unwrap(); | ||
60 | |||
61 | let git_log = run!("git log {}..HEAD --merges --reverse", prev_tag; echo = false)?; | ||
62 | let git_log_dst = website_root.join("git.log"); | ||
63 | fs2::write(git_log_dst, &git_log)?; | ||
64 | |||
65 | Ok(()) | ||
66 | } | ||
67 | } | ||