use super::documents::Document;
use lsp_types::{Diagnostic, DiagnosticSeverity};
use std::ops::Range;
use toml_edit::{Item, Table};
pub(super) fn diagnostic(
doc: &Document,
span: Range<usize>,
message: String,
severity: DiagnosticSeverity,
) -> Diagnostic {
Diagnostic {
range: doc.range(span),
severity: Some(severity),
source: Some("archival".into()),
message,
..Default::default()
}
}
pub(super) fn syntax_error(doc: &Document, err: &toml::de::Error) -> Diagnostic {
let span = err.span().unwrap_or(0..doc.text.len());
diagnostic(
doc,
span,
err.message().to_string(),
DiagnosticSeverity::ERROR,
)
}
pub(super) fn error_at_named_key(
doc: &Document,
message: String,
severity: DiagnosticSeverity,
) -> Diagnostic {
let named = message.split('"').nth(1).unwrap_or_default().to_string();
error_at_key(doc, &named, message, severity)
}
pub(super) fn error_at_key(
doc: &Document,
key: &str,
message: String,
severity: DiagnosticSeverity,
) -> Diagnostic {
let span = span_of_key(&doc.text, key).unwrap_or(0..doc.text.len());
diagnostic(doc, span, message, severity)
}
fn span_of_key(source: &str, key: &str) -> Option<Range<usize>> {
if key.is_empty() {
return None;
}
let parsed = toml_edit::Document::<&str>::parse(source).ok()?;
find_key(parsed.as_table(), key)
}
fn find_key(table: &Table, name: &str) -> Option<Range<usize>> {
for (key_name, item) in table.iter() {
let (key, _) = table.get_key_value(key_name)?;
if key_name == name {
return key.span();
}
let nested = child_tables(item)
.into_iter()
.find_map(|child| find_key(child, name));
if nested.is_some() {
return nested;
}
}
None
}
pub(super) fn child_tables(item: &Item) -> Vec<&Table> {
match item {
Item::ArrayOfTables(tables) => tables.iter().collect(),
Item::Table(table) => vec![table],
_ => vec![],
}
}