aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock4
-rw-r--r--crates/ra_hir_ty/src/infer.rs13
-rw-r--r--crates/ra_hir_ty/src/infer/expr.rs28
-rw-r--r--crates/ra_hir_ty/src/lib.rs6
-rw-r--r--crates/ra_hir_ty/src/method_resolution.rs12
-rw-r--r--crates/ra_hir_ty/src/primitive.rs54
-rw-r--r--crates/ra_hir_ty/src/traits/chalk/mapping.rs24
-rw-r--r--crates/ra_ide/src/hover.rs142
-rw-r--r--crates/ra_ide/src/runnables.rs92
-rw-r--r--crates/ra_ide/src/snapshots/highlight_doctest.html70
-rw-r--r--crates/ra_ide/src/snapshots/highlight_strings.html2
-rw-r--r--crates/ra_ide/src/syntax_highlighting.rs126
-rw-r--r--crates/ra_ide/src/syntax_highlighting/injection.rs168
-rw-r--r--crates/ra_ide/src/syntax_highlighting/tests.rs109
-rw-r--r--crates/ra_syntax/src/ast/tokens.rs37
-rw-r--r--crates/rust-analyzer/src/config.rs2
-rw-r--r--crates/rust-analyzer/src/main_loop/handlers.rs122
-rw-r--r--crates/rust-analyzer/src/to_proto.rs12
-rw-r--r--docs/dev/README.md83
-rw-r--r--editors/code/package.json15
-rw-r--r--xtask/src/lib.rs57
-rw-r--r--xtask/src/main.rs7
-rw-r--r--xtask/src/release.rs67
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]]
886name = "quote" 886name = "quote"
887version = "1.0.6" 887version = "1.0.7"
888source = "registry+https://github.com/rust-lang/crates.io-index" 888source = "registry+https://github.com/rust-lang/crates.io-index"
889checksum = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea" 889checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
890dependencies = [ 890dependencies = [
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;
39use super::{ 39use 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};
45use crate::{ 44use 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
24use super::{ 24use 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
59use crate::{ 59use 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};
64use display::HirDisplay; 64use 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
17use super::Substs; 17use super::Substs;
18use crate::{ 18use 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
8pub use hir_def::builtin_type::{BuiltinFloat, BuiltinInt, FloatBitness, IntBitness, Signedness}; 8pub use hir_def::builtin_type::{BuiltinFloat, BuiltinInt, FloatBitness, IntBitness, Signedness};
9 9
10#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)]
11pub enum Uncertain<T> {
12 Unknown,
13 Known(T),
14}
15
16impl From<IntTy> for Uncertain<IntTy> {
17 fn from(ty: IntTy) -> Self {
18 Uncertain::Known(ty)
19 }
20}
21
22impl 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
31impl From<FloatTy> for Uncertain<FloatTy> {
32 fn from(ty: FloatTy) -> Self {
33 Uncertain::Known(ty)
34 }
35}
36
37impl 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)]
47pub struct IntTy { 11pub 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
177impl 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
186impl 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
15use crate::{ 15use 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
15use crate::{ 15use 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};
20use test_utils::mark;
19 21
20#[derive(Clone, Debug, PartialEq, Eq)] 22#[derive(Clone, Debug, PartialEq, Eq)]
21pub struct HoverConfig { 23pub struct HoverConfig {
22 pub implementations: bool, 24 pub implementations: bool,
25 pub run: bool,
26 pub debug: bool,
23} 27}
24 28
25impl Default for HoverConfig { 29impl 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
31impl HoverConfig { 35impl 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)]
44pub enum HoverAction { 52pub 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
191fn 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
178fn hover_text( 221fn 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)]
293mod tests { 336mod 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
12use crate::{display::ToNav, FileId, NavigationTarget}; 12use crate::{display::ToNav, FileId, NavigationTarget};
13 13
14#[derive(Debug)] 14#[derive(Debug, Clone)]
15pub struct Runnable { 15pub 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)]
22pub enum TestId { 22pub 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)]
37pub enum RunnableKind { 37pub 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)]
46pub struct RunnableAction {
47 pub run_title: &'static str,
48 pub debugee: bool,
49}
50
51const TEST: RunnableAction = RunnableAction { run_title: "▶\u{fe0e} Run Test", debugee: true };
52const DOCTEST: RunnableAction =
53 RunnableAction { run_title: "▶\u{fe0e} Run Doctest", debugee: false };
54const BENCH: RunnableAction = RunnableAction { run_title: "▶\u{fe0e} Run Bench", debugee: true };
55const BIN: RunnableAction = RunnableAction { run_title: "▶\u{fe0e} Run", debugee: true };
56
57impl 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
62fn runnable(sema: &Semantics<RootDatabase>, item: SyntaxNode, file_id: FileId) -> Option<Runnable> { 98pub(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)]
139pub struct TestAttr { 179pub 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>
3body { margin: 0; }
4pre { 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>() -&gt; <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>) -&gt; <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">&gt;</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">&gt;</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 @@
1mod tags; 1mod tags;
2mod html; 2mod html;
3mod injection;
3#[cfg(test)] 4#[cfg(test)]
4mod tests; 5mod tests;
5 6
@@ -10,14 +11,14 @@ use ra_ide_db::{
10}; 11};
11use ra_prof::profile; 12use ra_prof::profile;
12use ra_syntax::{ 13use 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};
18use rustc_hash::FxHashMap; 19use rustc_hash::FxHashMap;
19 20
20use crate::{call_info::ActiveParameter, Analysis, FileId}; 21use crate::FileId;
21 22
22use ast::FormatSpecifier; 23use ast::FormatSpecifier;
23pub(crate) use html::highlight_as_html; 24pub(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
544fn 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
3use std::{collections::BTreeMap, convert::TryFrom};
4
5use ast::{HasQuotes, HasStringValue};
6use hir::Semantics;
7use ra_syntax::{ast, AstToken, SyntaxNode, SyntaxToken, TextRange, TextSize};
8use stdx::SepBy;
9
10use crate::{call_info::ActiveParameter, Analysis, HighlightTag, HighlightedRange, RootDatabase};
11
12use super::HighlightedRangeStack;
13
14pub(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
54type 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.
61pub(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.
131pub(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.
13fn 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]
11fn test_highlighting() { 23fn test_highlighting() {
12 let (analysis, file_id) = single_file( 24 check_highlighting(
13 r#" 25 r#"
14#[derive(Clone, Debug)] 26#[derive(Clone, Debug)]
15struct Foo { 27struct 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]
96fn test_rainbow_highlighting() { 105fn test_rainbow_highlighting() {
97 let (analysis, file_id) = single_file( 106 check_highlighting(
98 r#" 107 r#"
99fn main() { 108fn 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]
155fn test_flattening() { 161fn test_flattening() {
156 let (analysis, file_id) = single_file( 162 check_highlighting(
157 r##" 163 r##"
158fn fixture(ra_fixture: &str) {} 164fn 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 {}
192fn test_string_highlighting() { 194fn 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#"
197macro_rules! println { 199macro_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]
263fn test_unsafe_highlighting() { 261fn test_unsafe_highlighting() {
264 let (analysis, file_id) = single_file( 262 check_highlighting(
265 r#" 263 r#"
266unsafe fn unsafe_fn() {} 264unsafe 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(); 289fn test_highlight_doctest() {
290 assert_eq_text!(expected_html, actual_html); 290 check_highlighting(
291 r#"
292impl 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};
20use ra_ide::{ 20use 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};
24use ra_prof::profile; 24use ra_prof::profile;
25use ra_project_model::TargetKind; 25use 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
1137fn 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
1145fn 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
1172fn to_command_link(command: Command, tooltip: String) -> lsp_ext::CommandLink { 1153fn 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
1183fn 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
1202fn prepare_hover_actions( 1211fn 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
1229fn 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};
9use ra_syntax::{SyntaxKind, TextRange, TextSize}; 9use ra_syntax::{SyntaxKind, TextRange, TextSize};
10use ra_vfs::LineEndings; 10use 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::{}
184use super::{} // but prefer `use crate::` 184use super::{} // but prefer `use crate::`
185``` 185```
186 186
187## Import Style
188
189Items from `hir` and `ast` should be used qualified:
190
191```rust
192// Good
193use ra_syntax::ast;
194
195fn frobnicate(func: hir::Function, strukt: ast::StructDef) {}
196
197// Not as good
198use hir::Function;
199use ra_syntax::ast::StructDef;
200
201fn frobnicate(func: Function, strukt: StructDef) {}
202```
203
204Avoid local `use MyEnum::*` imports.
205
206Prefer `use crate::foo::bar` to `use super::bar`.
207
187## Order of Items 208## Order of Items
188 209
189Optimize for the reader who sees the file for the first time, and wants to get the general idea about what's going on. 210Optimize 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 {
220For `.md` and `.adoc` files, prefer a sentence-per-line format, don't wrap lines. 241For `.md` and `.adoc` files, prefer a sentence-per-line format, don't wrap lines.
221If the line is too long, you want to split the sentence in two :-) 242If the line is too long, you want to split the sentence in two :-)
222 243
244## Preconditions
245
246Function preconditions should generally be expressed in types and provided by the caller (rather than checked by callee):
247
248```rust
249// Good
250fn frbonicate(walrus: Walrus) {
251 ...
252}
253
254// Not as good
255fn 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
266We don't have specific rules around git history hygiene.
267Maintaining clean git history is encouraged, but not enforced.
268We use rebase workflow, it's OK to rewrite history during PR review process.
269
270Avoid @mentioning people in commit messages, as such messages create a lot of duplicate notification traffic during rebases.
271
272# Architecture Invariants
273
274This section tries to document high-level design constraints, which are not
275always obvious from the low-level code.
276
277## Incomplete syntax trees
278
279Syntax trees are by design incomplete and do not enforce well-formedness.
280If ast method returns an `Option`, it *can* be `None` at runtime, even if this is forbidden by the grammar.
281
282## LSP indenpendence
283
284rust-analyzer is independent from LSP.
285It provides features for a hypothetical perfect Rust-specific IDE client.
286Internal 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
290There's a semi-hard split between "compiler" and "IDE", at the `ra_hir` crate.
291Compiler derives new facts about source code.
292It explicitly acknowledges that not all info is available (ie, you can't look at types during name resolution).
293
294IDE assumes that all information is available at all times.
295
296IDE 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
301The main IDE crate (`ra_ide`) uses "Plain Old Data" for the API.
302Rather than talking in definitions and references, it talks in Strings and textual offsets.
303In 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.
304The results are 100% Rust specific though.
305
223# Logging 306# Logging
224 307
225Logging is done by both rust-analyzer and VS Code, so it might be tricky to 308Logging 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
5pub mod not_bash; 5pub mod not_bash;
6pub mod install; 6pub mod install;
7pub mod release;
7pub mod dist; 8pub mod dist;
8pub mod pre_commit; 9pub mod pre_commit;
9 10
@@ -19,7 +20,7 @@ use walkdir::{DirEntry, WalkDir};
19 20
20use crate::{ 21use 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
25pub use anyhow::{bail, Context as _, Result}; 26pub use anyhow::{bail, Context as _, Result};
@@ -153,60 +154,6 @@ pub fn run_pre_cache() -> Result<()> {
153 Ok(()) 154 Ok(())
154} 155}
155 156
156pub 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
179Commit: commit:{}[] +
180Release: 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
210fn is_release_tag(tag: &str) -> bool { 157fn 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
23fn main() -> Result<()> { 24fn 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 @@
1use crate::{
2 codegen, is_release_tag,
3 not_bash::{date_iso, fs2, run},
4 project_root, Mode, Result,
5};
6
7pub struct ReleaseCmd {
8 pub dry_run: bool,
9}
10
11impl 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}