use crate::{
display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel, ToNav},
- FilePosition, NavigationTarget, RangeInfo,
+ runnables::runnable,
+ FileId, FilePosition, NavigationTarget, RangeInfo, Runnable,
};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HoverConfig {
pub implementations: bool,
+ pub run: bool,
+ pub debug: bool,
}
impl Default for HoverConfig {
fn default() -> Self {
- Self { implementations: true }
+ Self { implementations: true, run: true, debug: true }
}
}
impl HoverConfig {
- pub const NO_ACTIONS: Self = Self { implementations: false };
+ pub const NO_ACTIONS: Self = Self { implementations: false, run: false, debug: false };
pub fn any(&self) -> bool {
- self.implementations
+ self.implementations || self.runnable()
}
pub fn none(&self) -> bool {
!self.any()
}
+
+ pub fn runnable(&self) -> bool {
+ self.run || self.debug
+ }
}
#[derive(Debug, Clone)]
pub enum HoverAction {
+ Runnable(Runnable),
Implementaion(FilePosition),
}
res.push_action(action);
}
+ if let Some(action) = runnable_action(&sema, name_kind, position.file_id) {
+ res.push_action(action);
+ }
+
return Some(RangeInfo::new(range, res));
}
}
}
}
+fn runnable_action(
+ sema: &Semantics<RootDatabase>,
+ def: Definition,
+ file_id: FileId,
+) -> Option<HoverAction> {
+ match def {
+ Definition::ModuleDef(it) => match it {
+ ModuleDef::Module(it) => match it.definition_source(sema.db).value {
+ ModuleSource::Module(it) => runnable(&sema, it.syntax().clone(), file_id)
+ .map(|it| HoverAction::Runnable(it)),
+ _ => None,
+ },
+ ModuleDef::Function(it) => {
+ runnable(&sema, it.source(sema.db).value.syntax().clone(), file_id)
+ .map(|it| HoverAction::Runnable(it))
+ }
+ _ => None,
+ },
+ _ => None,
+ }
+}
+
fn hover_text(
docs: Option<String>,
desc: Option<String>,
#[cfg(test)]
mod tests {
use super::*;
+ use insta::assert_debug_snapshot;
use ra_db::FileLoader;
use ra_syntax::TextRange;
fn assert_impl_action(action: &HoverAction, position: u32) {
let offset = match action {
HoverAction::Implementaion(pos) => pos.offset,
+ it => panic!("Unexpected hover action: {:#?}", it),
};
assert_eq!(offset, position.into());
}
);
assert_impl_action(&actions[0], 5);
}
+
+ #[test]
+ fn test_hover_test_has_action() {
+ let (_, actions) = check_hover_result(
+ "
+ //- /lib.rs
+ #[test]
+ fn foo_<|>test() {}
+ ",
+ &["fn foo_test()"],
+ );
+ assert_debug_snapshot!(actions,
+ @r###"
+ [
+ Runnable(
+ Runnable {
+ nav: NavigationTarget {
+ file_id: FileId(
+ 1,
+ ),
+ full_range: 0..24,
+ name: "foo_test",
+ kind: FN_DEF,
+ focus_range: Some(
+ 11..19,
+ ),
+ container_name: None,
+ description: None,
+ docs: None,
+ },
+ kind: Test {
+ test_id: Path(
+ "foo_test",
+ ),
+ attr: TestAttr {
+ ignore: false,
+ },
+ },
+ cfg_exprs: [],
+ },
+ ),
+ ]
+ "###);
+ }
+
+ #[test]
+ fn test_hover_test_mod_has_action() {
+ let (_, actions) = check_hover_result(
+ "
+ //- /lib.rs
+ mod tests<|> {
+ #[test]
+ fn foo_test() {}
+ }
+ ",
+ &["mod tests"],
+ );
+ assert_debug_snapshot!(actions,
+ @r###"
+ [
+ Runnable(
+ Runnable {
+ nav: NavigationTarget {
+ file_id: FileId(
+ 1,
+ ),
+ full_range: 0..46,
+ name: "tests",
+ kind: MODULE,
+ focus_range: Some(
+ 4..9,
+ ),
+ container_name: None,
+ description: None,
+ docs: None,
+ },
+ kind: TestMod {
+ path: "tests",
+ },
+ cfg_exprs: [],
+ },
+ ),
+ ]
+ "###);
+ }
}
use crate::{display::ToNav, FileId, NavigationTarget};
-#[derive(Debug)]
+#[derive(Debug, Clone)]
pub struct Runnable {
pub nav: NavigationTarget,
pub kind: RunnableKind,
pub cfg_exprs: Vec<CfgExpr>,
}
-#[derive(Debug)]
+#[derive(Debug, Clone)]
pub enum TestId {
Name(String),
Path(String),
}
}
-#[derive(Debug)]
+#[derive(Debug, Clone)]
pub enum RunnableKind {
Test { test_id: TestId, attr: TestAttr },
TestMod { path: String },
source_file.syntax().descendants().filter_map(|i| runnable(&sema, i, file_id)).collect()
}
-fn runnable(sema: &Semantics<RootDatabase>, item: SyntaxNode, file_id: FileId) -> Option<Runnable> {
+pub(crate) fn runnable(
+ sema: &Semantics<RootDatabase>,
+ item: SyntaxNode,
+ file_id: FileId,
+) -> Option<Runnable> {
match_ast! {
match item {
ast::FnDef(it) => runnable_fn(sema, it, file_id),
Some(Runnable { nav, kind, cfg_exprs })
}
-#[derive(Debug)]
+#[derive(Debug, Copy, Clone)]
pub struct TestAttr {
pub ignore: bool,
}
set(value, "/hoverActions/enable", &mut use_hover_actions);
if use_hover_actions {
set(value, "/hoverActions/implementations", &mut self.hover.implementations);
+ set(value, "/hoverActions/run", &mut self.hover.run);
+ set(value, "/hoverActions/debug", &mut self.hover.debug);
} else {
self.hover = HoverConfig::NO_ACTIONS;
}
TextDocumentIdentifier, Url, WorkspaceEdit,
};
use ra_ide::{
- FileId, FilePosition, FileRange, HoverAction, Query, RangeInfo, Runnable, RunnableKind, SearchScope,
- TextEdit,
+ FileId, FilePosition, FileRange, HoverAction, Query, RangeInfo, Runnable, RunnableKind,
+ SearchScope, TextEdit,
};
use ra_prof::profile;
use ra_project_model::TargetKind;
if !runnable.nav.full_range().contains_inclusive(offset) {
continue;
}
- }
+ }
if is_lib_target(&runnable, cargo_spec.as_ref()) {
continue;
}
- res.push(to_proto::runnable(&snap, file_id, runnable)?);
+ res.push(to_proto::runnable(&snap, file_id, &runnable)?);
}
// Add `cargo check` and `cargo test` for the whole package
}),
range: Some(range),
},
- actions: prepare_hover_actions(&snap, info.info.actions()),
+ actions: prepare_hover_actions(&snap, position.file_id, info.info.actions()),
};
Ok(Some(hover))
let action = runnable.action();
let range = to_proto::range(&line_index, runnable.nav.range());
- let r = to_proto::runnable(&snap, file_id, runnable)?;
+ let r = to_proto::runnable(&snap, file_id, &runnable)?;
if snap.config.lens.run {
let lens = CodeLens {
range,
}
if action.debugee && snap.config.lens.debug {
- let debug_lens = CodeLens {
- range,
- command: Some(debug_single_command(r)),
- data: None,
- };
+ let debug_lens =
+ CodeLens { range, command: Some(debug_single_command(r)), data: None };
lenses.push(debug_lens);
}
}
None
}
+fn to_runnable_action(
+ snap: &GlobalStateSnapshot,
+ file_id: FileId,
+ runnable: &Runnable,
+) -> Option<lsp_ext::CommandLinkGroup> {
+ to_proto::runnable(snap, file_id, runnable).ok().map(|r| {
+ let mut group = lsp_ext::CommandLinkGroup::default();
+
+ let action = runnable.action();
+ if snap.config.hover.run {
+ let run_command = run_single_command(&r, action.run_title);
+ group.commands.push(to_command_link(run_command, r.label.clone()));
+ }
+
+ if snap.config.hover.debug {
+ let hint = r.label.clone();
+ let dbg_command = debug_single_command(r);
+ group.commands.push(to_command_link(dbg_command, hint));
+ }
+
+ group
+ })
+}
+
fn prepare_hover_actions(
snap: &GlobalStateSnapshot,
+ file_id: FileId,
actions: &[HoverAction],
) -> Vec<lsp_ext::CommandLinkGroup> {
if snap.config.hover.none() || !snap.config.client_caps.hover_actions {
.iter()
.filter_map(|it| match it {
HoverAction::Implementaion(position) => show_impl_command_link(snap, position),
+ HoverAction::Runnable(r) => to_runnable_action(snap, file_id, r),
})
.collect()
}
if let Some(spec) = cargo_spec {
match spec.target_kind {
TargetKind::Bin => return true,
- _ => ()
+ _ => (),
}
}
}
false
-}
\ No newline at end of file
+}
pub(crate) fn runnable(
snap: &GlobalStateSnapshot,
file_id: FileId,
- runnable: Runnable,
+ runnable: &Runnable,
) -> Result<lsp_ext::Runnable> {
let spec = CargoTargetSpec::for_file(snap, file_id)?;
let target = spec.as_ref().map(|s| s.target.clone());
let (cargo_args, executable_args) =
CargoTargetSpec::runnable_args(spec, &runnable.kind, &runnable.cfg_exprs)?;
let label = runnable.label(target);
- let location = location_link(snap, None, runnable.nav)?;
+ let location = location_link(snap, None, runnable.nav.clone())?;
Ok(lsp_ext::Runnable {
label,
"type": "boolean",
"default": true
},
+ "rust-analyzer.hoverActions.run": {
+ "markdownDescription": "Whether to show `Run` action. Only applies when `#rust-analyzer.hoverActions.enable#` is set.",
+ "type": "boolean",
+ "default": true
+ },
+ "rust-analyzer.hoverActions.debug": {
+ "markdownDescription": "Whether to show `Debug` action. Only applies when `#rust-analyzer.hoverActions.enable#` is set.",
+ "type": "boolean",
+ "default": true
+ },
"rust-analyzer.linkedProjects": {
"markdownDescription": [
"Disable project auto-discovery in favor of explicitly specified set of projects.",