diff --git a/crates/nuwiki-lsp/src/config.rs b/crates/nuwiki-lsp/src/config.rs index d216cff..186dd60 100644 --- a/crates/nuwiki-lsp/src/config.rs +++ b/crates/nuwiki-lsp/src/config.rs @@ -153,6 +153,21 @@ pub enum LinkSeverity { Error, } +impl LinkSeverity { + /// Parse a client-supplied severity string. Case-insensitive; + /// accepts both `warn` and `warning`. Returns `None` for unknown + /// values so callers can fall back to the default. + pub fn parse(s: &str) -> Option { + match s.trim().to_ascii_lowercase().as_str() { + "off" => Some(Self::Off), + "hint" => Some(Self::Hint), + "warn" | "warning" => Some(Self::Warning), + "error" => Some(Self::Error), + _ => None, + } + } +} + impl WikiConfig { /// Synthetic default used when the client supplies neither /// `initializationOptions` nor a workspace root. `name` is empty; @@ -360,7 +375,7 @@ impl Config { Config { wikis, - diagnostic: DiagnosticConfig::default(), + diagnostic: diagnostic_from_raw(&raw.diagnostic), } } @@ -402,6 +417,11 @@ impl Config { } self.wikis = vec![wc]; } + // Only touch diagnostic settings when the payload carries them, so a + // partial `didChangeConfiguration` doesn't reset severity to default. + if raw.diagnostic.is_some() { + self.diagnostic = diagnostic_from_raw(&raw.diagnostic); + } } } @@ -449,6 +469,25 @@ struct InitOptions { file_extension: Option, syntax: Option, log_level: Option, + diagnostic: Option, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "snake_case")] +struct RawDiagnostic { + #[serde(default)] + link_severity: Option, +} + +/// Build a [`DiagnosticConfig`] from the raw client payload, falling back +/// to defaults for absent or unrecognised values. +fn diagnostic_from_raw(raw: &Option) -> DiagnosticConfig { + let link_severity = raw + .as_ref() + .and_then(|d| d.link_severity.as_deref()) + .and_then(LinkSeverity::parse) + .unwrap_or_default(); + DiagnosticConfig { link_severity } } #[derive(Debug, Deserialize)] diff --git a/crates/nuwiki-lsp/tests/index_and_config.rs b/crates/nuwiki-lsp/tests/index_and_config.rs index 7267c15..36fe0df 100644 --- a/crates/nuwiki-lsp/tests/index_and_config.rs +++ b/crates/nuwiki-lsp/tests/index_and_config.rs @@ -319,6 +319,54 @@ fn raw_wiki_parses_every_new_key() { assert!(w.auto_toc); } +#[test] +fn link_severity_parses_from_client_config() { + for (input, expected) in [ + ("off", LinkSeverity::Off), + ("hint", LinkSeverity::Hint), + ("warn", LinkSeverity::Warning), + ("warning", LinkSeverity::Warning), + ("error", LinkSeverity::Error), + ("ERROR", LinkSeverity::Error), + ] { + let cfg = config_from_json(json!({ + "wiki_root": "/tmp/w", + "diagnostic": { "link_severity": input }, + })); + assert_eq!( + cfg.diagnostic.link_severity, expected, + "input {input:?} should map to {expected:?}" + ); + } +} + +#[test] +fn link_severity_invalid_or_absent_falls_back_to_default() { + // Unknown string → default (Warning). + let bad = config_from_json(json!({ + "wiki_root": "/tmp/w", + "diagnostic": { "link_severity": "loud" }, + })); + assert_eq!(bad.diagnostic.link_severity, LinkSeverity::Warning); + + // No diagnostic key at all → default. + let none = config_from_json(json!({ "wiki_root": "/tmp/w" })); + assert_eq!(none.diagnostic.link_severity, LinkSeverity::Warning); +} + +#[test] +fn apply_change_preserves_severity_on_partial_update() { + let mut cfg = config_from_json(json!({ + "wiki_root": "/tmp/w", + "diagnostic": { "link_severity": "error" }, + })); + assert_eq!(cfg.diagnostic.link_severity, LinkSeverity::Error); + + // A later update that omits `diagnostic` must not reset severity. + cfg.apply_change(&json!({ "wiki_root": "/tmp/w2" })); + assert_eq!(cfg.diagnostic.link_severity, LinkSeverity::Error); +} + #[test] fn vim_flat_and_neovim_wrapped_payloads_desugar_identically() { // Parity guard for the two client shapes: @@ -332,6 +380,7 @@ fn vim_flat_and_neovim_wrapped_payloads_desugar_identically() { "file_extension": ".md", "syntax": "vimwiki", "log_level": "debug", + "diagnostic": { "link_severity": "error" }, "wikis": [ { "name": "personal", @@ -347,6 +396,11 @@ fn vim_flat_and_neovim_wrapped_payloads_desugar_identically() { let flat = config_from_json(inner.clone()); let wrapped = config_from_json(json!({ "nuwiki": inner })); + assert_eq!( + flat.diagnostic.link_severity, + wrapped.diagnostic.link_severity + ); + assert_eq!(flat.diagnostic.link_severity, LinkSeverity::Error); assert_eq!(flat.wikis.len(), wrapped.wikis.len()); for (a, b) in flat.wikis.iter().zip(wrapped.wikis.iter()) { assert_eq!(a.name, b.name);