aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-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
17 files changed, 754 insertions, 265 deletions
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 {