Define workspace level configs.

This commit is contained in:
Ali Bektas 2024-08-17 00:57:01 +02:00
parent b02c214132
commit d51fd9f196
4 changed files with 148 additions and 96 deletions

View file

@ -436,6 +436,13 @@ config_data! {
}
}
config_data! {
workspace: struct WorkspaceDefaultConfigData <- WorkspaceConfigInput -> {
}
}
config_data! {
/// Configs that only make sense when they are set by a client. As such they can only be defined
/// by setting them using client's settings (e.g `settings.json` on VS Code).
@ -738,7 +745,7 @@ pub enum RatomlFileKind {
// FIXME @alibektas : Seems like a clippy warning of this sort should tell that combining different ConfigInputs into one enum was not a good idea.
#[allow(clippy::large_enum_variant)]
enum RatomlFile {
Workspace(GlobalLocalConfigInput),
Workspace(WorkspaceLocalConfigInput),
Crate(LocalConfigInput),
}
@ -962,7 +969,7 @@ impl Config {
source_root_id,
(
RatomlFile::Workspace(
GlobalLocalConfigInput::from_toml(
WorkspaceLocalConfigInput::from_toml(
table,
&mut toml_errors,
),
@ -1000,7 +1007,7 @@ impl Config {
config.source_root_parent_map = source_root_map;
}
if config.check_command(None).is_empty() {
if config.check_command().is_empty() {
config.validation_errors.0.push(Arc::new(ConfigErrorInner::Json {
config_key: "/check/command".to_owned(),
error: serde_json::Error::custom("expected a non-empty string"),
@ -1444,11 +1451,11 @@ impl Config {
pub fn diagnostics(&self, source_root: Option<SourceRootId>) -> DiagnosticsConfig {
DiagnosticsConfig {
enabled: *self.diagnostics_enable(source_root),
enabled: *self.diagnostics_enable(),
proc_attr_macros_enabled: self.expand_proc_attr_macros(),
proc_macros_enabled: *self.procMacro_enable(),
disable_experimental: !self.diagnostics_experimental_enable(source_root),
disabled: self.diagnostics_disabled(source_root).clone(),
disable_experimental: !self.diagnostics_experimental_enable(),
disabled: self.diagnostics_disabled().clone(),
expr_fill_default: match self.assist_expressionFillDefault(source_root) {
ExprFillDefaultDef::Todo => ExprFillDefaultMode::Todo,
ExprFillDefaultDef::Default => ExprFillDefaultMode::Default,
@ -1458,7 +1465,7 @@ impl Config {
prefer_no_std: self.imports_preferNoStd(source_root).to_owned(),
prefer_prelude: self.imports_preferPrelude(source_root).to_owned(),
prefer_absolute: self.imports_prefixExternPrelude(source_root).to_owned(),
style_lints: self.diagnostics_styleLints_enable(source_root).to_owned(),
style_lints: self.diagnostics_styleLints_enable().to_owned(),
term_search_fuel: self.assist_termSearch_fuel(source_root).to_owned() as u64,
term_search_borrowck: self.assist_termSearch_borrowcheck(source_root).to_owned(),
}
@ -1651,11 +1658,11 @@ impl Config {
}
pub fn has_linked_projects(&self) -> bool {
!self.linkedProjects(None).is_empty()
!self.linkedProjects().is_empty()
}
pub fn linked_manifests(&self) -> impl Iterator<Item = &Utf8Path> + '_ {
self.linkedProjects(None).iter().filter_map(|it| match it {
self.linkedProjects().iter().filter_map(|it| match it {
ManifestOrProjectJson::Manifest(p) => Some(&**p),
// despite having a buildfile, using this variant as a manifest
// will fail.
@ -1665,20 +1672,20 @@ impl Config {
}
pub fn has_linked_project_jsons(&self) -> bool {
self.linkedProjects(None)
self.linkedProjects()
.iter()
.any(|it| matches!(it, ManifestOrProjectJson::ProjectJson { .. }))
}
pub fn discover_workspace_config(&self) -> Option<&DiscoverWorkspaceConfig> {
self.workspace_discoverConfig(None).as_ref()
self.workspace_discoverConfig().as_ref()
}
pub fn linked_or_discovered_projects(&self) -> Vec<LinkedProject> {
match self.linkedProjects(None).as_slice() {
match self.linkedProjects().as_slice() {
[] => {
let exclude_dirs: Vec<_> =
self.files_excludeDirs(None).iter().map(|p| self.root_path.join(p)).collect();
self.files_excludeDirs().iter().map(|p| self.root_path.join(p)).collect();
self.discovered_projects
.iter()
.filter(|project| {
@ -1713,48 +1720,48 @@ impl Config {
}
pub fn prefill_caches(&self) -> bool {
self.cachePriming_enable(None).to_owned()
self.cachePriming_enable().to_owned()
}
pub fn publish_diagnostics(&self) -> bool {
self.diagnostics_enable(None).to_owned()
self.diagnostics_enable().to_owned()
}
pub fn diagnostics_map(&self) -> DiagnosticsMapConfig {
DiagnosticsMapConfig {
remap_prefix: self.diagnostics_remapPrefix(None).clone(),
warnings_as_info: self.diagnostics_warningsAsInfo(None).clone(),
warnings_as_hint: self.diagnostics_warningsAsHint(None).clone(),
check_ignore: self.check_ignore(None).clone(),
remap_prefix: self.diagnostics_remapPrefix().clone(),
warnings_as_info: self.diagnostics_warningsAsInfo().clone(),
warnings_as_hint: self.diagnostics_warningsAsHint().clone(),
check_ignore: self.check_ignore().clone(),
}
}
pub fn extra_args(&self) -> &Vec<String> {
self.cargo_extraArgs(None)
self.cargo_extraArgs()
}
pub fn extra_env(&self) -> &FxHashMap<String, String> {
self.cargo_extraEnv(None)
self.cargo_extraEnv()
}
pub fn check_extra_args(&self) -> Vec<String> {
let mut extra_args = self.extra_args().clone();
extra_args.extend_from_slice(self.check_extraArgs(None));
extra_args.extend_from_slice(self.check_extraArgs());
extra_args
}
pub fn check_extra_env(&self) -> FxHashMap<String, String> {
let mut extra_env = self.cargo_extraEnv(None).clone();
extra_env.extend(self.check_extraEnv(None).clone());
let mut extra_env = self.cargo_extraEnv().clone();
extra_env.extend(self.check_extraEnv().clone());
extra_env
}
pub fn lru_parse_query_capacity(&self) -> Option<u16> {
self.lru_capacity(None).to_owned()
self.lru_capacity().to_owned()
}
pub fn lru_query_capacities_config(&self) -> Option<&FxHashMap<Box<str>, u16>> {
self.lru_query_capacities(None).is_empty().not().then(|| self.lru_query_capacities(None))
self.lru_query_capacities().is_empty().not().then(|| self.lru_query_capacities())
}
pub fn proc_macro_srv(&self) -> Option<AbsPathBuf> {
@ -1763,7 +1770,7 @@ impl Config {
}
pub fn ignored_proc_macros(&self) -> &FxHashMap<Box<str>, Box<[Box<str>]>> {
self.procMacro_ignored(None)
self.procMacro_ignored()
}
pub fn expand_proc_macros(&self) -> bool {
@ -1778,11 +1785,7 @@ impl Config {
}
_ => FilesWatcher::Server,
},
exclude: self
.files_excludeDirs(None)
.iter()
.map(|it| self.root_path.join(it))
.collect(),
exclude: self.files_excludeDirs().iter().map(|it| self.root_path.join(it)).collect(),
}
}
@ -1793,22 +1796,22 @@ impl Config {
}
pub fn cargo_autoreload_config(&self) -> bool {
self.cargo_autoreload(None).to_owned()
self.cargo_autoreload().to_owned()
}
pub fn run_build_scripts(&self) -> bool {
self.cargo_buildScripts_enable(None).to_owned() || self.procMacro_enable().to_owned()
self.cargo_buildScripts_enable().to_owned() || self.procMacro_enable().to_owned()
}
pub fn cargo(&self) -> CargoConfig {
let rustc_source = self.rustc_source(None).as_ref().map(|rustc_src| {
let rustc_source = self.rustc_source().as_ref().map(|rustc_src| {
if rustc_src == "discover" {
RustLibSource::Discover
} else {
RustLibSource::Path(self.root_path.join(rustc_src))
}
});
let sysroot = self.cargo_sysroot(None).as_ref().map(|sysroot| {
let sysroot = self.cargo_sysroot().as_ref().map(|sysroot| {
if sysroot == "discover" {
RustLibSource::Discover
} else {
@ -1816,24 +1819,24 @@ impl Config {
}
});
let sysroot_src =
self.cargo_sysrootSrc(None).as_ref().map(|sysroot| self.root_path.join(sysroot));
self.cargo_sysrootSrc().as_ref().map(|sysroot| self.root_path.join(sysroot));
CargoConfig {
all_targets: *self.cargo_allTargets(None),
features: match &self.cargo_features(None) {
all_targets: *self.cargo_allTargets(),
features: match &self.cargo_features() {
CargoFeaturesDef::All => CargoFeatures::All,
CargoFeaturesDef::Selected(features) => CargoFeatures::Selected {
features: features.clone(),
no_default_features: self.cargo_noDefaultFeatures(None).to_owned(),
no_default_features: self.cargo_noDefaultFeatures().to_owned(),
},
},
target: self.cargo_target(None).clone(),
target: self.cargo_target().clone(),
sysroot,
sysroot_src,
rustc_source,
cfg_overrides: project_model::CfgOverrides {
global: CfgDiff::new(
self.cargo_cfgs(None)
self.cargo_cfgs()
.iter()
.map(|(key, val)| match val {
Some(val) => CfgAtom::KeyValue {
@ -1848,43 +1851,43 @@ impl Config {
.unwrap(),
selective: Default::default(),
},
wrap_rustc_in_build_scripts: *self.cargo_buildScripts_useRustcWrapper(None),
invocation_strategy: match self.cargo_buildScripts_invocationStrategy(None) {
wrap_rustc_in_build_scripts: *self.cargo_buildScripts_useRustcWrapper(),
invocation_strategy: match self.cargo_buildScripts_invocationStrategy() {
InvocationStrategy::Once => project_model::InvocationStrategy::Once,
InvocationStrategy::PerWorkspace => project_model::InvocationStrategy::PerWorkspace,
},
run_build_script_command: self.cargo_buildScripts_overrideCommand(None).clone(),
extra_args: self.cargo_extraArgs(None).clone(),
extra_env: self.cargo_extraEnv(None).clone(),
run_build_script_command: self.cargo_buildScripts_overrideCommand().clone(),
extra_args: self.cargo_extraArgs().clone(),
extra_env: self.cargo_extraEnv().clone(),
target_dir: self.target_dir_from_config(),
}
}
pub fn rustfmt(&self, source_root_id: Option<SourceRootId>) -> RustfmtConfig {
match &self.rustfmt_overrideCommand(source_root_id) {
pub fn rustfmt(&self) -> RustfmtConfig {
match &self.rustfmt_overrideCommand() {
Some(args) if !args.is_empty() => {
let mut args = args.clone();
let command = args.remove(0);
RustfmtConfig::CustomCommand { command, args }
}
Some(_) | None => RustfmtConfig::Rustfmt {
extra_args: self.rustfmt_extraArgs(source_root_id).clone(),
enable_range_formatting: *self.rustfmt_rangeFormatting_enable(source_root_id),
extra_args: self.rustfmt_extraArgs().clone(),
enable_range_formatting: *self.rustfmt_rangeFormatting_enable(),
},
}
}
pub fn flycheck_workspace(&self) -> bool {
*self.check_workspace(None)
*self.check_workspace()
}
pub(crate) fn cargo_test_options(&self) -> CargoOptions {
CargoOptions {
target_triples: self.cargo_target(None).clone().into_iter().collect(),
target_triples: self.cargo_target().clone().into_iter().collect(),
all_targets: false,
no_default_features: *self.cargo_noDefaultFeatures(None),
all_features: matches!(self.cargo_features(None), CargoFeaturesDef::All),
features: match self.cargo_features(None).clone() {
no_default_features: *self.cargo_noDefaultFeatures(),
all_features: matches!(self.cargo_features(), CargoFeaturesDef::All),
features: match self.cargo_features().clone() {
CargoFeaturesDef::All => vec![],
CargoFeaturesDef::Selected(it) => it,
},
@ -1895,7 +1898,7 @@ impl Config {
}
pub(crate) fn flycheck(&self) -> FlycheckConfig {
match &self.check_overrideCommand(None) {
match &self.check_overrideCommand() {
Some(args) if !args.is_empty() => {
let mut args = args.clone();
let command = args.remove(0);
@ -1903,7 +1906,7 @@ impl Config {
command,
args,
extra_env: self.check_extra_env(),
invocation_strategy: match self.check_invocationStrategy(None) {
invocation_strategy: match self.check_invocationStrategy() {
InvocationStrategy::Once => crate::flycheck::InvocationStrategy::Once,
InvocationStrategy::PerWorkspace => {
crate::flycheck::InvocationStrategy::PerWorkspace
@ -1912,30 +1915,28 @@ impl Config {
}
}
Some(_) | None => FlycheckConfig::CargoCommand {
command: self.check_command(None).clone(),
command: self.check_command().clone(),
options: CargoOptions {
target_triples: self
.check_targets(None)
.check_targets()
.clone()
.and_then(|targets| match &targets.0[..] {
[] => None,
targets => Some(targets.into()),
})
.unwrap_or_else(|| self.cargo_target(None).clone().into_iter().collect()),
all_targets: self
.check_allTargets(None)
.unwrap_or(*self.cargo_allTargets(None)),
.unwrap_or_else(|| self.cargo_target().clone().into_iter().collect()),
all_targets: self.check_allTargets().unwrap_or(*self.cargo_allTargets()),
no_default_features: self
.check_noDefaultFeatures(None)
.unwrap_or(*self.cargo_noDefaultFeatures(None)),
.check_noDefaultFeatures()
.unwrap_or(*self.cargo_noDefaultFeatures()),
all_features: matches!(
self.check_features(None).as_ref().unwrap_or(self.cargo_features(None)),
self.check_features().as_ref().unwrap_or(self.cargo_features()),
CargoFeaturesDef::All
),
features: match self
.check_features(None)
.check_features()
.clone()
.unwrap_or_else(|| self.cargo_features(None).clone())
.unwrap_or_else(|| self.cargo_features().clone())
{
CargoFeaturesDef::All => vec![],
CargoFeaturesDef::Selected(it) => it,
@ -1950,7 +1951,7 @@ impl Config {
}
fn target_dir_from_config(&self) -> Option<Utf8PathBuf> {
self.cargo_targetDir(None).as_ref().and_then(|target_dir| match target_dir {
self.cargo_targetDir().as_ref().and_then(|target_dir| match target_dir {
TargetDirectory::UseSubdirectory(true) => {
Some(Utf8PathBuf::from("target/rust-analyzer"))
}
@ -1961,18 +1962,18 @@ impl Config {
}
pub fn check_on_save(&self) -> bool {
*self.checkOnSave(None)
*self.checkOnSave()
}
pub fn script_rebuild_on_save(&self) -> bool {
*self.cargo_buildScripts_rebuildOnSave(None)
*self.cargo_buildScripts_rebuildOnSave()
}
pub fn runnables(&self) -> RunnablesConfig {
RunnablesConfig {
override_cargo: self.runnables_command(None).clone(),
cargo_extra_args: self.runnables_extraArgs(None).clone(),
extra_test_binary_args: self.runnables_extraTestBinaryArgs(None).clone(),
override_cargo: self.runnables_command().clone(),
cargo_extra_args: self.runnables_extraArgs().clone(),
extra_test_binary_args: self.runnables_extraTestBinaryArgs().clone(),
}
}
@ -2043,7 +2044,7 @@ impl Config {
}
pub fn prime_caches_num_threads(&self) -> usize {
match self.cachePriming_numThreads(None) {
match self.cachePriming_numThreads() {
NumThreads::Concrete(0) | NumThreads::Physical => num_cpus::get_physical(),
&NumThreads::Concrete(n) => n,
NumThreads::Logical => num_cpus::get(),
@ -2542,6 +2543,45 @@ macro_rules! _impl_for_config_data {
)*
}
};
(workspace, $(
$(#[doc=$doc:literal])*
$vis:vis $field:ident : $ty:ty = $default:expr,
)*
) => {
impl Config {
$(
$($doc)*
#[allow(non_snake_case)]
$vis fn $field(&self, source_root: Option<SourceRootId>) -> &$ty {
let mut source_root = source_root.as_ref();
while let Some(sr) = source_root {
if let Some((file, _)) = self.ratoml_file.get(&sr) {
match file {
RatomlFile::Workspace(config) => {
if let Some(v) = config.workspace.$field.as_ref() {
return &v;
}
},
}
}
source_root = self.source_root_parent_map.get(&sr);
}
if let Some(v) = self.client_config.0.local.$field.as_ref() {
return &v;
}
if let Some((user_config, _)) = self.user_config.as_ref() {
if let Some(v) = user_config.local.$field.as_ref() {
return &v;
}
}
&self.default_config.workspace.$field
}
)*
}
};
(global, $(
$(#[doc=$doc:literal])*
$vis:vis $field:ident : $ty:ty = $default:expr,
@ -2551,18 +2591,8 @@ macro_rules! _impl_for_config_data {
$(
$($doc)*
#[allow(non_snake_case)]
$vis fn $field(&self, source_root : Option<SourceRootId>) -> &$ty {
let mut source_root = source_root.as_ref();
while let Some(sr) = source_root {
if let Some((RatomlFile::Workspace(config), _)) = self.ratoml_file.get(&sr) {
if let Some(v) = config.global.$field.as_ref() {
return &v;
}
}
source_root = self.source_root_parent_map.get(&sr);
}
// TODO Remove source_root
$vis fn $field(&self) -> &$ty {
if let Some(v) = self.client_config.0.global.$field.as_ref() {
return &v;
}
@ -2772,6 +2802,28 @@ impl GlobalLocalConfigInput {
}
}
/// All of the config levels, all fields `Option<T>`, to describe fields that are actually set by
/// some rust-analyzer.toml file or JSON blob. An empty rust-analyzer.toml corresponds to
/// all fields being None.
#[derive(Debug, Clone, Default)]
#[allow(dead_code)]
struct WorkspaceLocalConfigInput {
workspace: WorkspaceConfigInput,
local: LocalConfigInput,
}
impl WorkspaceLocalConfigInput {
#[allow(dead_code)]
const FIELDS: &'static [&'static [&'static str]] =
&[WorkspaceConfigInput::FIELDS, LocalConfigInput::FIELDS];
fn from_toml(toml: toml::Table, error_sink: &mut Vec<(String, toml::de::Error)>) -> Self {
Self {
workspace: WorkspaceConfigInput::from_toml(&toml, error_sink),
local: LocalConfigInput::from_toml(&toml, error_sink),
}
}
}
fn get_field_json<T: DeserializeOwned>(
json: &mut serde_json::Value,
error_sink: &mut Vec<(String, serde_json::Error)>,
@ -3475,7 +3527,7 @@ mod tests {
}));
(config, _, _) = config.apply_change(change);
assert_eq!(config.cargo_targetDir(None), &None);
assert_eq!(config.cargo_targetDir(), &None);
assert!(
matches!(config.flycheck(), FlycheckConfig::CargoCommand { options, .. } if options.target_dir.is_none())
);
@ -3493,7 +3545,7 @@ mod tests {
(config, _, _) = config.apply_change(change);
assert_eq!(config.cargo_targetDir(None), &Some(TargetDirectory::UseSubdirectory(true)));
assert_eq!(config.cargo_targetDir(), &Some(TargetDirectory::UseSubdirectory(true)));
assert!(
matches!(config.flycheck(), FlycheckConfig::CargoCommand { options, .. } if options.target_dir == Some(Utf8PathBuf::from("target/rust-analyzer")))
);
@ -3512,7 +3564,7 @@ mod tests {
(config, _, _) = config.apply_change(change);
assert_eq!(
config.cargo_targetDir(None),
config.cargo_targetDir(),
&Some(TargetDirectory::Directory(Utf8PathBuf::from("other_folder")))
);
assert!(

View file

@ -2113,9 +2113,8 @@ fn run_rustfmt(
let edition = editions.iter().copied().max();
let line_index = snap.file_line_index(file_id)?;
let sr = snap.analysis.source_root_id(file_id)?;
let mut command = match snap.config.rustfmt(Some(sr)) {
let mut command = match snap.config.rustfmt() {
RustfmtConfig::Rustfmt { extra_args, enable_range_formatting } => {
// FIXME: Set RUSTUP_TOOLCHAIN
let mut cmd = process::Command::new(toolchain::Tool::Rustfmt.path());
@ -2303,8 +2302,9 @@ pub(crate) fn internal_testing_fetch_config(
.transpose()?;
serde_json::to_value(match &*params.config {
"local" => state.config.assist(source_root).assist_emit_must_use,
// TODO Most probably will fail because it was not renamed to workspace
"global" => matches!(
state.config.rustfmt(source_root),
state.config.rustfmt(),
RustfmtConfig::Rustfmt { enable_range_formatting: true, .. }
),
_ => return Err(anyhow::anyhow!("Unknown test config key: {}", params.config)),

View file

@ -67,7 +67,7 @@ pub fn server_capabilities(config: &Config) -> ServerCapabilities {
code_action_provider: Some(config.caps().code_action_capabilities()),
code_lens_provider: Some(CodeLensOptions { resolve_provider: Some(true) }),
document_formatting_provider: Some(OneOf::Left(true)),
document_range_formatting_provider: match config.rustfmt(None) {
document_range_formatting_provider: match config.rustfmt() {
RustfmtConfig::Rustfmt { enable_range_formatting: true, .. } => Some(OneOf::Left(true)),
_ => Some(OneOf::Left(false)),
},

View file

@ -122,7 +122,7 @@ impl GlobalState {
};
let mut message = String::new();
if !self.config.cargo_autoreload(None)
if !self.config.cargo_autoreload()
&& self.is_quiescent()
&& self.fetch_workspaces_queue.op_requested()
&& self.config.discover_workspace_config().is_none()