feat(lsp): wire diagnostic.link_severity through from client config
The broken-link diagnostic severity was already consulted by
collect_diagnostics, but Config.diagnostic was hardcoded to the default and
never populated from initializationOptions/didChangeConfiguration. Parse a
client-supplied `diagnostic.link_severity` ('off'|'hint'|'warn'|'error',
case-insensitive) into LinkSeverity and apply it in both from_init_params and
apply_change. Partial config updates that omit `diagnostic` preserve the
existing severity rather than resetting it.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -153,6 +153,21 @@ pub enum LinkSeverity {
|
|||||||
Error,
|
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<Self> {
|
||||||
|
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 {
|
impl WikiConfig {
|
||||||
/// Synthetic default used when the client supplies neither
|
/// Synthetic default used when the client supplies neither
|
||||||
/// `initializationOptions` nor a workspace root. `name` is empty;
|
/// `initializationOptions` nor a workspace root. `name` is empty;
|
||||||
@@ -360,7 +375,7 @@ impl Config {
|
|||||||
|
|
||||||
Config {
|
Config {
|
||||||
wikis,
|
wikis,
|
||||||
diagnostic: DiagnosticConfig::default(),
|
diagnostic: diagnostic_from_raw(&raw.diagnostic),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -402,6 +417,11 @@ impl Config {
|
|||||||
}
|
}
|
||||||
self.wikis = vec![wc];
|
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<String>,
|
file_extension: Option<String>,
|
||||||
syntax: Option<String>,
|
syntax: Option<String>,
|
||||||
log_level: Option<String>,
|
log_level: Option<String>,
|
||||||
|
diagnostic: Option<RawDiagnostic>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
struct RawDiagnostic {
|
||||||
|
#[serde(default)]
|
||||||
|
link_severity: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build a [`DiagnosticConfig`] from the raw client payload, falling back
|
||||||
|
/// to defaults for absent or unrecognised values.
|
||||||
|
fn diagnostic_from_raw(raw: &Option<RawDiagnostic>) -> 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)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
|||||||
@@ -319,6 +319,54 @@ fn raw_wiki_parses_every_new_key() {
|
|||||||
assert!(w.auto_toc);
|
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]
|
#[test]
|
||||||
fn vim_flat_and_neovim_wrapped_payloads_desugar_identically() {
|
fn vim_flat_and_neovim_wrapped_payloads_desugar_identically() {
|
||||||
// Parity guard for the two client shapes:
|
// Parity guard for the two client shapes:
|
||||||
@@ -332,6 +380,7 @@ fn vim_flat_and_neovim_wrapped_payloads_desugar_identically() {
|
|||||||
"file_extension": ".md",
|
"file_extension": ".md",
|
||||||
"syntax": "vimwiki",
|
"syntax": "vimwiki",
|
||||||
"log_level": "debug",
|
"log_level": "debug",
|
||||||
|
"diagnostic": { "link_severity": "error" },
|
||||||
"wikis": [
|
"wikis": [
|
||||||
{
|
{
|
||||||
"name": "personal",
|
"name": "personal",
|
||||||
@@ -347,6 +396,11 @@ fn vim_flat_and_neovim_wrapped_payloads_desugar_identically() {
|
|||||||
let flat = config_from_json(inner.clone());
|
let flat = config_from_json(inner.clone());
|
||||||
let wrapped = config_from_json(json!({ "nuwiki": inner }));
|
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());
|
assert_eq!(flat.wikis.len(), wrapped.wikis.len());
|
||||||
for (a, b) in flat.wikis.iter().zip(wrapped.wikis.iter()) {
|
for (a, b) in flat.wikis.iter().zip(wrapped.wikis.iter()) {
|
||||||
assert_eq!(a.name, b.name);
|
assert_eq!(a.name, b.name);
|
||||||
|
|||||||
Reference in New Issue
Block a user