Repository: rust-analyzer/rust-analyzer
Pull request #3561 (2020-03-11 20:57:48)

feat: add debug code lens
Refs #3539


Patch:
diff --git a/crates/rust-analyzer/src/cargo_target_spec.rs b/crates/rust-analyzer/src/cargo_target_spec.rs
index 53751aafb7..321861b161 100644
--- a/crates/rust-analyzer/src/cargo_target_spec.rs
+++ b/crates/rust-analyzer/src/cargo_target_spec.rs
@@ -19,50 +19,48 @@ impl CargoTargetSpec {
     pub(crate) fn runnable_args(
         spec: Option<CargoTargetSpec>,
         kind: &RunnableKind,
-    ) -> Result<Vec<String>> {
-        let mut res = Vec::new();
+    ) -> Result<(Vec<String>, Vec<String>)> {
+        let mut args = Vec::new();
+        let mut extra_args = Vec::new();
         match kind {
             RunnableKind::Test { test_id } => {
-                res.push("test".to_string());
+                args.push("test".to_string());
                 if let Some(spec) = spec {
-                    spec.push_to(&mut res);
+                    spec.push_to(&mut args);
                 }
-                res.push("--".to_string());
-                res.push(test_id.to_string());
+                extra_args.push(test_id.to_string());
                 if let TestId::Path(_) = test_id {
-                    res.push("--exact".to_string());
+                    extra_args.push("--exact".to_string());
                 }
-                res.push("--nocapture".to_string());
+                extra_args.push("--nocapture".to_string());
             }
             RunnableKind::TestMod { path } => {
-                res.push("test".to_string());
+                args.push("test".to_string());
                 if let Some(spec) = spec {
-                    spec.push_to(&mut res);
+                    spec.push_to(&mut args);
                 }
-                res.push("--".to_string());
-                res.push(path.to_string());
-                res.push("--nocapture".to_string());
+                extra_args.push(path.to_string());
+                extra_args.push("--nocapture".to_string());
             }
             RunnableKind::Bench { test_id } => {
-                res.push("bench".to_string());
+                args.push("bench".to_string());
                 if let Some(spec) = spec {
-                    spec.push_to(&mut res);
+                    spec.push_to(&mut args);
                 }
-                res.push("--".to_string());
-                res.push(test_id.to_string());
+                extra_args.push(test_id.to_string());
                 if let TestId::Path(_) = test_id {
-                    res.push("--exact".to_string());
+                    extra_args.push("--exact".to_string());
                 }
-                res.push("--nocapture".to_string());
+                extra_args.push("--nocapture".to_string());
             }
             RunnableKind::Bin => {
-                res.push("run".to_string());
+                args.push("run".to_string());
                 if let Some(spec) = spec {
-                    spec.push_to(&mut res);
+                    spec.push_to(&mut args);
                 }
             }
         }
-        Ok(res)
+        Ok((args, extra_args))
     }
 
     pub(crate) fn for_file(
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index a8bf29ddf0..cfca06f569 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -51,6 +51,9 @@ pub struct ServerConfig {
 
     /// Cargo feature configurations.
     pub cargo_features: CargoFeatures,
+
+    /// Enabled if the vscode_lldb extension is available.
+    pub vscode_lldb: bool,
 }
 
 impl Default for ServerConfig {
@@ -70,6 +73,7 @@ impl Default for ServerConfig {
             additional_out_dirs: FxHashMap::default(),
             cargo_features: Default::default(),
             rustfmt_args: Vec::new(),
+            vscode_lldb: false,
         }
     }
 }
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index 4f7aac7540..b4add046f0 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -185,6 +185,7 @@ pub fn main_loop(
                     all_targets: config.cargo_watch_all_targets,
                 },
                 rustfmt_args: config.rustfmt_args,
+                vscode_lldb: config.vscode_lldb,
             }
         };
 
diff --git a/crates/rust-analyzer/src/main_loop/handlers.rs b/crates/rust-analyzer/src/main_loop/handlers.rs
index fcb40432d4..571a896f30 100644
--- a/crates/rust-analyzer/src/main_loop/handlers.rs
+++ b/crates/rust-analyzer/src/main_loop/handlers.rs
@@ -381,6 +381,7 @@ pub fn handle_runnables(
         label,
         bin: "cargo".to_string(),
         args: check_args,
+        extra_args: Vec::new(),
         env: FxHashMap::default(),
         cwd: workspace_root.map(|root| root.to_string_lossy().to_string()),
     });
@@ -794,18 +795,35 @@ pub fn handle_code_lens(
             RunnableKind::Bin => "Run",
         }
         .to_string();
-        let r = to_lsp_runnable(&world, file_id, runnable)?;
+        let mut r = to_lsp_runnable(&world, file_id, runnable)?;
         let lens = CodeLens {
             range: r.range,
             command: Some(Command {
                 title,
                 command: "rust-analyzer.runSingle".into(),
-                arguments: Some(vec![to_value(r).unwrap()]),
+                arguments: Some(vec![to_value(&r).unwrap()]),
             }),
             data: None,
         };
-
         lenses.push(lens);
+
+        if world.options.vscode_lldb {
+            if r.args[0] == "run" {
+                r.args[0] = "build".into();
+            } else {
+                r.args.push("--no-run".into());
+            }
+            let debug_lens = CodeLens {
+                range: r.range,
+                command: Some(Command {
+                    title: "Debug".into(),
+                    command: "rust-analyzer.debugSingle".into(),
+                    arguments: Some(vec![to_value(r).unwrap()]),
+                }),
+                data: None,
+            };
+            lenses.push(debug_lens);
+        }
     }
 
     // Handle impls
@@ -952,7 +970,7 @@ fn to_lsp_runnable(
     runnable: Runnable,
 ) -> Result<req::Runnable> {
     let spec = CargoTargetSpec::for_file(world, file_id)?;
-    let args = CargoTargetSpec::runnable_args(spec, &runnable.kind)?;
+    let (args, extra_args) = CargoTargetSpec::runnable_args(spec, &runnable.kind)?;
     let line_index = world.analysis().file_line_index(file_id)?;
     let label = match &runnable.kind {
         RunnableKind::Test { test_id } => format!("test {}", test_id),
@@ -965,6 +983,7 @@ fn to_lsp_runnable(
         label,
         bin: "cargo".to_string(),
         args,
+        extra_args,
         env: {
             let mut m = FxHashMap::default();
             m.insert("RUST_BACKTRACE".to_string(), "short".to_string());
@@ -973,6 +992,7 @@ fn to_lsp_runnable(
         cwd: world.workspace_root_for(file_id).map(|root| root.to_string_lossy().to_string()),
     })
 }
+
 fn highlight(world: &WorldSnapshot, file_id: FileId) -> Result<Vec<Decoration>> {
     let line_index = world.analysis().file_line_index(file_id)?;
     let res = world
diff --git a/crates/rust-analyzer/src/req.rs b/crates/rust-analyzer/src/req.rs
index a3efe3b9fe..156328df8f 100644
--- a/crates/rust-analyzer/src/req.rs
+++ b/crates/rust-analyzer/src/req.rs
@@ -169,6 +169,7 @@ pub struct Runnable {
     pub label: String,
     pub bin: String,
     pub args: Vec<String>,
+    pub extra_args: Vec<String>,
     pub env: FxHashMap<String, String>,
     pub cwd: Option<String>,
 }
diff --git a/crates/rust-analyzer/src/world.rs b/crates/rust-analyzer/src/world.rs
index 9ef368529e..a4fda9f806 100644
--- a/crates/rust-analyzer/src/world.rs
+++ b/crates/rust-analyzer/src/world.rs
@@ -37,6 +37,7 @@ pub struct Options {
     pub max_inlay_hint_length: Option<usize>,
     pub rustfmt_args: Vec<String>,
     pub cargo_watch: CheckOptions,
+    pub vscode_lldb: bool,
 }
 
 /// `WorldState` is the primary mutable state of the language server
diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts
index 6ce3b9235c..99c9c5ae70 100644
--- a/editors/code/src/client.ts
+++ b/editors/code/src/client.ts
@@ -41,6 +41,7 @@ export async function createClient(config: Config, serverPath: string): Promise<
             withSysroot: config.withSysroot,
             cargoFeatures: config.cargoFeatures,
             rustfmtArgs: config.rustfmtArgs,
+            vscodeLldb: vscode.extensions.getExtension("vadimcn.vscode-lldb") != null,
         },
         traceOutputChannel,
         middleware: {
diff --git a/editors/code/src/commands/runnables.ts b/editors/code/src/commands/runnables.ts
index 06b5134668..357155163d 100644
--- a/editors/code/src/commands/runnables.ts
+++ b/editors/code/src/commands/runnables.ts
@@ -62,6 +62,26 @@ export function runSingle(ctx: Ctx): Cmd {
     };
 }
 
+export function debugSingle(ctx: Ctx): Cmd {
+    return async (config: ra.Runnable) => {
+        const editor = ctx.activeRustEditor;
+        if (!editor) return;
+
+        const debugConfig = {
+            type: "lldb",
+            request: "launch",
+            name: config.label,
+            cargo: {
+                args: config.args,
+            },
+            args: config.extraArgs,
+            cwd: config.cwd
+        };
+
+        return vscode.debug.startDebugging(undefined, debugConfig);
+    };
+}
+
 class RunnableQuickPick implements vscode.QuickPickItem {
     public label: string;
     public description?: string | undefined;
@@ -87,7 +107,7 @@ function createTask(spec: ra.Runnable): vscode.Task {
         type: 'cargo',
         label: spec.label,
         command: spec.bin,
-        args: spec.args,
+        args: spec.extraArgs ? [...spec.args, '--', ...spec.extraArgs] : spec.args,
         env: spec.env,
     };
 
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts
index ecf53cf775..e01c89cc7c 100644
--- a/editors/code/src/main.ts
+++ b/editors/code/src/main.ts
@@ -83,6 +83,7 @@ export async function activate(context: vscode.ExtensionContext) {
 
     // Internal commands which are invoked by the server.
     ctx.registerCommand('runSingle', commands.runSingle);
+    ctx.registerCommand('debugSingle', commands.debugSingle);
     ctx.registerCommand('showReferences', commands.showReferences);
     ctx.registerCommand('applySourceChange', commands.applySourceChange);
     ctx.registerCommand('selectAndApplySourceChange', commands.selectAndApplySourceChange);
diff --git a/editors/code/src/rust-analyzer-api.ts b/editors/code/src/rust-analyzer-api.ts
index bd6e3ada08..e09a203c9f 100644
--- a/editors/code/src/rust-analyzer-api.ts
+++ b/editors/code/src/rust-analyzer-api.ts
@@ -80,13 +80,12 @@ export interface Runnable {
     label: string;
     bin: string;
     args: Vec<string>;
+    extraArgs: Vec<string>;
     env: FxHashMap<string, string>;
     cwd: Option<string>;
 }
 export const runnables = request<RunnablesParams, Vec<Runnable>>("runnables");
 
-
-
 export type InlayHint = InlayHint.TypeHint | InlayHint.ParamHint;
 
 export namespace InlayHint {


Test patch:
diff --git a/crates/rust-analyzer/tests/heavy_tests/main.rs b/crates/rust-analyzer/tests/heavy_tests/main.rs
index 970185deca..145429571c 100644
--- a/crates/rust-analyzer/tests/heavy_tests/main.rs
+++ b/crates/rust-analyzer/tests/heavy_tests/main.rs
@@ -75,7 +75,8 @@ fn foo() {
         RunnablesParams { text_document: server.doc_id("lib.rs"), position: None },
         json!([
           {
-            "args": [ "test", "--", "foo", "--nocapture" ],
+            "args": [ "test" ],
+            "extraArgs": [ "foo", "--nocapture" ],
             "bin": "cargo",
             "env": { "RUST_BACKTRACE": "short" },
             "cwd": null,
@@ -90,6 +91,7 @@ fn foo() {
               "check",
               "--all"
             ],
+            "extraArgs": [],
             "bin": "cargo",
             "env": {},
             "cwd": null,
@@ -141,13 +143,11 @@ fn main() {}
 
     server.wait_until_workspace_is_loaded();
     server.request::<Runnables>(
-        RunnablesParams {
-            text_document: server.doc_id("foo/tests/spam.rs"),
-            position: None,
-        },
+        RunnablesParams { text_document: server.doc_id("foo/tests/spam.rs"), position: None },
         json!([
           {
-            "args": [ "test", "--package", "foo", "--test", "spam", "--", "test_eggs", "--exact", "--nocapture" ],
+            "args": [ "test", "--package", "foo", "--test", "spam" ],
+            "extraArgs": [ "test_eggs", "--exact", "--nocapture" ],
             "bin": "cargo",
             "env": { "RUST_BACKTRACE": "short" },
             "label": "test test_eggs",
@@ -165,6 +165,7 @@ fn main() {}
               "--test",
               "spam"
             ],
+            "extraArgs": [],
             "bin": "cargo",
             "env": {},
             "cwd": server.path().join("foo"),
@@ -180,7 +181,7 @@ fn main() {}
               }
             }
           }
-        ])
+        ]),
     );
 }