diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 53aee833d62..b5dc6f0fa98 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -33,6 +33,36 @@ pub struct Config { pub inlay_hints: InlayHintsConfig, pub completion: CompletionConfig, pub call_info_full: bool, + pub lens: LensConfig, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct LensConfig { + pub run: bool, + pub debug: bool, + pub impementations: bool, +} + +impl Default for LensConfig { + fn default() -> Self { + Self { run: true, debug: true, impementations: true } + } +} + +impl LensConfig { + pub const NO_LENS: LensConfig = Self { run: false, debug: false, impementations: false }; + + pub fn any(&self) -> bool { + self.impementations || self.runnable() + } + + pub fn none(&self) -> bool { + !self.any() + } + + pub fn runnable(&self) -> bool { + self.run || self.debug + } } #[derive(Debug, Clone)] @@ -107,6 +137,7 @@ impl Default for Config { ..CompletionConfig::default() }, call_info_full: true, + lens: LensConfig::default(), } } } @@ -196,6 +227,16 @@ impl Config { set(value, "/completion/addCallArgumentSnippets", &mut self.completion.add_call_argument_snippets); set(value, "/callInfo/full", &mut self.call_info_full); + let mut lens_enabled = true; + set(value, "/lens/enable", &mut lens_enabled); + if lens_enabled { + set(value, "/lens/run", &mut self.lens.run); + set(value, "/lens/debug", &mut self.lens.debug); + set(value, "/lens/implementations", &mut self.lens.impementations); + } else { + self.lens = LensConfig::NO_LENS; + } + log::info!("Config::update() = {:#?}", self); fn get<'a, T: Deserialize<'a>>(value: &'a serde_json::Value, pointer: &str) -> Option { diff --git a/crates/rust-analyzer/src/main_loop/handlers.rs b/crates/rust-analyzer/src/main_loop/handlers.rs index 6b14830b6df..e6755675285 100644 --- a/crates/rust-analyzer/src/main_loop/handlers.rs +++ b/crates/rust-analyzer/src/main_loop/handlers.rs @@ -812,88 +812,108 @@ pub fn handle_code_lens( params: lsp_types::CodeLensParams, ) -> Result>> { let _p = profile("handle_code_lens"); - let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?; - let line_index = world.analysis().file_line_index(file_id)?; - let mut lenses: Vec = Default::default(); - let cargo_spec = CargoTargetSpec::for_file(&world, file_id)?; - // Gather runnables - for runnable in world.analysis().runnables(file_id)? { - let title = match &runnable.kind { - RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => "▶\u{fe0e} Run Test", - RunnableKind::DocTest { .. } => "▶\u{fe0e} Run Doctest", - RunnableKind::Bench { .. } => "Run Bench", - RunnableKind::Bin => { - // Do not suggest binary run on other target than binary - match &cargo_spec { - Some(spec) => match spec.target_kind { - TargetKind::Bin => "Run", - _ => continue, - }, - None => continue, - } - } - } - .to_string(); - 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()]), - }), - data: None, - }; - lenses.push(lens); - - 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); + if world.config.lens.none() { + // early return before any db query! + return Ok(Some(lenses)); } - // Handle impls - lenses.extend( - world - .analysis() - .file_structure(file_id)? - .into_iter() - .filter(|it| match it.kind { - SyntaxKind::TRAIT_DEF | SyntaxKind::STRUCT_DEF | SyntaxKind::ENUM_DEF => true, - _ => false, - }) - .map(|it| { - let range = to_proto::range(&line_index, it.node_range); - let pos = range.start; - let lens_params = lsp_types::request::GotoImplementationParams { - text_document_position_params: lsp_types::TextDocumentPositionParams::new( - params.text_document.clone(), - pos, - ), - work_done_progress_params: Default::default(), - partial_result_params: Default::default(), - }; - CodeLens { - range, - command: None, - data: Some(to_value(CodeLensResolveData::Impls(lens_params)).unwrap()), - } - }), - ); + let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?; + let line_index = world.analysis().file_line_index(file_id)?; + let cargo_spec = CargoTargetSpec::for_file(&world, file_id)?; + if world.config.lens.runnable() { + // Gather runnables + for runnable in world.analysis().runnables(file_id)? { + let (run_title, debugee) = match &runnable.kind { + RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => { + ("▶️\u{fe0e}Run Test", true) + } + RunnableKind::DocTest { .. } => { + // cargo does not support -no-run for doctests + ("▶️\u{fe0e}Run Doctest", false) + } + RunnableKind::Bench { .. } => { + // Nothing wrong with bench debugging + ("Run Bench", true) + } + RunnableKind::Bin => { + // Do not suggest binary run on other target than binary + match &cargo_spec { + Some(spec) => match spec.target_kind { + TargetKind::Bin => ("Run", true), + _ => continue, + }, + None => continue, + } + } + }; + + let mut r = to_lsp_runnable(&world, file_id, runnable)?; + if world.config.lens.run { + let lens = CodeLens { + range: r.range, + command: Some(Command { + title: run_title.to_string(), + command: "rust-analyzer.runSingle".into(), + arguments: Some(vec![to_value(&r).unwrap()]), + }), + data: None, + }; + lenses.push(lens); + } + + if debugee && world.config.lens.debug { + 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); + } + } + } + + if world.config.lens.impementations { + // Handle impls + lenses.extend( + world + .analysis() + .file_structure(file_id)? + .into_iter() + .filter(|it| match it.kind { + SyntaxKind::TRAIT_DEF | SyntaxKind::STRUCT_DEF | SyntaxKind::ENUM_DEF => true, + _ => false, + }) + .map(|it| { + let range = to_proto::range(&line_index, it.node_range); + let pos = range.start; + let lens_params = lsp_types::request::GotoImplementationParams { + text_document_position_params: lsp_types::TextDocumentPositionParams::new( + params.text_document.clone(), + pos, + ), + work_done_progress_params: Default::default(), + partial_result_params: Default::default(), + }; + CodeLens { + range, + command: None, + data: Some(to_value(CodeLensResolveData::Impls(lens_params)).unwrap()), + } + }), + ); + } Ok(Some(lenses)) } diff --git a/editors/code/package.json b/editors/code/package.json index 4045ab3d275..d899f60e333 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -443,6 +443,26 @@ "type": "object", "default": {}, "description": "Optional settings passed to the debug engine. Example:\n{ \"lldb\": { \"terminal\":\"external\"} }" + }, + "rust-analyzer.lens.enable": { + "description": "Whether to show CodeLens in Rust files.", + "type": "boolean", + "default": true + }, + "rust-analyzer.lens.run": { + "markdownDescription": "Whether to show Run lens. Only applies when `#rust-analyzer.lens.enable#` is set.", + "type": "boolean", + "default": true + }, + "rust-analyzer.lens.debug": { + "markdownDescription": "Whether to show Debug lens. Only applies when `#rust-analyzer.lens.enable#` is set.", + "type": "boolean", + "default": true + }, + "rust-analyzer.lens.implementations": { + "markdownDescription": "Whether to show Implementations lens. Only applies when `#rust-analyzer.lens.enable#` is set.", + "type": "boolean", + "default": true } } }, diff --git a/editors/code/src/commands/runnables.ts b/editors/code/src/commands/runnables.ts index b1d93fc34eb..0bd30fb077d 100644 --- a/editors/code/src/commands/runnables.ts +++ b/editors/code/src/commands/runnables.ts @@ -7,7 +7,7 @@ import { startDebugSession, getDebugConfiguration } from '../debug'; const quickPickButtons = [{ iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configurtation." }]; -async function selectRunnable(ctx: Ctx, prevRunnable?: RunnableQuickPick, showButtons: boolean = true): Promise { +async function selectRunnable(ctx: Ctx, prevRunnable?: RunnableQuickPick, debuggeeOnly = false, showButtons: boolean = true): Promise { const editor = ctx.activeRustEditor; const client = ctx.client; if (!editor || !client) return; @@ -33,9 +33,20 @@ async function selectRunnable(ctx: Ctx, prevRunnable?: RunnableQuickPick, showBu ) { continue; } + + if (debuggeeOnly && (r.label.startsWith('doctest') || r.label.startsWith('cargo'))) { + continue; + } items.push(new RunnableQuickPick(r)); } + if (items.length === 0) { + // it is the debug case, run always has at least 'cargo check ...' + // see crates\rust-analyzer\src\main_loop\handlers.rs, handle_runnables + vscode.window.showErrorMessage("There's no debug target!"); + return; + } + return await new Promise((resolve) => { const disposables: vscode.Disposable[] = []; const close = (result?: RunnableQuickPick) => { @@ -107,7 +118,7 @@ export function debug(ctx: Ctx): Cmd { let prevDebuggee: RunnableQuickPick | undefined; return async () => { - const item = await selectRunnable(ctx, prevDebuggee); + const item = await selectRunnable(ctx, prevDebuggee, true); if (!item) return; item.detail = 'restart'; @@ -147,7 +158,7 @@ async function makeDebugConfig(ctx: Ctx, item: RunnableQuickPick): Promise export function newDebugConfig(ctx: Ctx): Cmd { return async () => { - const item = await selectRunnable(ctx, undefined, false); + const item = await selectRunnable(ctx, undefined, true, false); if (!item) return; await makeDebugConfig(ctx, item); diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index 1652827c32a..ee294fbe312 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -16,6 +16,10 @@ export class Config { "files", "highlighting", "updates.channel", + "lens.enable", + "lens.run", + "lens.debug", + "lens.implementations", ] .map(opt => `${this.rootSection}.${opt}`); @@ -119,4 +123,13 @@ export class Config { sourceFileMap: sourceFileMap }; } + + get lens() { + return { + enable: this.get("lens.enable"), + run: this.get("lens.run"), + debug: this.get("lens.debug"), + implementations: this.get("lens.implementations"), + }; + } }