From 8903c890a486c0ea7f981e0a938bff81e1845125 Mon Sep 17 00:00:00 2001 From: David Barsky Date: Thu, 5 Oct 2023 13:02:03 -0400 Subject: [PATCH] Extend `TargetSpec` functionality to rust-project.json --- crates/ide/src/runnables.rs | 6 +- crates/project-model/src/lib.rs | 2 +- crates/project-model/src/project_json.rs | 122 +++++++++-- crates/rust-analyzer/src/global_state.rs | 17 +- crates/rust-analyzer/src/handlers/request.rs | 102 ++++----- crates/rust-analyzer/src/lib.rs | 2 +- crates/rust-analyzer/src/lsp/ext.rs | 29 ++- crates/rust-analyzer/src/lsp/to_proto.rs | 126 ++++++----- .../{cargo_target_spec.rs => target_spec.rs} | 202 +++++++++++++----- docs/dev/lsp-extensions.md | 14 +- editors/code/src/commands.ts | 7 +- editors/code/src/debug.ts | 42 ++-- editors/code/src/lsp_ext.ts | 27 ++- editors/code/src/run.ts | 54 +++-- editors/code/src/util.ts | 5 + editors/code/tsconfig.json | 1 - 16 files changed, 530 insertions(+), 228 deletions(-) rename crates/rust-analyzer/src/{cargo_target_spec.rs => target_spec.rs} (54%) diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs index d334e66d3dd6..8db46a05990b 100644 --- a/crates/ide/src/runnables.rs +++ b/crates/ide/src/runnables.rs @@ -65,15 +65,13 @@ enum RunnableTestKind { impl Runnable { // test package::module::testname - pub fn label(&self, target: Option) -> String { + pub fn label(&self, target: String) -> String { match &self.kind { RunnableKind::Test { test_id, .. } => format!("test {test_id}"), RunnableKind::TestMod { path } => format!("test-mod {path}"), RunnableKind::Bench { test_id } => format!("bench {test_id}"), RunnableKind::DocTest { test_id, .. } => format!("doctest {test_id}"), - RunnableKind::Bin => { - target.map_or_else(|| "run binary".to_string(), |t| format!("run {t}")) - } + RunnableKind::Bin => target, } } diff --git a/crates/project-model/src/lib.rs b/crates/project-model/src/lib.rs index 5f9b708289d1..2ba90aa38a9a 100644 --- a/crates/project-model/src/lib.rs +++ b/crates/project-model/src/lib.rs @@ -20,7 +20,7 @@ mod manifest_path; mod cargo_workspace; mod cfg_flag; -mod project_json; +pub mod project_json; mod sysroot; mod workspace; mod rustc_cfg; diff --git a/crates/project-model/src/project_json.rs b/crates/project-model/src/project_json.rs index cf3231498f3e..15926af43b2b 100644 --- a/crates/project-model/src/project_json.rs +++ b/crates/project-model/src/project_json.rs @@ -56,7 +56,7 @@ use rustc_hash::FxHashMap; use serde::{de, Deserialize}; use std::path::PathBuf; -use crate::cfg_flag::CfgFlag; +use crate::{cfg_flag::CfgFlag, TargetKind}; /// Roots and crates that compose this Rust project. #[derive(Clone, Debug, Eq, PartialEq)] @@ -73,20 +73,37 @@ pub struct ProjectJson { /// useful in creating the crate graph. #[derive(Clone, Debug, Eq, PartialEq)] pub struct Crate { - pub(crate) display_name: Option, - pub(crate) root_module: AbsPathBuf, - pub(crate) edition: Edition, - pub(crate) version: Option, - pub(crate) deps: Vec, - pub(crate) cfg: Vec, - pub(crate) target: Option, - pub(crate) env: FxHashMap, - pub(crate) proc_macro_dylib_path: Option, - pub(crate) is_workspace_member: bool, - pub(crate) include: Vec, - pub(crate) exclude: Vec, - pub(crate) is_proc_macro: bool, - pub(crate) repository: Option, + pub display_name: Option, + pub root_module: AbsPathBuf, + pub edition: Edition, + pub version: Option, + pub deps: Vec, + pub cfg: Vec, + pub target: Option, + pub env: FxHashMap, + pub proc_macro_dylib_path: Option, + pub is_workspace_member: bool, + pub include: Vec, + pub exclude: Vec, + pub is_proc_macro: bool, + pub repository: Option, + pub target_spec: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct TargetSpec { + pub manifest_file: AbsPathBuf, + pub target_label: String, + pub target_kind: TargetKind, + pub runnables: Runnables, + pub flycheck_command: Vec, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Runnables { + pub check: Vec, + pub run: Vec, + pub test: Vec, } impl ProjectJson { @@ -121,6 +138,20 @@ impl ProjectJson { None => (vec![root_module.parent().unwrap().to_path_buf()], Vec::new()), }; + let target_spec = match crate_data.target_spec { + Some(spec) => { + let spec = TargetSpec { + manifest_file: absolutize_on_base(spec.manifest_file), + target_label: spec.target_label, + target_kind: spec.target_kind.into(), + runnables: spec.runnables.into(), + flycheck_command: spec.flycheck_command, + }; + Some(spec) + } + None => None, + }; + Crate { display_name: crate_data .display_name @@ -150,6 +181,7 @@ impl ProjectJson { exclude, is_proc_macro: crate_data.is_proc_macro, repository: crate_data.repository, + target_spec, } }) .collect(), @@ -173,6 +205,15 @@ impl ProjectJson { pub fn path(&self) -> &AbsPath { &self.project_root } + + pub fn crate_by_root(&self, root: &AbsPath) -> Option { + self.crates + .iter() + .filter(|krate| krate.is_workspace_member) + .find(|krate| &krate.root_module == root) + .map(|krate| krate) + .cloned() + } } #[derive(Deserialize, Debug, Clone)] @@ -202,6 +243,8 @@ struct CrateData { is_proc_macro: bool, #[serde(default)] repository: Option, + #[serde(default)] + target_spec: Option, } #[derive(Deserialize, Debug, Clone)] @@ -217,6 +260,55 @@ enum EditionData { Edition2024, } +#[derive(Deserialize, Debug, Clone)] +pub struct TargetSpecData { + manifest_file: PathBuf, + target_label: String, + target_kind: TargetKindData, + runnables: RunnablesData, + flycheck_command: Vec, +} + +#[derive(Deserialize, Debug, Clone)] +pub struct RunnablesData { + check: Vec, + run: Vec, + test: Vec, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum TargetKindData { + Bin, + /// Any kind of Cargo lib crate-type (dylib, rlib, proc-macro, ...). + Lib, + Example, + Test, + Bench, + BuildScript, + Other, +} + +impl From for TargetKind { + fn from(value: TargetKindData) -> Self { + match value { + TargetKindData::Bin => TargetKind::Bin, + TargetKindData::Lib => TargetKind::Lib, + TargetKindData::Example => TargetKind::Example, + TargetKindData::Test => TargetKind::Test, + TargetKindData::Bench => TargetKind::Bench, + TargetKindData::BuildScript => TargetKind::BuildScript, + TargetKindData::Other => TargetKind::Other, + } + } +} + +impl From for Runnables { + fn from(value: RunnablesData) -> Self { + Runnables { check: value.check, run: value.run, test: value.test } + } +} + impl From for Edition { fn from(data: EditionData) -> Self { match data { diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs index f57a27305f03..5a82ff50c83d 100644 --- a/crates/rust-analyzer/src/global_state.rs +++ b/crates/rust-analyzer/src/global_state.rs @@ -18,7 +18,7 @@ use parking_lot::{ RwLockWriteGuard, }; use proc_macro_api::ProcMacroServer; -use project_model::{CargoWorkspace, ProjectWorkspace, Target, WorkspaceBuildScripts}; +use project_model::{CargoWorkspace, ProjectJson, ProjectWorkspace, Target, WorkspaceBuildScripts}; use rustc_hash::{FxHashMap, FxHashSet}; use triomphe::Arc; use vfs::{AnchoredPathBuf, Vfs}; @@ -478,18 +478,20 @@ impl GlobalStateSnapshot { self.vfs_read().file_path(file_id) } - pub(crate) fn cargo_target_for_crate_root( + pub(crate) fn target_for_crate_root( &self, crate_id: CrateId, - ) -> Option<(&CargoWorkspace, Target)> { + ) -> Option> { let file_id = self.analysis.crate_root(crate_id).ok()?; let path = self.vfs_read().file_path(file_id); let path = path.as_path()?; self.workspaces.iter().find_map(|ws| match ws { ProjectWorkspace::Cargo { cargo, .. } => { - cargo.target_by_root(path).map(|it| (cargo, it)) + cargo.target_by_root(path).map(|it| TargetForCrateRoot::Cargo(cargo, it)) + } + ProjectWorkspace::Json { project, .. } => { + project.crate_by_root(path).map(|it| TargetForCrateRoot::JsonProject(project, it)) } - ProjectWorkspace::Json { .. } => None, ProjectWorkspace::DetachedFiles { .. } => None, }) } @@ -503,6 +505,11 @@ impl GlobalStateSnapshot { } } +pub(crate) enum TargetForCrateRoot<'a> { + Cargo(&'a CargoWorkspace, Target), + JsonProject(&'a ProjectJson, project_model::project_json::Crate), +} + pub(crate) fn file_id_to_url(vfs: &vfs::Vfs, id: FileId) -> Url { let path = vfs.file_path(id); let path = path.as_path().unwrap(); diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs index d8a590c80884..ec9ac75594e7 100644 --- a/crates/rust-analyzer/src/handlers/request.rs +++ b/crates/rust-analyzer/src/handlers/request.rs @@ -34,12 +34,12 @@ use triomphe::Arc; use vfs::{AbsPath, AbsPathBuf, FileId, VfsPath}; use crate::{ - cargo_target_spec::CargoTargetSpec, config::{Config, RustfmtConfig, WorkspaceSymbolConfig}, diff::diff, global_state::{GlobalState, GlobalStateSnapshot}, line_index::LineEndings, lsp::{ + ext::{CargoRunnable, RunnableData}, from_proto, to_proto, utils::{all_edits_are_disjoint, invalid_params_error}, LspError, @@ -48,6 +48,7 @@ use crate::{ self, CrateInfoResult, ExternalDocsPair, ExternalDocsResponse, FetchDependencyListParams, FetchDependencyListResult, PositionOrRange, ViewCrateGraphParams, WorkspaceSymbolParams, }, + target_spec::TargetSpec, }; pub(crate) fn handle_workspace_reload(state: &mut GlobalState, _: ()) -> anyhow::Result<()> { @@ -709,13 +710,13 @@ pub(crate) fn handle_parent_module( Some(&crate_id) => crate_id, None => return Ok(None), }; - let cargo_spec = match CargoTargetSpec::for_file(&snap, file_id)? { + let cargo_spec = match TargetSpec::for_file(&snap, file_id)? { Some(it) => it, None => return Ok(None), }; if snap.analysis.crate_root(crate_id)? == file_id { - let cargo_toml_url = to_proto::url_from_abs_path(&cargo_spec.cargo_toml); + let cargo_toml_url = to_proto::url_from_abs_path(&cargo_spec.project_manifest()); let res = vec![LocationLink { origin_selection_range: None, target_uri: cargo_toml_url, @@ -742,7 +743,7 @@ pub(crate) fn handle_runnables( let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; let line_index = snap.file_line_index(file_id)?; let offset = params.position.and_then(|it| from_proto::offset(&line_index, it).ok()); - let cargo_spec = CargoTargetSpec::for_file(&snap, file_id)?; + let target_spec = TargetSpec::for_file(&snap, file_id)?; let expect_test = match offset { Some(offset) => { @@ -759,45 +760,50 @@ pub(crate) fn handle_runnables( if should_skip_for_offset(&runnable, offset) { continue; } - if should_skip_target(&runnable, cargo_spec.as_ref()) { + if should_skip_target(&runnable, target_spec.as_ref()) { continue; } - let mut runnable = to_proto::runnable(&snap, runnable)?; - if expect_test { - runnable.label = format!("{} + expect", runnable.label); - runnable.args.expect_test = Some(true); + if let Some(mut runnable) = to_proto::runnable(&snap, runnable)? { + if expect_test { + if let lsp_ext::RunnableData::Cargo(r) = &mut runnable.args { + runnable.label = format!("{} + expect", runnable.label); + r.expect_test = Some(true); + } + } + res.push(runnable); } - res.push(runnable); } // Add `cargo check` and `cargo test` for all targets of the whole package let config = snap.config.runnables(); - match cargo_spec { + match target_spec { Some(spec) => { - let all_targets = !snap.analysis.is_crate_no_std(spec.crate_id)?; - for cmd in ["check", "test"] { - let mut cargo_args = - vec![cmd.to_owned(), "--package".to_owned(), spec.package.clone()]; - if all_targets { - cargo_args.push("--all-targets".to_owned()); + if let TargetSpec::Cargo(spec) = spec { + let all_targets = !snap.analysis.is_crate_no_std(spec.crate_id)?; + for cmd in ["check", "test"] { + let mut cargo_args = + vec![cmd.to_owned(), "--package".to_owned(), spec.package.clone()]; + if all_targets { + cargo_args.push("--all-targets".to_owned()); + } + res.push(lsp_ext::Runnable { + label: format!( + "cargo {cmd} -p {}{all_targets}", + spec.package, + all_targets = if all_targets { " --all-targets" } else { "" } + ), + location: None, + kind: lsp_ext::RunnableKind::Cargo, + args: RunnableData::Cargo(CargoRunnable { + workspace_root: Some(spec.workspace_root.clone().into()), + override_cargo: config.override_cargo.clone(), + cargo_args, + cargo_extra_args: config.cargo_extra_args.clone(), + executable_args: Vec::new(), + expect_test: None, + }), + }) } - res.push(lsp_ext::Runnable { - label: format!( - "cargo {cmd} -p {}{all_targets}", - spec.package, - all_targets = if all_targets { " --all-targets" } else { "" } - ), - location: None, - kind: lsp_ext::RunnableKind::Cargo, - args: lsp_ext::CargoRunnable { - workspace_root: Some(spec.workspace_root.clone().into()), - override_cargo: config.override_cargo.clone(), - cargo_args, - cargo_extra_args: config.cargo_extra_args.clone(), - executable_args: Vec::new(), - expect_test: None, - }, - }) } } None => { @@ -806,14 +812,14 @@ pub(crate) fn handle_runnables( label: "cargo check --workspace".to_string(), location: None, kind: lsp_ext::RunnableKind::Cargo, - args: lsp_ext::CargoRunnable { + args: RunnableData::Cargo(lsp_ext::CargoRunnable { workspace_root: None, override_cargo: config.override_cargo, cargo_args: vec!["check".to_string(), "--workspace".to_string()], cargo_extra_args: config.cargo_extra_args, executable_args: Vec::new(), expect_test: None, - }, + }), }); } } @@ -839,7 +845,7 @@ pub(crate) fn handle_related_tests( let tests = snap.analysis.related_tests(position, None)?; let mut res = Vec::new(); for it in tests { - if let Ok(runnable) = to_proto::runnable(&snap, it) { + if let Ok(Some(runnable)) = to_proto::runnable(&snap, it) { res.push(lsp_ext::TestInfo { runnable }) } } @@ -1299,14 +1305,14 @@ pub(crate) fn handle_code_lens( } let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; - let cargo_target_spec = CargoTargetSpec::for_file(&snap, file_id)?; + let cargo_target_spec = TargetSpec::for_file(&snap, file_id)?; let annotations = snap.analysis.annotations( &AnnotationConfig { binary_target: cargo_target_spec .map(|spec| { matches!( - spec.target_kind, + spec.target_kind(), TargetKind::Bin | TargetKind::Example | TargetKind::Test ) }) @@ -1708,12 +1714,12 @@ pub(crate) fn handle_open_cargo_toml( let _p = profile::span("handle_open_cargo_toml"); let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; - let cargo_spec = match CargoTargetSpec::for_file(&snap, file_id)? { + let cargo_spec = match TargetSpec::for_file(&snap, file_id)? { Some(it) => it, None => return Ok(None), }; - let cargo_toml_url = to_proto::url_from_abs_path(&cargo_spec.cargo_toml); + let cargo_toml_url = to_proto::url_from_abs_path(&cargo_spec.project_manifest()); let res: lsp_types::GotoDefinitionResponse = Location::new(cargo_toml_url, Range::default()).into(); Ok(Some(res)) @@ -1838,7 +1844,7 @@ fn runnable_action_links( return None; } - let cargo_spec = CargoTargetSpec::for_file(snap, runnable.nav.file_id).ok()?; + let cargo_spec = TargetSpec::for_file(snap, runnable.nav.file_id).ok()?; if should_skip_target(&runnable, cargo_spec.as_ref()) { return None; } @@ -1849,7 +1855,7 @@ fn runnable_action_links( } let title = runnable.title(); - let r = to_proto::runnable(snap, runnable).ok()?; + let r = to_proto::runnable(snap, runnable).ok()??; let mut group = lsp_ext::CommandLinkGroup::default(); @@ -1904,13 +1910,13 @@ fn prepare_hover_actions( .collect() } -fn should_skip_target(runnable: &Runnable, cargo_spec: Option<&CargoTargetSpec>) -> bool { +fn should_skip_target(runnable: &Runnable, cargo_spec: Option<&TargetSpec>) -> bool { match runnable.kind { RunnableKind::Bin => { // Do not suggest binary run on other target than binary match &cargo_spec { Some(spec) => !matches!( - spec.target_kind, + spec.target_kind(), TargetKind::Bin | TargetKind::Example | TargetKind::Test ), None => true, @@ -1986,7 +1992,7 @@ fn run_rustfmt( } RustfmtConfig::CustomCommand { command, args } => { let cmd = PathBuf::from(&command); - let workspace = CargoTargetSpec::for_file(&snap, file_id)?; + let workspace = TargetSpec::for_file(&snap, file_id)?; let mut cmd = match workspace { Some(spec) => { // approach: if the command name contains a path separator, join it with the workspace root. @@ -1994,9 +2000,9 @@ fn run_rustfmt( // as a fallback, rely on $PATH-based discovery. let cmd_path = if cfg!(windows) && command.contains(&[std::path::MAIN_SEPARATOR, '/']) { - spec.workspace_root.join(cmd).into() + spec.workspace_root().join(cmd).into() } else if command.contains(std::path::MAIN_SEPARATOR) { - spec.workspace_root.join(cmd).into() + spec.workspace_root().join(cmd).into() } else { cmd }; diff --git a/crates/rust-analyzer/src/lib.rs b/crates/rust-analyzer/src/lib.rs index 29bc0b80d8a1..ac382ba50334 100644 --- a/crates/rust-analyzer/src/lib.rs +++ b/crates/rust-analyzer/src/lib.rs @@ -19,7 +19,7 @@ macro_rules! eprintln { } mod caps; -mod cargo_target_spec; +mod target_spec; mod diagnostics; mod diff; mod dispatch; diff --git a/crates/rust-analyzer/src/lsp/ext.rs b/crates/rust-analyzer/src/lsp/ext.rs index ad56899163d3..67c2d1f0a85c 100644 --- a/crates/rust-analyzer/src/lsp/ext.rs +++ b/crates/rust-analyzer/src/lsp/ext.rs @@ -1,5 +1,6 @@ //! rust-analyzer extensions to the LSP. +use std::path::Path; use std::{collections::HashMap, path::PathBuf}; use ide_db::line_index::WideEncoding; @@ -311,13 +312,31 @@ pub struct Runnable { #[serde(skip_serializing_if = "Option::is_none")] pub location: Option, pub kind: RunnableKind, - pub args: CargoRunnable, + pub args: RunnableData, +} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +#[serde(untagged)] +pub enum RunnableData { + Cargo(CargoRunnable), + ProjectJson(ProjectJsonRunnable), +} + +impl RunnableData { + pub fn workspace_root(&self) -> Option<&Path> { + match self { + RunnableData::Cargo(cargo) => cargo.workspace_root.as_deref(), + RunnableData::ProjectJson(project_json) => Some(&project_json.workspace_root), + } + } } #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "lowercase")] pub enum RunnableKind { Cargo, + ProjectJson, } #[derive(Deserialize, Serialize, Debug)] @@ -337,6 +356,14 @@ pub struct CargoRunnable { pub expect_test: Option, } +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct ProjectJsonRunnable { + pub build_command: String, + pub workspace_root: PathBuf, + pub args: Vec, +} + pub enum RelatedTests {} impl Request for RelatedTests { diff --git a/crates/rust-analyzer/src/lsp/to_proto.rs b/crates/rust-analyzer/src/lsp/to_proto.rs index dae560c5de12..39197edf030b 100644 --- a/crates/rust-analyzer/src/lsp/to_proto.rs +++ b/crates/rust-analyzer/src/lsp/to_proto.rs @@ -19,16 +19,17 @@ use serde_json::to_value; use vfs::AbsPath; use crate::{ - cargo_target_spec::CargoTargetSpec, config::{CallInfoConfig, Config}, global_state::GlobalStateSnapshot, line_index::{LineEndings, LineIndex, PositionEncoding}, lsp::{ + ext::ProjectJsonRunnable, semantic_tokens::{self, standard_fallback_type}, utils::invalid_params_error, LspError, }, lsp_ext::{self, SnippetTextEdit}, + target_spec::{CargoTargetSpec, ProjectJsonSpec, TargetSpec}, }; pub(crate) fn position(line_index: &LineIndex, offset: TextSize) -> lsp_types::Position { @@ -1300,29 +1301,52 @@ pub(crate) fn code_action( pub(crate) fn runnable( snap: &GlobalStateSnapshot, runnable: Runnable, -) -> Cancellable { +) -> Cancellable> { let config = snap.config.runnables(); - let spec = CargoTargetSpec::for_file(snap, runnable.nav.file_id)?; - let workspace_root = spec.as_ref().map(|it| it.workspace_root.clone()); - let target = spec.as_ref().map(|s| s.target.clone()); - let (cargo_args, executable_args) = - CargoTargetSpec::runnable_args(snap, spec, &runnable.kind, &runnable.cfg); - let label = runnable.label(target); - let location = location_link(snap, None, runnable.nav)?; - - Ok(lsp_ext::Runnable { - label, - location: Some(location), - kind: lsp_ext::RunnableKind::Cargo, - args: lsp_ext::CargoRunnable { - workspace_root: workspace_root.map(|it| it.into()), - override_cargo: config.override_cargo, - cargo_args, - cargo_extra_args: config.cargo_extra_args, - executable_args, - expect_test: None, - }, - }) + let spec = TargetSpec::for_file(snap, runnable.nav.file_id)?; + let workspace_root = spec.as_ref().map(|it| it.workspace_root().to_owned()); + + match spec { + Some(TargetSpec::Cargo(spec)) => { + let (cargo_args, executable_args) = + CargoTargetSpec::runnable_args(snap, spec.clone(), &runnable.kind, &runnable.cfg); + let label = runnable.label(spec.target.clone()); + let location = location_link(snap, None, runnable.nav)?; + + let runnable = lsp_ext::Runnable { + label, + location: Some(location), + kind: lsp_ext::RunnableKind::Cargo, + args: lsp_ext::RunnableData::Cargo(lsp_ext::CargoRunnable { + workspace_root: workspace_root.map(|it| it.to_owned().into()), + override_cargo: config.override_cargo, + cargo_args, + cargo_extra_args: config.cargo_extra_args, + executable_args, + expect_test: None, + }), + }; + return Ok(Some(runnable)); + } + Some(TargetSpec::ProjectJson(spec)) => { + let label = runnable.label(spec.target_label.clone()); + let location = location_link(snap, None, runnable.nav)?; + let args = ProjectJsonSpec::runnable_args(spec.clone(), &runnable.kind); + + let runnable = lsp_ext::Runnable { + label, + location: Some(location), + kind: lsp_ext::RunnableKind::ProjectJson, + args: lsp_ext::RunnableData::ProjectJson(ProjectJsonRunnable { + build_command: config.override_cargo.unwrap(), + args, + workspace_root: spec.workspace_root.clone().into(), + }), + }; + return Ok(Some(runnable)); + } + None => Ok(None), + } } pub(crate) fn code_lens( @@ -1346,33 +1370,35 @@ pub(crate) fn code_lens( }; let r = runnable(snap, run)?; - let lens_config = snap.config.lens(); - if lens_config.run - && client_commands_config.run_single - && r.args.workspace_root.is_some() - { - let command = command::run_single(&r, &title); - acc.push(lsp_types::CodeLens { - range: annotation_range, - command: Some(command), - data: None, - }) - } - if lens_config.debug && can_debug && client_commands_config.debug_single { - let command = command::debug_single(&r); - acc.push(lsp_types::CodeLens { - range: annotation_range, - command: Some(command), - data: None, - }) - } - if lens_config.interpret { - let command = command::interpret_single(&r); - acc.push(lsp_types::CodeLens { - range: annotation_range, - command: Some(command), - data: None, - }) + if let Some(r) = r { + let lens_config = snap.config.lens(); + if lens_config.run + && client_commands_config.run_single + && r.args.workspace_root().is_some() + { + let command = command::run_single(&r, &title); + acc.push(lsp_types::CodeLens { + range: annotation_range, + command: Some(command), + data: None, + }) + } + if lens_config.debug && can_debug && client_commands_config.debug_single { + let command = command::debug_single(&r); + acc.push(lsp_types::CodeLens { + range: annotation_range, + command: Some(command), + data: None, + }) + } + if lens_config.interpret { + let command = command::interpret_single(&r); + acc.push(lsp_types::CodeLens { + range: annotation_range, + command: Some(command), + data: None, + }) + } } } AnnotationKind::HasImpls { pos, data } => { diff --git a/crates/rust-analyzer/src/cargo_target_spec.rs b/crates/rust-analyzer/src/target_spec.rs similarity index 54% rename from crates/rust-analyzer/src/cargo_target_spec.rs rename to crates/rust-analyzer/src/target_spec.rs index 728bade0d0a5..8aff3f0c3979 100644 --- a/crates/rust-analyzer/src/cargo_target_spec.rs +++ b/crates/rust-analyzer/src/target_spec.rs @@ -1,35 +1,164 @@ //! See `CargoTargetSpec` -use std::mem; - use cfg::{CfgAtom, CfgExpr}; use ide::{Cancellable, CrateId, FileId, RunnableKind, TestId}; use project_model::{self, CargoFeatures, ManifestPath, TargetKind}; use rustc_hash::FxHashSet; -use vfs::AbsPathBuf; +use vfs::{AbsPath, AbsPathBuf}; -use crate::global_state::GlobalStateSnapshot; +use crate::global_state::{GlobalStateSnapshot, TargetForCrateRoot}; /// Abstract representation of Cargo target. /// /// We use it to cook up the set of cli args we need to pass to Cargo to /// build/test/run the target. +#[derive(Clone)] +pub(crate) enum TargetSpec { + Cargo(CargoTargetSpec), + ProjectJson(ProjectJsonSpec), +} + +impl TargetSpec { + pub(crate) fn for_file( + global_state_snapshot: &GlobalStateSnapshot, + file_id: FileId, + ) -> Cancellable> { + let crate_id = match &*global_state_snapshot.analysis.crates_for(file_id)? { + &[crate_id, ..] => crate_id, + _ => return Ok(None), + }; + + match global_state_snapshot.target_for_crate_root(crate_id) { + Some(TargetForCrateRoot::Cargo(cargo_ws, target)) => { + let target_data = &cargo_ws[target]; + let package_data = &cargo_ws[target_data.package]; + let res = CargoTargetSpec { + workspace_root: cargo_ws.workspace_root().to_path_buf(), + project_manifest: package_data.manifest.clone(), + crate_id, + package: cargo_ws.package_flag(package_data), + target: target_data.name.clone(), + target_kind: target_data.kind, + required_features: target_data.required_features.clone(), + features: package_data.features.keys().cloned().collect(), + }; + Ok(Some(TargetSpec::Cargo(res))) + } + Some(TargetForCrateRoot::JsonProject(_, krate)) => match krate.target_spec { + Some(target) => { + let manifest = ManifestPath::try_from(target.manifest_file).unwrap(); + let res = ProjectJsonSpec { + workspace_root: manifest.parent().to_path_buf(), + project_manifest: manifest, + target_kind: target.target_kind, + target_label: target.target_label, + runnables: target.runnables, + }; + Ok(Some(TargetSpec::ProjectJson(res))) + } + None => { + tracing::debug!(?krate, "no target spec"); + return Ok(None); + } + }, + None => { + tracing::debug!(?crate_id, "no target found"); + return Ok(None); + } + } + } + + pub(crate) fn project_manifest(&self) -> &AbsPath { + match self { + TargetSpec::Cargo(cargo) => &cargo.project_manifest, + TargetSpec::ProjectJson(project_json) => &project_json.project_manifest, + } + } + + pub(crate) fn workspace_root(&self) -> &AbsPath { + match self { + TargetSpec::Cargo(cargo) => &cargo.workspace_root, + TargetSpec::ProjectJson(project_json) => &project_json.workspace_root, + } + } + + pub(crate) fn target_kind(&self) -> TargetKind { + match self { + TargetSpec::Cargo(cargo) => cargo.target_kind, + TargetSpec::ProjectJson(project_json) => project_json.target_kind, + } + } +} + #[derive(Clone)] pub(crate) struct CargoTargetSpec { pub(crate) workspace_root: AbsPathBuf, - pub(crate) cargo_toml: ManifestPath, + /// [`project_manifest`] corresponds to the file defining a crate. With Cargo, + /// this will be a Cargo.toml, but with a non-Cargo build system, this might be + /// a `TARGETS` or `BUCK` file. Multiple, distinct crates can share a + /// single `project_manifest`. + pub(crate) project_manifest: ManifestPath, + pub(crate) crate_id: CrateId, pub(crate) package: String, pub(crate) target: String, pub(crate) target_kind: TargetKind, - pub(crate) crate_id: CrateId, pub(crate) required_features: Vec, pub(crate) features: FxHashSet, } +#[derive(Clone)] +pub(crate) struct ProjectJsonSpec { + pub(crate) workspace_root: AbsPathBuf, + /// [`project_manifest`] corresponds to the file defining a crate. With Cargo, + /// this will be a Cargo.toml, but with a non-Cargo build system, this might be + /// a `TARGETS` or `BUCK` file. Multiple, distinct crates can share a + /// single `project_manifest`. + pub(crate) project_manifest: ManifestPath, + pub(crate) target_label: String, + pub(crate) target_kind: TargetKind, + pub(crate) runnables: project_model::project_json::Runnables, +} + +impl ProjectJsonSpec { + pub(crate) fn runnable_args(spec: ProjectJsonSpec, kind: &RunnableKind) -> Vec { + let mut args = Vec::new(); + + match kind { + RunnableKind::Test { test_id, attr: _ } | RunnableKind::DocTest { test_id } => { + let mut test_runnable = spec.runnables.test; + for arg in &mut test_runnable.iter_mut() { + if arg == "{test_id}" { + *arg = test_id.to_string() + } + } + + args.append(&mut test_runnable); + } + RunnableKind::TestMod { path } => { + let mut test_runnable = spec.runnables.test; + for arg in &mut test_runnable.iter_mut() { + if arg == "{test_id}" { + *arg = path.to_string() + } + } + + args.append(&mut test_runnable); + } + RunnableKind::Bin => { + let mut run = spec.runnables.run; + args.append(&mut run); + } + _ => (), + }; + + args + } +} + impl CargoTargetSpec { pub(crate) fn runnable_args( snap: &GlobalStateSnapshot, - spec: Option, + spec: CargoTargetSpec, kind: &RunnableKind, cfg: &Option, ) -> (Vec, Vec) { @@ -68,22 +197,18 @@ impl CargoTargetSpec { extra_args.push("--nocapture".to_owned()); } RunnableKind::Bin => { - let subcommand = match spec { - Some(CargoTargetSpec { target_kind: TargetKind::Test, .. }) => "test", + let subcommand = match spec.target_kind { + TargetKind::Test => "test", _ => "run", }; args.push(subcommand.to_owned()); } } - let (allowed_features, target_required_features) = if let Some(mut spec) = spec { - let allowed_features = mem::take(&mut spec.features); - let required_features = mem::take(&mut spec.required_features); - spec.push_to(&mut args, kind); - (allowed_features, required_features) - } else { - (Default::default(), Default::default()) - }; + let allowed_features = spec.features.clone(); + let target_required_features = spec.required_features.clone(); + + spec.clone().push_to(&mut args, kind); let cargo_config = snap.config.cargo(); @@ -120,59 +245,32 @@ impl CargoTargetSpec { (args, extra_args) } - pub(crate) fn for_file( - global_state_snapshot: &GlobalStateSnapshot, - file_id: FileId, - ) -> Cancellable> { - let crate_id = match &*global_state_snapshot.analysis.crates_for(file_id)? { - &[crate_id, ..] => crate_id, - _ => return Ok(None), - }; - let (cargo_ws, target) = match global_state_snapshot.cargo_target_for_crate_root(crate_id) { - Some(it) => it, - None => return Ok(None), - }; - - let target_data = &cargo_ws[target]; - let package_data = &cargo_ws[target_data.package]; - let res = CargoTargetSpec { - workspace_root: cargo_ws.workspace_root().to_path_buf(), - cargo_toml: package_data.manifest.clone(), - package: cargo_ws.package_flag(package_data), - target: target_data.name.clone(), - target_kind: target_data.kind, - required_features: target_data.required_features.clone(), - features: package_data.features.keys().cloned().collect(), - crate_id, - }; - - Ok(Some(res)) - } - pub(crate) fn push_to(self, buf: &mut Vec, kind: &RunnableKind) { + let (package, target, target_kind) = (self.package, self.target, self.target_kind); + buf.push("--package".to_owned()); - buf.push(self.package); + buf.push(package); // Can't mix --doc with other target flags if let RunnableKind::DocTest { .. } = kind { return; } - match self.target_kind { + match target_kind { TargetKind::Bin => { buf.push("--bin".to_owned()); - buf.push(self.target); + buf.push(target); } TargetKind::Test => { buf.push("--test".to_owned()); - buf.push(self.target); + buf.push(target); } TargetKind::Bench => { buf.push("--bench".to_owned()); - buf.push(self.target); + buf.push(target); } TargetKind::Example => { buf.push("--example".to_owned()); - buf.push(self.target); + buf.push(target); } TargetKind::Lib => { buf.push("--lib".to_owned()); diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md index b66c9c943a16..b9afd43c8056 100644 --- a/docs/dev/lsp-extensions.md +++ b/docs/dev/lsp-extensions.md @@ -1,5 +1,5 @@