SnippetCap,
};
use itertools::Itertools;
-use lsp_types::{ClientCapabilities, MarkupKind};
+use lsp_types::{ClientCapabilities, ClientInfo, MarkupKind};
use project_model::{
CargoConfig, CargoFeatures, ProjectJson, ProjectJsonData, ProjectManifest, RustcSource,
UnsetTestCrates,
/// Unsets `#[cfg(test)]` for the specified crates.
cargo_unsetTest: Vec<String> = "[\"core\"]",
+ /// Run the check command for diagnostics on save.
+ checkOnSave | checkOnSave_enable: bool = "true",
+
/// Check all targets and tests (`--all-targets`).
- checkOnSave_allTargets: bool = "true",
+ check_allTargets | checkOnSave_allTargets: bool = "true",
/// Cargo command to use for `cargo check`.
- checkOnSave_command: String = "\"check\"",
- /// Run specified `cargo check` command for diagnostics on save.
- checkOnSave_enable: bool = "true",
+ check_command | checkOnSave_command: String = "\"check\"",
/// Extra arguments for `cargo check`.
- checkOnSave_extraArgs: Vec<String> = "[]",
+ check_extraArgs | checkOnSave_extraArgs: Vec<String> = "[]",
/// Extra environment variables that will be set when running `cargo check`.
/// Extends `#rust-analyzer.cargo.extraEnv#`.
- checkOnSave_extraEnv: FxHashMap<String, String> = "{}",
+ check_extraEnv | checkOnSave_extraEnv: FxHashMap<String, String> = "{}",
/// List of features to activate. Defaults to
/// `#rust-analyzer.cargo.features#`.
///
/// Set to `"all"` to pass `--all-features` to Cargo.
- checkOnSave_features: Option<CargoFeaturesDef> = "null",
+ check_features | checkOnSave_features: Option<CargoFeaturesDef> = "null",
/// Specifies the working directory for running checks.
/// - "workspace": run checks for workspaces in the corresponding workspaces' root directories.
// FIXME: Ideally we would support this in some way
/// - "root": run checks in the project's root directory.
/// This config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#`
/// is set.
- checkOnSave_invocationLocation: InvocationLocation = "\"workspace\"",
+ check_invocationLocation | checkOnSave_invocationLocation: InvocationLocation = "\"workspace\"",
/// Specifies the invocation strategy to use when running the checkOnSave command.
/// If `per_workspace` is set, the command will be executed for each workspace.
/// If `once` is set, the command will be executed once.
/// This config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#`
/// is set.
- checkOnSave_invocationStrategy: InvocationStrategy = "\"per_workspace\"",
+ check_invocationStrategy | checkOnSave_invocationStrategy: InvocationStrategy = "\"per_workspace\"",
/// Whether to pass `--no-default-features` to Cargo. Defaults to
/// `#rust-analyzer.cargo.noDefaultFeatures#`.
- checkOnSave_noDefaultFeatures: Option<bool> = "null",
+ check_noDefaultFeatures | checkOnSave_noDefaultFeatures: Option<bool> = "null",
/// Override the command rust-analyzer uses instead of `cargo check` for
/// diagnostics on save. The command is required to output json and
- /// should therefore include `--message-format=json` or a similar option.
+ /// should therefore include `--message-format=json` or a similar option
+ /// (if your client supports the `colorDiagnosticOutput` experimental
+ /// capability, you can use `--message-format=json-diagnostic-rendered-ansi`).
///
/// If you're changing this because you're using some tool wrapping
/// Cargo, you might also want to change
/// cargo check --workspace --message-format=json --all-targets
/// ```
/// .
- checkOnSave_overrideCommand: Option<Vec<String>> = "null",
+ check_overrideCommand | checkOnSave_overrideCommand: Option<Vec<String>> = "null",
/// Check for specific targets. Defaults to `#rust-analyzer.cargo.target#` if empty.
///
/// Can be a single target, e.g. `"x86_64-unknown-linux-gnu"` or a list of targets, e.g.
/// `["aarch64-apple-darwin", "x86_64-apple-darwin"]`.
///
/// Aliased as `"checkOnSave.targets"`.
- checkOnSave_target | checkOnSave_targets: CheckOnSaveTargets = "[]",
+ check_targets | checkOnSave_targets | checkOnSave_target: Option<CheckOnSaveTargets> = "null",
/// Toggles the additional completions that automatically add imports when completed.
/// Note that your client must specify the `additionalTextEdits` LSP client capability to truly have this feature enabled.
inlayHints_closingBraceHints_minLines: usize = "25",
/// Whether to show inlay type hints for return types of closures.
inlayHints_closureReturnTypeHints_enable: ClosureReturnTypeHintsDef = "\"never\"",
+ /// Whether to show enum variant discriminant hints.
+ inlayHints_discriminantHints_enable: DiscriminantHintsDef = "\"never\"",
/// Whether to show inlay hints for type adjustments.
inlayHints_expressionAdjustmentHints_enable: AdjustmentHintsDef = "\"never\"",
+ /// Whether to hide inlay hints for type adjustments outside of `unsafe` blocks.
+ inlayHints_expressionAdjustmentHints_hideOutsideUnsafe: bool = "false",
+ /// Whether to show inlay hints as postfix ops (`.*` instead of `*`, etc).
+ inlayHints_expressionAdjustmentHints_mode: AdjustmentHintsModeDef = "\"prefix\"",
/// Whether to show inlay type hints for elided lifetimes in function signatures.
inlayHints_lifetimeElisionHints_enable: LifetimeElisionDef = "\"never\"",
/// Whether to prefer using parameter names as the name for elided lifetime hints if possible.
inlayHints_lifetimeElisionHints_useParameterNames: bool = "false",
+ /// Whether to use location links for parts of type mentioned in inlay hints.
+ inlayHints_locationLinks: bool = "true",
/// Maximum length for inlay hints. Set to null to have an unlimited length.
inlayHints_maxLength: Option<usize> = "25",
/// Whether to show function parameter name inlay hints at the call
/// Whether to show `can't find Cargo.toml` error message.
notifications_cargoTomlNotFound: bool = "true",
+ /// How many worker threads in the main loop. The default `null` means to pick automatically.
+ numThreads: Option<usize> = "null",
+
/// Expand attribute macros. Requires `#rust-analyzer.procMacro.enable#` to be set.
procMacro_attributes_enable: bool = "true",
/// Enable support for procedural macros, implies `#rust-analyzer.cargo.buildScripts.enable#`.
}
}
+ pub fn client_specific_adjustments(&mut self, client_info: &Option<ClientInfo>) {
+ // FIXME: remove this when we drop support for vscode 1.65 and below
+ if let Some(client) = client_info {
+ if client.name.contains("Code") || client.name.contains("Codium") {
+ if let Some(version) = &client.version {
+ if version.as_str() < "1.76" {
+ self.data.inlayHints_locationLinks = false;
+ }
+ }
+ }
+ }
+ }
+
pub fn update(&mut self, mut json: serde_json::Value) -> Result<(), ConfigUpdateError> {
tracing::info!("updating config from JSON: {:#}", json);
if json.is_null() || json.as_object().map_or(false, |it| it.is_empty()) {
fn validate(&self, error_sink: &mut Vec<(String, serde_json::Error)>) {
use serde::de::Error;
- if self.data.checkOnSave_command.is_empty() {
+ if self.data.check_command.is_empty() {
error_sink.push((
- "/checkOnSave/command".to_string(),
+ "/check/command".to_string(),
serde_json::Error::custom("expected a non-empty string"),
));
}
self.experimental("serverStatusNotification")
}
+ /// Whether the client supports colored output for full diagnostics from `checkOnSave`.
+ pub fn color_diagnostic_output(&self) -> bool {
+ self.experimental("colorDiagnosticOutput")
+ }
+
pub fn publish_diagnostics(&self) -> bool {
self.data.diagnostics_enable
}
pub fn check_on_save_extra_env(&self) -> FxHashMap<String, String> {
let mut extra_env = self.data.cargo_extraEnv.clone();
- extra_env.extend(self.data.checkOnSave_extraEnv.clone());
+ extra_env.extend(self.data.check_extraEnv.clone());
extra_env
}
}
}
- pub fn flycheck(&self) -> Option<FlycheckConfig> {
- if !self.data.checkOnSave_enable {
- return None;
- }
- let flycheck_config = match &self.data.checkOnSave_overrideCommand {
+ pub fn flycheck(&self) -> FlycheckConfig {
+ match &self.data.check_overrideCommand {
Some(args) if !args.is_empty() => {
let mut args = args.clone();
let command = args.remove(0);
command,
args,
extra_env: self.check_on_save_extra_env(),
- invocation_strategy: match self.data.checkOnSave_invocationStrategy {
+ invocation_strategy: match self.data.check_invocationStrategy {
InvocationStrategy::Once => flycheck::InvocationStrategy::Once,
InvocationStrategy::PerWorkspace => {
flycheck::InvocationStrategy::PerWorkspace
}
},
- invocation_location: match self.data.checkOnSave_invocationLocation {
+ invocation_location: match self.data.check_invocationLocation {
InvocationLocation::Root => {
flycheck::InvocationLocation::Root(self.root_path.clone())
}
}
}
Some(_) | None => FlycheckConfig::CargoCommand {
- command: self.data.checkOnSave_command.clone(),
- target_triples: match &self.data.checkOnSave_target.0[..] {
- [] => self.data.cargo_target.clone().into_iter().collect(),
- targets => targets.into(),
- },
- all_targets: self.data.checkOnSave_allTargets,
+ command: self.data.check_command.clone(),
+ target_triples: self
+ .data
+ .check_targets
+ .clone()
+ .and_then(|targets| match &targets.0[..] {
+ [] => None,
+ targets => Some(targets.into()),
+ })
+ .unwrap_or_else(|| self.data.cargo_target.clone().into_iter().collect()),
+ all_targets: self.data.check_allTargets,
no_default_features: self
.data
- .checkOnSave_noDefaultFeatures
+ .check_noDefaultFeatures
.unwrap_or(self.data.cargo_noDefaultFeatures),
all_features: matches!(
- self.data.checkOnSave_features.as_ref().unwrap_or(&self.data.cargo_features),
+ self.data.check_features.as_ref().unwrap_or(&self.data.cargo_features),
CargoFeaturesDef::All
),
features: match self
.data
- .checkOnSave_features
+ .check_features
.clone()
.unwrap_or_else(|| self.data.cargo_features.clone())
{
CargoFeaturesDef::All => vec![],
CargoFeaturesDef::Selected(it) => it,
},
- extra_args: self.data.checkOnSave_extraArgs.clone(),
+ extra_args: self.data.check_extraArgs.clone(),
extra_env: self.check_on_save_extra_env(),
+ ansi_color_output: self.color_diagnostic_output(),
},
- };
- Some(flycheck_config)
+ }
+ }
+
+ pub fn check_on_save(&self) -> bool {
+ self.data.checkOnSave
}
pub fn runnables(&self) -> RunnablesConfig {
pub fn inlay_hints(&self) -> InlayHintsConfig {
InlayHintsConfig {
+ location_links: self.data.inlayHints_locationLinks,
render_colons: self.data.inlayHints_renderColons,
type_hints: self.data.inlayHints_typeHints_enable,
parameter_hints: self.data.inlayHints_parameterHints_enable,
chaining_hints: self.data.inlayHints_chainingHints_enable,
+ discriminant_hints: match self.data.inlayHints_discriminantHints_enable {
+ DiscriminantHintsDef::Always => ide::DiscriminantHints::Always,
+ DiscriminantHintsDef::Never => ide::DiscriminantHints::Never,
+ DiscriminantHintsDef::Fieldless => ide::DiscriminantHints::Fieldless,
+ },
closure_return_type_hints: match self.data.inlayHints_closureReturnTypeHints_enable {
ClosureReturnTypeHintsDef::Always => ide::ClosureReturnTypeHints::Always,
ClosureReturnTypeHintsDef::Never => ide::ClosureReturnTypeHints::Never,
},
AdjustmentHintsDef::Reborrow => ide::AdjustmentHints::ReborrowOnly,
},
+ adjustment_hints_mode: match self.data.inlayHints_expressionAdjustmentHints_mode {
+ AdjustmentHintsModeDef::Prefix => ide::AdjustmentHintsMode::Prefix,
+ AdjustmentHintsModeDef::Postfix => ide::AdjustmentHintsMode::Postfix,
+ AdjustmentHintsModeDef::PreferPrefix => ide::AdjustmentHintsMode::PreferPrefix,
+ AdjustmentHintsModeDef::PreferPostfix => ide::AdjustmentHintsMode::PreferPostfix,
+ },
+ adjustment_hints_hide_outside_unsafe: self
+ .data
+ .inlayHints_expressionAdjustmentHints_hideOutsideUnsafe,
binding_mode_hints: self.data.inlayHints_bindingModeHints_enable,
param_names_for_lifetime_elision_hints: self
.data
}
}
+ pub fn main_loop_num_threads(&self) -> usize {
+ self.data.numThreads.unwrap_or(num_cpus::get_physical().try_into().unwrap_or(1))
+ }
+
pub fn typing_autoclose_angle(&self) -> bool {
self.data.typing_autoClosingAngleBrackets_enable
}
named_unit_variant!(skip_trivial);
named_unit_variant!(mutable);
named_unit_variant!(reborrow);
+ named_unit_variant!(fieldless);
named_unit_variant!(with_block);
}
Reborrow,
}
+#[derive(Deserialize, Debug, Clone)]
+#[serde(untagged)]
+enum DiscriminantHintsDef {
+ #[serde(deserialize_with = "true_or_always")]
+ Always,
+ #[serde(deserialize_with = "false_or_never")]
+ Never,
+ #[serde(deserialize_with = "de_unit_v::fieldless")]
+ Fieldless,
+}
+
+#[derive(Deserialize, Debug, Clone)]
+#[serde(rename_all = "snake_case")]
+enum AdjustmentHintsModeDef {
+ Prefix,
+ Postfix,
+ PreferPrefix,
+ PreferPostfix,
+}
+
#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "snake_case")]
enum FilesWatcherDef {
alias: Option<&'static str>,
default: &str,
) -> T {
- let default = serde_json::from_str(default).unwrap();
// XXX: check alias first, to work-around the VS Code where it pre-fills the
// defaults instead of sending an empty object.
alias
.into_iter()
.chain(iter::once(field))
- .find_map(move |field| {
+ .filter_map(move |field| {
let mut pointer = field.replace('_', "/");
pointer.insert(0, '/');
- json.pointer_mut(&pointer).and_then(|it| match serde_json::from_value(it.take()) {
- Ok(it) => Some(it),
- Err(e) => {
- tracing::warn!("Failed to deserialize config field at {}: {:?}", pointer, e);
- error_sink.push((pointer, e));
- None
- }
- })
+ json.pointer_mut(&pointer)
+ .map(|it| serde_json::from_value(it.take()).map_err(|e| (e, pointer)))
+ })
+ .find(Result::is_ok)
+ .and_then(|res| match res {
+ Ok(it) => Some(it),
+ Err((e, pointer)) => {
+ tracing::warn!("Failed to deserialize config field at {}: {:?}", pointer, e);
+ error_sink.push((pointer, e));
+ None
+ }
})
- .unwrap_or(default)
+ .unwrap_or_else(|| serde_json::from_str(default).unwrap())
}
fn schema(fields: &[(&'static str, &'static str, &[&str], &str)]) -> serde_json::Value {
- for ((f1, ..), (f2, ..)) in fields.iter().zip(&fields[1..]) {
- fn key(f: &str) -> &str {
- f.splitn(2, '_').next().unwrap()
- }
- assert!(key(f1) <= key(f2), "wrong field order: {:?} {:?}", f1, f2);
- }
-
let map = fields
.iter()
.map(|(field, ty, doc, default)| {
let name = field.replace('_', ".");
- let name = format!("rust-analyzer.{}", name);
+ let name = format!("rust-analyzer.{name}");
let props = field_props(field, ty, doc, default);
(name, props)
})
let doc = doc.trim_end_matches('\n');
assert!(
doc.ends_with('.') && doc.starts_with(char::is_uppercase),
- "bad docs for {}: {:?}",
- field,
- doc
+ "bad docs for {field}: {doc:?}"
);
let default = default.parse::<serde_json::Value>().unwrap();
"type": ["null", "array"],
"items": { "type": "string" },
},
- "MergeBehaviorDef" => set! {
- "type": "string",
- "enum": ["none", "crate", "module"],
- "enumDescriptions": [
- "Do not merge imports at all.",
- "Merge imports from the same crate into a single `use` statement.",
- "Merge imports from the same module into a single `use` statement."
- ],
- },
"ExprFillDefaultDef" => set! {
"type": "string",
"enum": ["todo", "default"],
"Only show auto borrow and dereference adjustment hints."
]
},
+ "DiscriminantHintsDef" => set! {
+ "type": "string",
+ "enum": [
+ "always",
+ "never",
+ "fieldless"
+ ],
+ "enumDescriptions": [
+ "Always show all discriminant hints.",
+ "Never show discriminant hints.",
+ "Only show discriminant hints on fieldless enum variants."
+ ]
+ },
+ "AdjustmentHintsModeDef" => set! {
+ "type": "string",
+ "enum": [
+ "prefix",
+ "postfix",
+ "prefer_prefix",
+ "prefer_postfix",
+ ],
+ "enumDescriptions": [
+ "Always show adjustment hints as prefix (`*expr`).",
+ "Always show adjustment hints as postfix (`expr.*`).",
+ "Show prefix or postfix depending on which uses less parenthesis, prefering prefix.",
+ "Show prefix or postfix depending on which uses less parenthesis, prefering postfix.",
+ ]
+ },
"CargoFeaturesDef" => set! {
"anyOf": [
{
"The command will be executed in the project root."
],
},
- "CheckOnSaveTargets" => set! {
+ "Option<CheckOnSaveTargets>" => set! {
"anyOf": [
+ {
+ "type": "null"
+ },
{
"type": "string",
},
},
],
},
- _ => panic!("missing entry for {}: {}", ty, default),
+ _ => panic!("missing entry for {ty}: {default}"),
}
map.into()
.iter()
.map(|(field, _ty, doc, default)| {
let name = format!("rust-analyzer.{}", field.replace('_', "."));
- let doc = doc_comment_to_string(*doc);
+ let doc = doc_comment_to_string(doc);
if default.contains('\n') {
format!(
- r#"[[{}]]{}::
+ r#"[[{name}]]{name}::
+
--
Default:
----
-{}
+{default}
----
-{}
+{doc}
--
-"#,
- name, name, default, doc
+"#
)
} else {
- format!("[[{}]]{} (default: `{}`)::\n+\n--\n{}--\n", name, name, default, doc)
+ format!("[[{name}]]{name} (default: `{default}`)::\n+\n--\n{doc}--\n")
}
})
.collect::<String>()
}
fn doc_comment_to_string(doc: &[&str]) -> String {
- doc.iter().map(|it| it.strip_prefix(' ').unwrap_or(it)).map(|it| format!("{}\n", it)).collect()
+ doc.iter().map(|it| it.strip_prefix(' ').unwrap_or(it)).map(|it| format!("{it}\n")).collect()
}
#[cfg(test)]
#[test]
fn generate_package_json_config() {
let s = Config::json_schema();
- let schema = format!("{:#}", s);
+ let schema = format!("{s:#}");
let mut schema = schema
.trim_start_matches('{')
.trim_end_matches('}')