]> git.lizzy.rs Git - rust.git/commitdiff
Add Run|Debug hover actions
authorvsrs <vit@conrlab.com>
Sat, 6 Jun 2020 11:30:29 +0000 (14:30 +0300)
committervsrs <vit@conrlab.com>
Sat, 6 Jun 2020 15:17:52 +0000 (18:17 +0300)
crates/ra_ide/src/hover.rs
crates/ra_ide/src/runnables.rs
crates/rust-analyzer/src/config.rs
crates/rust-analyzer/src/main_loop/handlers.rs
crates/rust-analyzer/src/to_proto.rs
editors/code/package.json

index 846d8c69ba26f8acaf1082543080e7747b2db7d9..138a7a7a9cb282cd5999fc10452ef1696b9aa4fc 100644 (file)
 
 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),
 }
 
@@ -125,6 +133,10 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn
                 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));
         }
     }
@@ -175,6 +187,28 @@ fn to_action(nav_target: NavigationTarget) -> HoverAction {
     }
 }
 
+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>,
@@ -292,6 +326,7 @@ fn priority(n: &SyntaxToken) -> usize {
 #[cfg(test)]
 mod tests {
     use super::*;
+    use insta::assert_debug_snapshot;
 
     use ra_db::FileLoader;
     use ra_syntax::TextRange;
@@ -309,6 +344,7 @@ fn trim_markup_opt(s: Option<&str>) -> Option<&str> {
     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());
     }
@@ -1176,4 +1212,89 @@ enum foo<|>() {
         );
         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: [],
+                    },
+                ),
+            ]
+            "###);
+    }
 }
index 9f7b5edfd5766879eecba712e6606b4c93f02597..fc57dc33d78d7af0bd6b1bba2f9b9f048e96b004 100644 (file)
 
 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),
@@ -33,7 +33,7 @@ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
     }
 }
 
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub enum RunnableKind {
     Test { test_id: TestId, attr: TestAttr },
     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()
 }
 
-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),
@@ -171,7 +175,7 @@ fn runnable_fn(
     Some(Runnable { nav, kind, cfg_exprs })
 }
 
-#[derive(Debug)]
+#[derive(Debug, Copy, Clone)]
 pub struct TestAttr {
     pub ignore: bool,
 }
index 8d6efdbe8cf970775f15a7f241c5af720e0b3381..17671f89ee415db0bef17dff04d848e75e846906 100644 (file)
@@ -285,6 +285,8 @@ pub fn update(&mut self, value: &serde_json::Value) {
         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;
         }
index da16976d3daab11fbc299b10a57b7ab88ff5a7b3..cae447eea8721ee838c0a7af4a2e588d41f94c51 100644 (file)
@@ -18,8 +18,8 @@
     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;
@@ -403,12 +403,12 @@ pub fn handle_runnables(
             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
@@ -550,7 +550,7 @@ pub fn handle_hover(
             }),
             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))
@@ -818,7 +818,7 @@ pub fn handle_code_lens(
 
             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,
@@ -829,11 +829,8 @@ pub fn handle_code_lens(
             }
 
             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);
             }
         }
@@ -1183,8 +1180,33 @@ fn show_impl_command_link(
     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 {
@@ -1195,6 +1217,7 @@ fn prepare_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()
 }
@@ -1205,10 +1228,10 @@ fn is_lib_target(runnable: &Runnable, cargo_spec: Option<&CargoTargetSpec>) -> b
         if let Some(spec) = cargo_spec {
             match spec.target_kind {
                 TargetKind::Bin => return true,
-                _ => ()
+                _ => (),
             }
         }
     }
 
     false
-}
\ No newline at end of file
+}
index 710df1fbdec4e7d3e530b8c2d64fbe083bd5641e..5daf037dabf38402be85fe8840238bb3cab9204b 100644 (file)
@@ -656,14 +656,14 @@ pub(crate) fn resolved_code_action(
 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,
index b9c57db3bcf74485248c99de1e9b9c72b92e190b..7fdb5c27d9d1f12a20e70f85bc9d8c92ad5be08e 100644 (file)
                     "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.",