mirror of
https://github.com/rust-lang/rust-analyzer
synced 2025-01-13 13:48:50 +00:00
Add Run|Debug hover actions
This commit is contained in:
parent
de74c0dcab
commit
3434f1dd2c
6 changed files with 184 additions and 24 deletions
|
@ -14,34 +14,42 @@ use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffs
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel, ToNav},
|
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)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct HoverConfig {
|
pub struct HoverConfig {
|
||||||
pub implementations: bool,
|
pub implementations: bool,
|
||||||
|
pub run: bool,
|
||||||
|
pub debug: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for HoverConfig {
|
impl Default for HoverConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self { implementations: true }
|
Self { implementations: true, run: true, debug: true }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HoverConfig {
|
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 {
|
pub fn any(&self) -> bool {
|
||||||
self.implementations
|
self.implementations || self.runnable()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn none(&self) -> bool {
|
pub fn none(&self) -> bool {
|
||||||
!self.any()
|
!self.any()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn runnable(&self) -> bool {
|
||||||
|
self.run || self.debug
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum HoverAction {
|
pub enum HoverAction {
|
||||||
|
Runnable(Runnable),
|
||||||
Implementaion(FilePosition),
|
Implementaion(FilePosition),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,6 +133,10 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn
|
||||||
res.push_action(action);
|
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));
|
return Some(RangeInfo::new(range, res));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -175,6 +187,28 @@ fn show_implementations_action(db: &RootDatabase, def: Definition) -> Option<Hov
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(
|
fn hover_text(
|
||||||
docs: Option<String>,
|
docs: Option<String>,
|
||||||
desc: Option<String>,
|
desc: Option<String>,
|
||||||
|
@ -292,6 +326,7 @@ fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use insta::assert_debug_snapshot;
|
||||||
|
|
||||||
use ra_db::FileLoader;
|
use ra_db::FileLoader;
|
||||||
use ra_syntax::TextRange;
|
use ra_syntax::TextRange;
|
||||||
|
@ -309,6 +344,7 @@ mod tests {
|
||||||
fn assert_impl_action(action: &HoverAction, position: u32) {
|
fn assert_impl_action(action: &HoverAction, position: u32) {
|
||||||
let offset = match action {
|
let offset = match action {
|
||||||
HoverAction::Implementaion(pos) => pos.offset,
|
HoverAction::Implementaion(pos) => pos.offset,
|
||||||
|
it => panic!("Unexpected hover action: {:#?}", it),
|
||||||
};
|
};
|
||||||
assert_eq!(offset, position.into());
|
assert_eq!(offset, position.into());
|
||||||
}
|
}
|
||||||
|
@ -1176,4 +1212,89 @@ fn func(foo: i32) { if true { <|>foo; }; }
|
||||||
);
|
);
|
||||||
assert_impl_action(&actions[0], 5);
|
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: [],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
"###);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,14 +11,14 @@ use ra_syntax::{
|
||||||
|
|
||||||
use crate::{display::ToNav, FileId, NavigationTarget};
|
use crate::{display::ToNav, FileId, NavigationTarget};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Runnable {
|
pub struct Runnable {
|
||||||
pub nav: NavigationTarget,
|
pub nav: NavigationTarget,
|
||||||
pub kind: RunnableKind,
|
pub kind: RunnableKind,
|
||||||
pub cfg_exprs: Vec<CfgExpr>,
|
pub cfg_exprs: Vec<CfgExpr>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum TestId {
|
pub enum TestId {
|
||||||
Name(String),
|
Name(String),
|
||||||
Path(String),
|
Path(String),
|
||||||
|
@ -33,7 +33,7 @@ impl fmt::Display for TestId {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum RunnableKind {
|
pub enum RunnableKind {
|
||||||
Test { test_id: TestId, attr: TestAttr },
|
Test { test_id: TestId, attr: TestAttr },
|
||||||
TestMod { path: String },
|
TestMod { path: String },
|
||||||
|
@ -95,7 +95,11 @@ pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {
|
||||||
source_file.syntax().descendants().filter_map(|i| runnable(&sema, i, file_id)).collect()
|
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_ast! {
|
||||||
match item {
|
match item {
|
||||||
ast::FnDef(it) => runnable_fn(sema, it, file_id),
|
ast::FnDef(it) => runnable_fn(sema, it, file_id),
|
||||||
|
@ -171,7 +175,7 @@ fn runnable_fn(
|
||||||
Some(Runnable { nav, kind, cfg_exprs })
|
Some(Runnable { nav, kind, cfg_exprs })
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct TestAttr {
|
pub struct TestAttr {
|
||||||
pub ignore: bool,
|
pub ignore: bool,
|
||||||
}
|
}
|
||||||
|
|
|
@ -285,6 +285,8 @@ impl Config {
|
||||||
set(value, "/hoverActions/enable", &mut use_hover_actions);
|
set(value, "/hoverActions/enable", &mut use_hover_actions);
|
||||||
if use_hover_actions {
|
if use_hover_actions {
|
||||||
set(value, "/hoverActions/implementations", &mut self.hover.implementations);
|
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 {
|
} else {
|
||||||
self.hover = HoverConfig::NO_ACTIONS;
|
self.hover = HoverConfig::NO_ACTIONS;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,8 @@ use lsp_types::{
|
||||||
TextDocumentIdentifier, Url, WorkspaceEdit,
|
TextDocumentIdentifier, Url, WorkspaceEdit,
|
||||||
};
|
};
|
||||||
use ra_ide::{
|
use ra_ide::{
|
||||||
FileId, FilePosition, FileRange, HoverAction, Query, RangeInfo, Runnable, RunnableKind, SearchScope,
|
FileId, FilePosition, FileRange, HoverAction, Query, RangeInfo, Runnable, RunnableKind,
|
||||||
TextEdit,
|
SearchScope, TextEdit,
|
||||||
};
|
};
|
||||||
use ra_prof::profile;
|
use ra_prof::profile;
|
||||||
use ra_project_model::TargetKind;
|
use ra_project_model::TargetKind;
|
||||||
|
@ -403,12 +403,12 @@ pub fn handle_runnables(
|
||||||
if !runnable.nav.full_range().contains_inclusive(offset) {
|
if !runnable.nav.full_range().contains_inclusive(offset) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if is_lib_target(&runnable, cargo_spec.as_ref()) {
|
if is_lib_target(&runnable, cargo_spec.as_ref()) {
|
||||||
continue;
|
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
|
// Add `cargo check` and `cargo test` for the whole package
|
||||||
|
@ -550,7 +550,7 @@ pub fn handle_hover(
|
||||||
}),
|
}),
|
||||||
range: Some(range),
|
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))
|
Ok(Some(hover))
|
||||||
|
@ -818,7 +818,7 @@ pub fn handle_code_lens(
|
||||||
|
|
||||||
let action = runnable.action();
|
let action = runnable.action();
|
||||||
let range = to_proto::range(&line_index, runnable.nav.range());
|
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 {
|
if snap.config.lens.run {
|
||||||
let lens = CodeLens {
|
let lens = CodeLens {
|
||||||
range,
|
range,
|
||||||
|
@ -829,11 +829,8 @@ pub fn handle_code_lens(
|
||||||
}
|
}
|
||||||
|
|
||||||
if action.debugee && snap.config.lens.debug {
|
if action.debugee && snap.config.lens.debug {
|
||||||
let debug_lens = CodeLens {
|
let debug_lens =
|
||||||
range,
|
CodeLens { range, command: Some(debug_single_command(r)), data: None };
|
||||||
command: Some(debug_single_command(r)),
|
|
||||||
data: None,
|
|
||||||
};
|
|
||||||
lenses.push(debug_lens);
|
lenses.push(debug_lens);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1183,8 +1180,33 @@ fn show_impl_command_link(
|
||||||
None
|
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(
|
fn prepare_hover_actions(
|
||||||
snap: &GlobalStateSnapshot,
|
snap: &GlobalStateSnapshot,
|
||||||
|
file_id: FileId,
|
||||||
actions: &[HoverAction],
|
actions: &[HoverAction],
|
||||||
) -> Vec<lsp_ext::CommandLinkGroup> {
|
) -> Vec<lsp_ext::CommandLinkGroup> {
|
||||||
if snap.config.hover.none() || !snap.config.client_caps.hover_actions {
|
if snap.config.hover.none() || !snap.config.client_caps.hover_actions {
|
||||||
|
@ -1195,6 +1217,7 @@ fn prepare_hover_actions(
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|it| match it {
|
.filter_map(|it| match it {
|
||||||
HoverAction::Implementaion(position) => show_impl_command_link(snap, position),
|
HoverAction::Implementaion(position) => show_impl_command_link(snap, position),
|
||||||
|
HoverAction::Runnable(r) => to_runnable_action(snap, file_id, r),
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
@ -1205,10 +1228,10 @@ fn is_lib_target(runnable: &Runnable, cargo_spec: Option<&CargoTargetSpec>) -> b
|
||||||
if let Some(spec) = cargo_spec {
|
if let Some(spec) = cargo_spec {
|
||||||
match spec.target_kind {
|
match spec.target_kind {
|
||||||
TargetKind::Bin => return true,
|
TargetKind::Bin => return true,
|
||||||
_ => ()
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
|
@ -656,14 +656,14 @@ pub(crate) fn resolved_code_action(
|
||||||
pub(crate) fn runnable(
|
pub(crate) fn runnable(
|
||||||
snap: &GlobalStateSnapshot,
|
snap: &GlobalStateSnapshot,
|
||||||
file_id: FileId,
|
file_id: FileId,
|
||||||
runnable: Runnable,
|
runnable: &Runnable,
|
||||||
) -> Result<lsp_ext::Runnable> {
|
) -> Result<lsp_ext::Runnable> {
|
||||||
let spec = CargoTargetSpec::for_file(snap, file_id)?;
|
let spec = CargoTargetSpec::for_file(snap, file_id)?;
|
||||||
let target = spec.as_ref().map(|s| s.target.clone());
|
let target = spec.as_ref().map(|s| s.target.clone());
|
||||||
let (cargo_args, executable_args) =
|
let (cargo_args, executable_args) =
|
||||||
CargoTargetSpec::runnable_args(spec, &runnable.kind, &runnable.cfg_exprs)?;
|
CargoTargetSpec::runnable_args(spec, &runnable.kind, &runnable.cfg_exprs)?;
|
||||||
let label = runnable.label(target);
|
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 {
|
Ok(lsp_ext::Runnable {
|
||||||
label,
|
label,
|
||||||
|
|
|
@ -486,6 +486,16 @@
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"default": true
|
"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": {
|
"rust-analyzer.linkedProjects": {
|
||||||
"markdownDescription": [
|
"markdownDescription": [
|
||||||
"Disable project auto-discovery in favor of explicitly specified set of projects.",
|
"Disable project auto-discovery in favor of explicitly specified set of projects.",
|
||||||
|
|
Loading…
Reference in a new issue