use std::sync::{Arc, OnceLock};
use oneyaml_core::Encoding;
use oneyaml_core::{Code, Diagnostic, LineIndex, Path, Settings, Severity, Span, code};
use crate::diag_cap::Dropped;
use oneyaml_syntax::decode::{InvalidRun, InvalidRuns};
use oneyaml_syntax::tree::{Node as TreeNode, SyntaxKind, SyntaxTree};
#[cfg(test)]
use crate::construct::Node as Ev;
use crate::construct::Source;
use crate::construct::settings::read_diagnostics;
use crate::construct::tree::TreeSource;
use crate::error::Error;
use crate::value::Value;
mod anchors;
mod cap_index;
mod carry;
mod comment_edit;
mod comments;
mod edit;
mod error;
mod key;
mod lookup;
mod path_arg;
mod place;
mod render;
mod stream;
pub(crate) mod tags;
mod view;
pub use comments::{
Comment, CommentExplanation, CommentId, CommentOwner, CommentRule, Comments, NO_COMMENTS,
};
pub use edit::{Entry, NodeMut, ReinsertError, Removed};
pub use error::EditError;
pub use key::AsKey;
pub use path_arg::IntoPath;
pub use stream::{DocumentMut, Stream};
pub use view::{MappingRef, NodeKind, NodeRef, ScalarRef, ScalarStyle, SequenceRef};
#[derive(Clone, Debug)]
struct Origin {
encoder: oneyaml_syntax::Encoder,
invalid: InvalidRuns,
}
type TreeId = oneyaml_syntax::tree::NodeId;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Mode {
Strict { single: bool },
Recovering { single: bool },
}
struct Shared {
tree: SyntaxTree,
settings: Settings,
diagnostics: Vec<Diagnostic>,
decoded: usize,
pre: usize,
doc_ids: Vec<TreeId>,
checked: usize,
errors: usize,
error_docs: Vec<usize>,
origin: Origin,
anchors: OnceLock<anchors::Index>,
nl: &'static str,
lines: OnceLock<LineIndex>,
node_events: OnceLock<Vec<u32>>,
tags: tags::DocTags,
mode: Mode,
edited: bool,
trailing: Vec<Diagnostic>,
error_marks: Vec<(usize, usize, Code)>,
settings_diags: Vec<Diagnostic>,
dropped: Dropped,
found: usize,
capped: bool,
counts: std::sync::Mutex<Counts>,
cap_view: OnceLock<Arc<[Diagnostic]>>,
cap_index: std::sync::Mutex<Option<Box<cap_index::CapIndex>>>,
lookups: std::sync::Mutex<lookup::Indexes>,
}
impl core::fmt::Debug for Shared {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Shared")
.field("documents", &self.doc_ids.len())
.field("span", &self.tree.root().span())
.field("edited", &self.edited)
.field("mode", &self.mode)
.field("encoding", &self.origin.encoder.encoding())
.finish_non_exhaustive()
}
}
#[derive(Clone, Debug, Default)]
struct Counts {
docs: std::collections::BTreeMap<usize, (usize, usize)>,
stale: std::collections::BTreeSet<usize>,
unknown: bool,
delta: i128,
}
fn moved_to_end(d: &Diagnostic, from: usize, to: usize) -> Diagnostic {
let mv = |s: &mut oneyaml_core::Span| {
let shift = |x: usize| {
if to >= from {
x.saturating_add(to.saturating_sub(from))
} else {
x.saturating_sub(from.saturating_sub(to))
}
};
*s = oneyaml_core::Span::new(shift(s.start), shift(s.end));
};
let mut d = d.clone();
mv(&mut d.primary.span);
for l in &mut d.secondary {
mv(&mut l.span);
}
for sg in &mut d.suggestions {
for e in &mut sg.edits {
mv(&mut e.span);
}
}
d
}
fn invalid_code(e: Encoding) -> Code {
match e {
Encoding::Utf16Le | Encoding::Utf16Be => code::INVALID_UTF16,
Encoding::Utf32Le | Encoding::Utf32Be => code::INVALID_UTF32,
_ => code::INVALID_UTF8,
}
}
fn line_break_of(text: &str) -> &'static str {
match text.find(['\n', '\r']) {
Some(i) if text.get(i..).is_some_and(|t| t.starts_with("\r\n")) => "\r\n",
Some(i) if text.get(i..).is_some_and(|t| t.starts_with('\r')) => "\r",
_ => "\n",
}
}
fn checked_documents(tree: &SyntaxTree, docs: &[TreeId]) -> usize {
let first_error = tree
.diagnostics_by_element()
.filter(|(_, d)| d.severity == Severity::Error)
.map(|(n, _)| n.map_or(usize::MAX, TreeNode::preorder_index))
.min();
let Some(at) = first_error else {
return docs.len();
};
docs.iter()
.filter(|id| tree.get(**id).is_ok_and(|d| d.preorder_index() <= at))
.count()
}
fn documents_with_error_nodes(tree: &SyntaxTree, docs: &[TreeId]) -> Vec<usize> {
if !tree.has_errors() {
return Vec::new();
}
docs.iter()
.enumerate()
.filter(|(_, id)| {
tree.get(**id)
.is_ok_and(|d| d.descendants().any(|n| n.kind() == SyntaxKind::Error))
})
.map(|(i, _)| i)
.collect()
}
impl Shared {
fn copy(&self) -> Self {
Self {
tree: self.tree.fork(),
settings: self.settings.clone(),
diagnostics: self.diagnostics.clone(),
decoded: self.decoded,
pre: self.pre,
doc_ids: self.doc_ids.clone(),
checked: self.checked,
errors: self.errors,
error_docs: self.error_docs.clone(),
origin: self.origin.clone(),
anchors: self.anchors.clone(),
lines: self.lines.clone(),
node_events: OnceLock::new(),
tags: tags::DocTags::new(),
nl: self.nl,
mode: self.mode,
edited: self.edited,
trailing: self.trailing.clone(),
error_marks: self.error_marks.clone(),
settings_diags: self.settings_diags.clone(),
dropped: self.dropped,
found: self.found,
capped: self.capped,
counts: std::sync::Mutex::new(self.counts.lock().map(|c| c.clone()).unwrap_or_else(
|_| Counts {
unknown: true,
..Counts::default()
},
)),
cap_view: OnceLock::new(),
cap_index: std::sync::Mutex::new(
self.cap_index
.lock()
.ok()
.and_then(|ix| ix.as_ref().cloned()),
),
lookups: std::sync::Mutex::new(
self.lookups
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone(),
),
}
}
fn build(
text: String,
settings: &Settings,
origin: Origin,
mut diags: Vec<Diagnostic>,
mut dropped: Dropped,
) -> Self {
diags.sort_by_key(|d| d.primary.span.start);
let decoded = diags.len();
let settings_diags = match read_diagnostics(settings) {
Ok(w) => w,
Err(e) => vec![e.diagnostic().clone()],
};
diags.extend(settings_diags.iter().cloned());
let tree = SyntaxTree::parse_with(text, settings);
let pre = diags.len();
let mut error_marks: Vec<(usize, usize, Code)> = Vec::new();
for d in tree.diagnostics() {
if d.severity == Severity::Error {
error_marks.push((d.primary.span.start, d.primary.span.end, d.code));
}
if SyntaxTree::is_skeleton(&d) {
dropped = dropped.and(Dropped::one(d.primary.span.start));
} else {
diags.push(d.into_owned());
}
}
if let Some(events) = diags.get_mut(pre..) {
events.sort_by_key(|d| d.primary.span.start);
}
let doc_ids: Vec<TreeId> = tree
.root()
.children()
.filter(|n| n.kind() == SyntaxKind::Document)
.map(TreeNode::id)
.collect();
let checked = checked_documents(&tree, &doc_ids);
let errors = tree.error_count();
let error_docs = documents_with_error_nodes(&tree, &doc_ids);
let trailing = tree
.diagnostics_by_element()
.filter(|(n, _)| n.is_none())
.map(|(_, d)| d.into_owned())
.collect();
let nl = line_break_of(tree.source());
Self {
tree,
settings: settings.clone(),
diagnostics: diags,
decoded,
pre,
doc_ids,
checked,
errors,
error_docs,
origin,
anchors: OnceLock::new(),
lines: OnceLock::new(),
node_events: OnceLock::new(),
tags: tags::DocTags::new(),
nl,
mode: Mode::Strict { single: true },
edited: false,
trailing,
error_marks,
settings_diags,
dropped,
found: 0,
capped: false,
counts: std::sync::Mutex::new(Counts::default()),
cap_view: OnceLock::new(),
cap_index: std::sync::Mutex::new(None),
lookups: std::sync::Mutex::new(lookup::Indexes::default()),
}
}
fn from_str(text: String, settings: &Settings) -> Self {
let origin = Origin {
encoder: oneyaml_syntax::Encoder::default(),
invalid: InvalidRuns::default(),
};
Self::build(text, settings, origin, Vec::new(), Dropped::default())
}
fn from_slice_strict(bytes: &[u8], settings: &Settings) -> Result<Self, Error> {
match oneyaml_syntax::decode_strict(bytes) {
Ok(decoded) => Ok(Self::from_decoded(decoded, settings)),
Err(e) => {
let (d, text) = *e;
Err(Error::from_diagnostic(d).finish(Some(&text), Vec::new()))
}
}
}
fn from_slice(bytes: &[u8], settings: &Settings) -> Self {
let keep = settings
.get_limits()
.get_max_diagnostics()
.saturating_add(1);
Self::from_decoded(oneyaml_syntax::decode_with_limit(bytes, keep), settings)
}
fn from_decoded(decoded: oneyaml_syntax::Decoded<'_>, settings: &Settings) -> Self {
let (diags, invalid, encoder, text) = decoded.into_parts();
let first_dropped = invalid.get(diags.len()).map(|r| r.text.start);
let dropped = Dropped {
count: invalid.len().saturating_sub(diags.len()),
first: first_dropped,
};
let origin = Origin { encoder, invalid };
Self::build(text.into_owned(), settings, origin, diags, dropped)
}
fn text(&self) -> std::borrow::Cow<'_, str> {
if self.edited {
std::borrow::Cow::Owned(self.tree.emit())
} else {
std::borrow::Cow::Borrowed(self.tree.source())
}
}
fn after_edit(&mut self) {
self.edited = true;
self.lines = OnceLock::new();
self.cap_view = OnceLock::new();
self.node_events = OnceLock::new();
if self.tree.error_count() != self.errors {
self.checked = checked_documents(&self.tree, &self.doc_ids);
self.errors = self.tree.error_count();
self.error_docs = documents_with_error_nodes(&self.tree, &self.doc_ids);
self.drop_cap_index();
}
}
fn drop_cap_index(&mut self) {
self.cap_index = std::sync::Mutex::new(None);
}
fn documents_changed(&mut self) {
self.cap_view = OnceLock::new();
self.drop_cap_index();
self.doc_ids = self.documents().map(TreeNode::id).collect();
self.checked = checked_documents(&self.tree, &self.doc_ids);
self.errors = self.tree.error_count();
self.error_docs = documents_with_error_nodes(&self.tree, &self.doc_ids);
}
fn doc_span(&self, i: usize) -> Option<Span> {
let id = self.doc_ids.get(i)?;
self.tree.get(*id).ok().map(TreeNode::span)
}
fn region_of(&self, d: TreeNode<'_>) -> Span {
let is_doc = |n: &TreeNode<'_>| n.kind() == SyntaxKind::Document;
let mut prev = d.prev_sibling();
while let Some(p) = prev.filter(|p| !is_doc(p)) {
prev = p.prev_sibling();
}
let mut next = d.next_sibling();
while let Some(n) = next.filter(|n| !is_doc(n)) {
next = n.next_sibling();
}
let root = self.tree.root().span();
let start = prev.map_or(root.start, |p| self.split_between(p, d));
let end = next.map_or(root.end, |n| self.split_between(d, n));
Span::new(start, end.max(start))
}
fn split_between(&self, prev: TreeNode<'_>, next: TreeNode<'_>) -> usize {
let tree = &self.tree;
let mine: std::collections::BTreeSet<TreeId> = tree
.comment_attachments_between(Some(prev), Some(next))
.into_iter()
.filter(|(_, a)| a.document() == Some(prev))
.map(|(c, _)| c.id())
.collect();
let mut pos = prev.span().end;
let mut last = comments::last_token(prev);
while let Some(t) = last.filter(|t| t.span().is_empty()) {
last = place::prev_token(t);
}
let mut on_line = last.is_some_and(|t| !comments::ends_line(t));
let mut first_next = None;
let mut at = prev.next_sibling();
'stretch: while let Some(top) = at.filter(|t| *t != next) {
for t in top.descendants().filter(|t| t.is_token()) {
if t.kind() == SyntaxKind::Comment {
if mine.contains(&t.id()) {
pos = t.span().end;
on_line = true;
} else {
first_next = Some(t.span().start);
break 'stretch;
}
} else if comments::ends_line(t) {
if on_line {
pos = t.span().end;
on_line = false;
}
} else if comments::visible(t) {
first_next = Some(t.span().start);
break 'stretch;
}
}
at = top.next_sibling();
}
let pos = pos.min(next.span().start);
first_next.map_or(pos, |f| pos.min(f))
}
fn anchor_index(&self) -> &anchors::Index {
self.anchors
.get_or_init(|| anchors::Index::build(&self.tree))
}
fn fresh_diagnostics(&self) -> Vec<Diagnostic> {
if self.tree.suppressed_diagnostics() > 0 {
return self.reparsed().diagnostics;
}
let mut out: Vec<Diagnostic> = self
.diagnostics
.get(..self.pre)
.unwrap_or_default()
.to_vec();
let pre = out.len();
out.extend(self.tree.diagnostics().map(std::borrow::Cow::into_owned));
let found = self.meaning();
out.extend(found.list);
if let Some(events) = out.get_mut(pre..) {
events.sort_by_key(|d| d.primary.span.start);
}
crate::diag_cap::cap(&mut out, found.dropped, self.limit());
out
}
fn limit(&self) -> usize {
self.settings.get_limits().get_max_diagnostics()
}
fn meaning(&self) -> Found {
let mut events = TreeSource::new(&self.tree);
let mut out = Vec::new();
let found = match self.mode {
Mode::Strict { single } => {
let found = crate::de::check_unexpanded(&mut events, &self.settings, single, false);
out.extend(found.errors.first().map(|e| e.diagnostic().clone()));
found
}
Mode::Recovering { single } => {
let found = crate::de::check_unexpanded(&mut events, &self.settings, false, true);
out.extend(found.errors.iter().map(|e| e.diagnostic().clone()));
if single {
out.extend(self.multiple_documents());
}
found
}
};
out.extend(found.warnings);
Found {
list: out,
dropped: found.suppressed,
dropped_errors: found.dropped_errors,
}
}
fn reparsed(&self) -> Shared {
#[cfg(test)]
tests::count_reparse();
let mut s = Shared::from_str(self.text().into_owned(), &self.settings);
s.mode = self.mode;
let found = s.meaning();
s.add_diagnostics(found);
s
}
fn cap_possible(&self) -> bool {
if self.capped || self.tree.suppressed_diagnostics() > 0 {
return true;
}
let Ok(mut c) = self.counts.lock() else {
return true;
};
let stale = std::mem::take(&mut c.stale);
for j in stale {
let now = self.complete_count(j);
if let Some(e) = c.docs.get_mut(&j) {
let was = e.1;
e.1 = now;
c.delta = c
.delta
.saturating_add(i128::try_from(now).unwrap_or(i128::MAX))
.saturating_sub(i128::try_from(was).unwrap_or(i128::MAX));
} else {
c.unknown = true;
}
}
if c.unknown {
return true;
}
let total = i128::try_from(self.found)
.unwrap_or(i128::MAX)
.saturating_add(c.delta);
total > i128::try_from(self.limit()).unwrap_or(i128::MAX)
}
fn complete_count(&self, i: usize) -> usize {
let (list, dropped) = self.edited_doc_events(i);
list.len().saturating_add(dropped.count)
}
fn record_count(&self, i: usize) {
if let Ok(mut c) = self.counts.lock() {
c.docs.entry(i).or_insert_with(|| {
let n = self
.parsed_count(i)
.unwrap_or_else(|| self.complete_count(i));
(n, n)
});
}
}
fn parsed_count(&self, i: usize) -> Option<usize> {
if self.edited
|| self.capped
|| self.decoded > 0
|| self.tree.suppressed_diagnostics() > 0
|| self.dropped.count > 0
{
return None;
}
let events = self.diagnostics.get(self.pre..)?;
Some(self.owned(events, i).len())
}
fn mark_edited(&mut self, i: usize) {
if let Ok(c) = self.counts.get_mut() {
if c.docs.contains_key(&i) {
c.stale.insert(i);
} else {
c.unknown = true;
}
}
let poisoned = match self.cap_index.get_mut() {
Ok(Some(ix)) => {
ix.mark(i);
false
}
Ok(None) => false,
Err(_) => true,
};
if poisoned {
self.drop_cap_index();
}
}
fn capped_view(&self) -> &[Diagnostic] {
self.cap_view.get_or_init(|| {
if self.tree.suppressed_diagnostics() > 0 {
if self.doc_ids.len() <= 1 {
return self.reparsed().diagnostics.into();
}
return self.capped_indexed().unwrap_or_else(|| {
#[cfg(test)]
tests::count_fallback();
self.reparsed().diagnostics.into()
});
}
self.capped_indexed()
.unwrap_or_else(|| self.capped_scan().into())
})
}
fn capped_scan(&self) -> Vec<Diagnostic> {
let keep = self.limit().saturating_add(1);
let mut out: Vec<Diagnostic> = self
.diagnostics
.get(..self.pre)
.unwrap_or_default()
.to_vec();
let mut extra = Dropped::default();
let mut error_kept = out.iter().any(|d| d.severity == Severity::Error);
for i in 0..self.doc_ids.len() {
let (own, dropped) = self.edited_doc_events(i);
extra = extra.and(dropped);
for d in own {
let error = d.severity == Severity::Error;
if out.len() < keep || (error && !error_kept) {
error_kept |= error;
out.push(d);
} else {
extra = extra.and(Dropped::one(d.primary.span.start));
}
}
}
crate::diag_cap::cap(&mut out, extra, self.limit());
out
}
fn capped_indexed(&self) -> Option<Arc<[Diagnostic]>> {
let limit = self.limit();
let keep = limit.saturating_add(1);
let memo = Memo::new(self, keep);
let mut slot = self.cap_index.lock().ok()?;
if slot.is_none() {
*slot = cap_index::CapIndex::build(self.doc_ids.len(), &memo).map(Box::new);
}
let ix = slot.as_mut()?;
if !ix.refresh(&memo) {
*slot = None;
return None;
}
let prev = std::mem::take(&mut ix.shown);
let ix: &cap_index::CapIndex = ix;
let is_error = |d: &Diagnostic| d.severity == Severity::Error;
let mut out: Vec<Diagnostic> = self
.diagnostics
.get(..self.pre)
.unwrap_or_default()
.to_vec();
let mut error_kept = out.iter().any(is_error);
let total = ix.total();
let skeletons = self.tree.suppressed_diagnostics() > 0;
let events = |j: usize, need: usize, want_error: bool| {
if let Some(head) = self.seen_head(&prev, ix, j, need, want_error) {
return Some(head);
}
let meaning = match memo.take(j) {
Some((m, _)) => m,
None if ix.meaning_empty(j) => Vec::new(),
None => self.doc_meaning_of(j).0,
};
let head = if skeletons {
self.doc_head_filled(j, &meaning, need)
} else {
self.doc_head(j, &meaning, &[], need)
};
let ok = head.n == ix.leaf_n(j);
debug_assert!(ok, "document {j}: its diagnostics do not match its count");
ok.then_some(head)
};
let push = keep.saturating_sub(out.len()).min(total.n);
let mut taken = 0usize;
let mut seen: Vec<Placed> = Vec::new();
let mut next = 0usize;
let mut rest: Option<(usize, Option<Option<Diagnostic>>)> = None;
while taken < push {
let (j, before) = ix.nth_diagnostic(taken)?;
if before != 0 {
debug_assert!(false, "document {j} read from its middle");
return None;
}
let want = push.saturating_sub(taken);
let head = events(j, want, false)?;
let n = want.min(head.n);
let at = out.len();
let error = head.list.iter().take(n).position(is_error);
for d in head.list.into_iter().take(n) {
error_kept |= is_error(&d);
out.push(d);
}
seen.push(Placed {
doc: j,
at,
len: n,
error: error.map(|e| at.saturating_add(e)),
});
rest = (head.n > n).then(|| (seen.len().saturating_sub(1), head.first_error));
taken = taken.saturating_add(n);
next = j.saturating_add(1);
}
let mut error_pushed = false;
if !error_kept && total.errors > 0 {
let mut error = None;
let mut placed = None;
if let Some((k, known)) = rest {
let doc = seen.get(k).map_or(usize::MAX, |p| p.doc);
error = match known {
Some(e) => e,
None => events(doc, 0, true)?.first_error.flatten(),
};
placed = Some(k);
}
if error.is_none() {
if let Some(j) = ix.first_error_from(next) {
error = events(j, 0, true)?.first_error.flatten();
seen.push(Placed {
doc: j,
at: out.len(),
len: 0,
error: None,
});
placed = Some(seen.len().saturating_sub(1));
}
}
if let Some(e) = error {
if let Some(p) = placed.and_then(|k| seen.get_mut(k)) {
p.error = Some(out.len());
}
out.push(e);
error_pushed = true;
}
}
let unlisted = total
.n
.saturating_sub(push)
.saturating_sub(usize::from(error_pushed));
let first = total
.dropped
.first
.or_else(|| (unlisted > 0).then(|| out.last().map(|d| d.primary.span.start))?);
let extra = Dropped {
count: total.dropped.count.saturating_add(unlisted),
first,
};
let at_limit = out.get(limit).cloned();
let cut = crate::diag_cap::cap(&mut out, extra, limit);
if skeletons && out.iter().any(SyntaxTree::is_skeleton) {
return None;
}
let list: Arc<[Diagnostic]> = out.into();
let shown = self.shown_record(&list, &seen, cut, at_limit);
drop(prev);
if let Some(ix) = slot.as_mut() {
ix.shown = shown;
}
Some(list)
}
fn shown_record(
&self,
list: &Arc<[Diagnostic]>,
seen: &[Placed],
cut: Option<crate::diag_cap::Cut>,
at_limit: Option<Diagnostic>,
) -> cap_index::Shown {
let place = |p: usize| match cut {
None => Some(p),
Some(c) if p < c.limit => Some(p),
Some(c) if c.error == Some(p) => c.limit.checked_add(1),
Some(_) => None,
};
let mut docs = std::collections::BTreeMap::new();
for p in seen {
let end = p.at.saturating_add(p.len);
let (len, spare) = match cut {
None => (p.len, false),
Some(c) if end <= c.limit => (p.len, false),
Some(c) if p.at <= c.limit && end == c.limit.saturating_add(1) => {
(c.limit.saturating_sub(p.at), true)
}
Some(_) => continue,
};
let error = match p.error.and_then(place) {
Some(at) => cap_index::ErrorAt::At(at),
None => cap_index::ErrorAt::Unknown,
};
docs.insert(
p.doc,
cap_index::Seen {
start: self.region_start(p.doc),
at: p.at,
len,
spare,
error,
},
);
}
cap_index::Shown {
list: Some(Arc::clone(list)),
spare: cut.and(at_limit),
docs,
}
}
fn seen_head(
&self,
prev: &cap_index::Shown,
ix: &cap_index::CapIndex,
j: usize,
need: usize,
want_error: bool,
) -> Option<Head> {
let seen = prev.docs.get(&j)?;
let list = prev.list.as_deref()?;
let n = ix.leaf_n(j);
let take = need.min(n);
let avail = seen.len.saturating_add(usize::from(seen.spare));
if avail < take {
return None;
}
let now = self.region_start(j);
let moved = |d: &Diagnostic| {
if now == seen.start {
d.clone()
} else {
moved_to_end(d, seen.start, now)
}
};
let error = if ix.leaf_errors(j) == 0 {
Some(None)
} else {
match seen.error {
cap_index::ErrorAt::At(at) => Some(Some(moved(list.get(at)?))),
cap_index::ErrorAt::Unknown if want_error => return None,
cap_index::ErrorAt::Unknown => None,
}
};
let own = list.get(seen.at..seen.at.checked_add(seen.len)?)?;
let mut out: Vec<Diagnostic> = own.iter().take(take).map(moved).collect();
if out.len() < take {
out.push(moved(prev.spare.as_ref()?));
}
Some(Head {
list: out,
first_error: error,
n,
})
}
fn capped_doc(&self, i: usize) -> Vec<Diagnostic> {
let all = self.capped_view();
let pre = self.pre.min(all.len());
let (settings, events) = all.split_at(pre);
let mut out = settings
.get(self.decoded.min(pre)..)
.unwrap_or_default()
.to_vec();
out.extend_from_slice(self.owned(events, i));
out
}
fn doc_meaning(&self, i: usize, doc: TreeNode<'_>) -> (Vec<Diagnostic>, Dropped) {
#[cfg(test)]
tests::count_doc_read();
if i >= self.checked {
return (Vec::new(), Dropped::default());
}
let mut events = TreeSource::over(&self.tree, doc);
let recover = matches!(self.mode, Mode::Recovering { .. });
let found = crate::de::check_unexpanded(&mut events, &self.settings, false, recover);
let mut out = found.warnings;
if recover {
out.extend(found.errors.iter().map(|e| e.diagnostic().clone()));
} else {
out.extend(found.errors.first().map(|e| e.diagnostic().clone()));
}
(out, found.suppressed)
}
fn region_tree_diagnostics(&self, i: usize) -> Vec<Diagnostic> {
let mut out = Vec::new();
self.region_tree_each(i, &[], |d| out.push(d.into_owned()));
out
}
fn region_tree_counts(&self, i: usize, bounds: Option<(usize, usize)>) -> (usize, usize) {
let (mut n, mut errors) = (0usize, 0usize);
let bounds = bounds
.unwrap_or_else(|| (self.region_start(i), self.region_start(i.saturating_add(1))));
self.region_tree_each_within(i, bounds, &[], |d| {
n = n.saturating_add(1);
errors = errors.saturating_add(usize::from(d.severity == Severity::Error));
});
(n, errors)
}
fn region_tree_each<'s>(
&'s self,
i: usize,
fill: &'s [Diagnostic],
f: impl FnMut(std::borrow::Cow<'s, Diagnostic>),
) {
let bounds = (self.region_start(i), self.region_start(i.saturating_add(1)));
self.region_tree_each_within(i, bounds, fill, f);
}
fn region_tree_each_within<'s>(
&'s self,
i: usize,
(from, to): (usize, usize),
fill: &'s [Diagnostic],
mut f: impl FnMut(std::borrow::Cow<'s, Diagnostic>),
) {
#[cfg(test)]
tests::count_doc_read();
let owned = |d: &Diagnostic| from <= d.primary.span.start && d.primary.span.start < to;
let tree = &self.tree;
let first = match i.checked_sub(1).and_then(|p| self.doc_ids.get(p)) {
Some(id) => tree.get(*id).ok(),
None => tree.root().first_child(),
};
let stop = self.doc_ids.get(i.saturating_add(1)).copied();
let mut at = first;
while let Some(n) = at {
tree.diagnostics_within_filled(n, fill)
.map(|(_, d)| d)
.filter(|d| owned(d))
.for_each(&mut f);
if Some(n.id()) == stop {
break;
}
at = n.next_sibling();
}
if stop.is_none() {
let base = tree.source().len();
let end = if self.edited {
tree.root().span().end
} else {
base
};
self.trailing
.iter()
.map(|d| moved_to_end(d, base, end))
.filter(|d| owned(d))
.for_each(|d| f(std::borrow::Cow::Owned(d)));
}
}
fn edited_doc_events(&self, i: usize) -> (Vec<Diagnostic>, Dropped) {
let mut out = self.region_tree_diagnostics(i);
let (meaning, suppressed) = self.doc_meaning_of(i);
out.extend(meaning);
out.sort_by_key(|d| d.primary.span.start);
(out, suppressed)
}
fn doc_meaning_of(&self, i: usize) -> (Vec<Diagnostic>, Dropped) {
match self.doc_ids.get(i).and_then(|id| self.tree.get(*id).ok()) {
Some(doc) => self.doc_meaning(i, doc),
None => (Vec::new(), Dropped::default()),
}
}
fn doc_head(&self, i: usize, meaning: &[Diagnostic], fill: &[Diagnostic], need: usize) -> Head {
let mut heap: std::collections::BinaryHeap<Keyed> = std::collections::BinaryHeap::new();
let mut error: Option<Keyed> = None;
let mut seq = 0usize;
let mut n = 0usize;
let mut take = |d: std::borrow::Cow<'_, Diagnostic>| {
let key = (d.primary.span.start, seq);
seq = seq.saturating_add(1);
n = n.saturating_add(1);
let first_error =
d.severity == Severity::Error && error.as_ref().is_none_or(|e| key < e.key);
let in_head =
need > 0 && (heap.len() < need || heap.peek().is_some_and(|top| key < top.key));
if in_head {
let d = d.into_owned();
if first_error {
error = Some(Keyed { key, d: d.clone() });
}
if heap.len() >= need {
heap.pop();
}
heap.push(Keyed { key, d });
} else if first_error {
error = Some(Keyed {
key,
d: d.into_owned(),
});
}
};
self.region_tree_each(i, fill, &mut take);
meaning
.iter()
.for_each(|d| take(std::borrow::Cow::Borrowed(d)));
Head {
list: heap.into_sorted_vec().into_iter().map(|k| k.d).collect(),
first_error: Some(error.map(|k| k.d)),
n,
}
}
fn doc_head_filled(&self, i: usize, meaning: &[Diagnostic], need: usize) -> Head {
let head = self.doc_head(i, meaning, &[], need);
let shown = head.list.iter().any(SyntaxTree::is_skeleton)
|| head
.first_error
.as_ref()
.and_then(Option::as_ref)
.is_some_and(SyntaxTree::is_skeleton);
if !shown {
return head;
}
let Some((start, text)) = self.region_text(i) else {
return head;
};
drop(head);
#[cfg(test)]
tests::count_fill();
let again = SyntaxTree::parse_with(text, &self.settings);
let mut fill: Vec<Diagnostic> = again
.diagnostics()
.filter(|d| !SyntaxTree::is_skeleton(d))
.map(|d| moved_to_end(&d, 0, start))
.collect();
drop(again);
fill.sort_by_key(|d| d.primary.span.start);
self.doc_head(i, meaning, &fill, need)
}
fn region_text(&self, i: usize) -> Option<(usize, String)> {
let d = self.doc_ids.get(i).and_then(|id| self.tree.get(*id).ok())?;
let r = self.region_of(d);
let is_doc = |n: &TreeNode<'_>| n.kind() == SyntaxKind::Document;
let mut first = d;
while let Some(p) = first.prev_sibling().filter(|p| !is_doc(p)) {
first = p;
}
let mut text = String::new();
let mut at = Some(first);
while let Some(top) = at {
if top != d && is_doc(&top) {
break;
}
for n in top.descendants().filter(|n| n.is_token()) {
let s = n.span();
if r.start <= s.start && s.end <= r.end {
text.push_str(&n.text());
}
}
at = top.next_sibling();
}
Some((r.start, text))
}
fn edited_stream_diagnostics(&self) -> Vec<Diagnostic> {
if self.doc_ids.is_empty() {
return self.fresh_diagnostics();
}
if self.cap_possible() {
return self.capped_view().to_vec();
}
let mut out: Vec<Diagnostic> = self
.diagnostics
.get(..self.pre)
.unwrap_or_default()
.to_vec();
for i in 0..self.doc_ids.len() {
out.extend(self.edited_doc_events(i).0);
}
out
}
fn lines(&self) -> &LineIndex {
self.lines.get_or_init(|| LineIndex::new(&self.text()))
}
fn pre_error(&self, index: Option<usize>) -> Option<Error> {
let d = match self.runs(index).next() {
Some(r) => oneyaml_syntax::run_diagnostic(self.origin.encoder.encoding(), &r),
None => self
.settings_diags
.iter()
.find(|d| d.severity == Severity::Error)?
.clone(),
};
let e = Error::from_diagnostic(d).locate(self.lines());
Some(e.finish(Some(&self.text()), Vec::new()))
}
fn runs(&self, index: Option<usize>) -> impl ExactSizeIterator<Item = InvalidRun<'_>> + '_ {
let runs = &self.origin.invalid;
let (lo, hi) = match index {
None => (0, runs.len()),
Some(i) => {
let (from, to) = (self.region_start(i), self.region_start(i.saturating_add(1)));
let lo = runs.first_at(from);
(lo, runs.first_at(to).max(lo))
}
};
runs.iter().skip(lo).take(hi.saturating_sub(lo))
}
fn settings_error(&self) -> bool {
self.settings_diags
.iter()
.any(|d| d.severity == Severity::Error)
}
fn marks(&self, index: Option<usize>) -> &[(usize, usize, Code)] {
let all = self.error_marks.as_slice();
let Some(i) = index else {
return all;
};
let (from, to) = (self.region_start(i), self.region_start(i.saturating_add(1)));
let lo = all.partition_point(|m| m.0 < from);
let hi = all.partition_point(|m| m.0 < to).max(lo);
all.get(lo..hi).unwrap_or_default()
}
fn documents(&self) -> impl Iterator<Item = TreeNode<'_>> + '_ {
self.tree
.root()
.children()
.filter(|n| n.kind() == SyntaxKind::Document)
}
fn strict_check(&self, single: bool) -> Result<Found, Error> {
if let Some(e) = self.pre_error(None) {
return Err(e);
}
let mut events = TreeSource::new(&self.tree);
let found = crate::de::check_unexpanded(&mut events, &self.settings, single, false);
match found.errors.into_iter().next() {
Some(e) => {
let (mut w, tree_dropped) = events.take_warnings();
w.extend(found.warnings);
w.sort_by_key(|d| d.primary.span.start);
let extra = found.suppressed.and(tree_dropped);
crate::diag_cap::cap(&mut w, extra, self.limit());
Err(e.finish(Some(self.tree.source()), w))
}
None => Ok(Found {
list: found.warnings,
dropped: found.suppressed,
dropped_errors: found.dropped_errors,
}),
}
}
fn recovering_check(&self) -> Found {
let mut events = TreeSource::new(&self.tree);
let found = crate::de::check_unexpanded(&mut events, &self.settings, false, true);
let mut out = found.warnings;
out.extend(found.errors.iter().map(|e| e.diagnostic().clone()));
Found {
list: out,
dropped: found.suppressed,
dropped_errors: found.dropped_errors,
}
}
fn region_start(&self, i: usize) -> usize {
if i >= self.doc_ids.len() {
return usize::MAX;
}
let Some(p) = i.checked_sub(1) else {
return 0;
};
let node = |j: usize| self.doc_ids.get(j).and_then(|id| self.tree.get(*id).ok());
match (node(p), node(i)) {
(Some(prev), Some(this)) => self.split_between(prev, this),
_ => self.doc_span(i).map_or(usize::MAX, |s| s.start),
}
}
fn owned<'s>(&self, list: &'s [Diagnostic], i: usize) -> &'s [Diagnostic] {
let (from, to) = (self.region_start(i), self.region_start(i.saturating_add(1)));
let lo = list.partition_point(|d| d.primary.span.start < from);
let hi = list.partition_point(|d| d.primary.span.start < to).max(lo);
list.get(lo..hi).unwrap_or_default()
}
fn add_diagnostics(&mut self, found: Found) {
for d in found.list.iter().filter(|d| d.severity == Severity::Error) {
self.error_marks
.push((d.primary.span.start, d.primary.span.end, d.code));
}
for (code, span) in &found.dropped_errors {
self.error_marks.push((span.start, span.end, *code));
}
self.error_marks
.sort_unstable_by_key(|m| (m.0, m.1, m.2.get()));
self.diagnostics.extend(found.list);
if let Some(events) = self.diagnostics.get_mut(self.pre..) {
events.sort_by_key(|d| d.primary.span.start);
}
let extra = found.dropped.and(self.dropped);
self.found = self.diagnostics.len().saturating_add(extra.count);
let limit = self.limit();
if let Some(cut) = crate::diag_cap::cap(&mut self.diagnostics, extra, limit) {
self.decoded = cut.before(self.decoded);
self.pre = cut.before(self.pre);
self.capped = true;
}
}
fn multiple_documents(&self) -> Option<Diagnostic> {
let second = self.documents().nth(1)?;
let e = Error::new(
code::MULTIPLE_DOCUMENTS,
"the input has more than one document",
Some(crate::construct::tree::doc_start_span(second)),
)
.with_help("use oneyaml::Stream to read each document");
Some(e.diagnostic().clone())
}
#[cfg(test)]
fn first_error(&self, single: bool) -> Option<Error> {
let src = self.tree.source();
if let Some(e) = self.pre_error(None) {
return Some(e);
}
if !self.tree.has_errors() && (!single || self.documents().nth(1).is_none()) {
return None;
}
let mut events = TreeSource::new(&self.tree);
let mut docs = 0usize;
loop {
match events.next_event() {
Ok(e) => match e.node {
Ev::StreamEnd => return None,
Ev::DocStart { .. } if single && docs == 1 => {
let e = Error::new(
code::MULTIPLE_DOCUMENTS,
"the input has more than one document",
Some(e.span),
)
.with_help("use oneyaml::Stream to read each document");
return Some(e.finish(Some(src), events.take_warnings().0));
}
Ev::DocStart { .. } => docs = docs.saturating_add(1),
_ => {}
},
Err(e) => return Some(e.finish(Some(src), events.take_warnings().0)),
}
}
}
}
struct Memo<'s> {
shared: &'s Shared,
boundary: std::cell::Cell<Option<(usize, usize)>>,
room: std::cell::Cell<usize>,
kept: std::cell::RefCell<std::collections::BTreeMap<usize, (Vec<Diagnostic>, Dropped)>>,
}
impl<'s> Memo<'s> {
fn new(shared: &'s Shared, keep: usize) -> Self {
Self {
shared,
boundary: std::cell::Cell::new(None),
room: std::cell::Cell::new(keep),
kept: std::cell::RefCell::new(std::collections::BTreeMap::new()),
}
}
fn take(&self, j: usize) -> Option<(Vec<Diagnostic>, Dropped)> {
self.kept.try_borrow_mut().ok()?.remove(&j)
}
}
impl cap_index::Parts for Memo<'_> {
fn syntax_part(&self, i: usize) -> cap_index::Agg {
let s = self.shared;
let from = match self.boundary.get() {
Some((j, at)) if j == i => at,
_ => s.region_start(i),
};
let next = i.saturating_add(1);
let to = s.region_start(next);
self.boundary.set(Some((next, to)));
let (n, errors) = s.region_tree_counts(i, Some((from, to)));
cap_index::Agg {
n,
errors,
dropped: Dropped::default(),
}
}
fn meaning_part(&self, i: usize) -> cap_index::Agg {
let (list, dropped) = self.shared.doc_meaning_of(i);
let agg = cap_index::Agg {
n: list.len(),
errors: list
.iter()
.filter(|d| d.severity == Severity::Error)
.count(),
dropped,
};
if let Some(room) = self
.room
.get()
.checked_sub(list.len())
.filter(|_| !list.is_empty())
{
if let Ok(mut kept) = self.kept.try_borrow_mut() {
self.room.set(room);
kept.insert(i, (list, dropped));
}
}
agg
}
}
struct Placed {
doc: usize,
at: usize,
len: usize,
error: Option<usize>,
}
struct Head {
list: Vec<Diagnostic>,
first_error: Option<Option<Diagnostic>>,
n: usize,
}
struct Keyed {
key: (usize, usize),
d: Diagnostic,
}
impl PartialEq for Keyed {
fn eq(&self, other: &Self) -> bool {
self.key == other.key
}
}
impl Eq for Keyed {}
impl PartialOrd for Keyed {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Keyed {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.key.cmp(&other.key)
}
}
struct Found {
list: Vec<Diagnostic>,
dropped: Dropped,
dropped_errors: Vec<(Code, Span)>,
}
#[must_use]
#[derive(Clone, Debug)]
pub struct Parsed<T> {
value: T,
diagnostics: Vec<Diagnostic>,
}
impl<T> Parsed<T> {
pub fn value(&self) -> &T {
&self.value
}
pub fn into_value(self) -> T {
self.value
}
#[must_use]
pub fn diagnostics(&self) -> &[Diagnostic] {
&self.diagnostics
}
#[must_use]
pub fn has_errors(&self) -> bool {
self.diagnostics
.iter()
.any(|d| d.severity == Severity::Error)
}
#[must_use]
pub fn render_diagnostics(&self, src: &str) -> String {
let index = LineIndex::new(src);
let rendered: Vec<String> = self
.diagnostics
.iter()
.map(|d| crate::Render::render_indexed(d, src, &index))
.collect();
rendered.join("\n")
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct NodeId(oneyaml_syntax::tree::NodeId);
pub struct Document {
shared: Arc<Shared>,
doc: Option<oneyaml_syntax::tree::NodeId>,
index: Option<usize>,
id: u64,
guard: Option<edit::Guard>,
ix_log: Vec<edit::IxOp>,
explicit: Option<Arc<comments::Explicit>>,
explicit_log: Vec<comments::ExplicitUndo>,
tail: edit::TailGuard,
error_neighbour: std::sync::atomic::AtomicBool,
}
impl core::fmt::Debug for Document {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Document")
.field("index", &self.index)
.field("span", &self.span())
.field("edited", &self.shared.edited)
.field("encoding", &self.encoding())
.field("settings", self.settings())
.finish_non_exhaustive()
}
}
static NEXT_DOCUMENT: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
fn document_id() -> u64 {
NEXT_DOCUMENT.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
}
impl Clone for Document {
fn clone(&self) -> Self {
Self {
shared: Arc::clone(&self.shared),
doc: self.doc,
index: self.index,
id: document_id(),
guard: self.guard.clone(),
ix_log: Vec::new(),
explicit_log: Vec::new(),
tail: edit::TailGuard::default(),
error_neighbour: std::sync::atomic::AtomicBool::new(false),
explicit: self.explicit.clone(),
}
}
}
impl Document {
fn whole(shared: Shared) -> Self {
let doc = shared.documents().next().map(TreeNode::id);
Self {
shared: Arc::new(shared),
doc,
index: None,
id: document_id(),
guard: None,
ix_log: Vec::new(),
explicit_log: Vec::new(),
tail: edit::TailGuard::default(),
error_neighbour: std::sync::atomic::AtomicBool::new(false),
explicit: None,
}
}
fn of_stream(
shared: &Arc<Shared>,
doc: Option<oneyaml_syntax::tree::NodeId>,
index: Option<usize>,
) -> Self {
Self {
shared: Arc::clone(shared),
doc,
index,
id: document_id(),
guard: None,
ix_log: Vec::new(),
explicit_log: Vec::new(),
tail: edit::TailGuard::default(),
error_neighbour: std::sync::atomic::AtomicBool::new(false),
explicit: None,
}
}
fn strict(mut shared: Shared) -> Result<Self, Error> {
let found = shared.strict_check(true)?;
shared.add_diagnostics(found);
shared.mode = Mode::Strict { single: true };
Ok(Self::whole(shared))
}
fn recovering(mut shared: Shared) -> Parsed<Self> {
shared.mode = Mode::Recovering { single: true };
let mut found = shared.recovering_check();
found.list.extend(shared.multiple_documents());
shared.add_diagnostics(found);
let diagnostics = shared.diagnostics.clone();
Parsed {
value: Self::whole(shared),
diagnostics,
}
}
pub fn parse(src: impl Into<String>) -> Result<Self, Error> {
Self::parse_with(src, &Settings::new())
}
pub fn parse_with(src: impl Into<String>, settings: &Settings) -> Result<Self, Error> {
Self::strict(Shared::from_str(src.into(), settings))
}
pub fn parse_recovering(src: impl Into<String>) -> Parsed<Self> {
Self::parse_recovering_with(src, &Settings::new())
}
pub fn parse_recovering_with(src: impl Into<String>, settings: &Settings) -> Parsed<Self> {
Self::recovering(Shared::from_str(src.into(), settings))
}
pub fn from_slice(bytes: &[u8]) -> Result<Self, Error> {
Self::from_slice_with(bytes, &Settings::new())
}
pub fn from_slice_with(bytes: &[u8], settings: &Settings) -> Result<Self, Error> {
Self::strict(Shared::from_slice_strict(bytes, settings)?)
}
pub fn from_slice_recovering(bytes: &[u8], settings: &Settings) -> Parsed<Self> {
Self::recovering(Shared::from_slice(bytes, settings))
}
#[must_use]
pub fn settings(&self) -> &Settings {
&self.shared.settings
}
#[must_use]
pub fn diagnostics(&self) -> Vec<Diagnostic> {
if self.shared.edited {
return self.edited_diagnostics();
}
self.own_diagnostics().cloned().collect()
}
#[must_use]
pub fn render_diagnostics(&self) -> String {
let text = self.shared.text();
let index = self.shared.lines();
let rendered: Vec<String> = self
.diagnostics()
.iter()
.map(|d| crate::Render::render_indexed(d, &text, index))
.collect();
rendered.join("\n")
}
fn edited_diagnostics(&self) -> Vec<Diagnostic> {
let s = &self.shared;
let Some(i) = self.index else {
return s.fresh_diagnostics();
};
if s.cap_possible() {
return s.capped_doc(i);
}
let settings = s.diagnostics.get(s.decoded..s.pre).unwrap_or_default();
let mut out = settings.to_vec();
out.extend(s.edited_doc_events(i).0);
out
}
fn own_diagnostics(&self) -> impl Iterator<Item = &Diagnostic> + '_ {
let s = &self.shared;
let all = s.diagnostics.as_slice();
let decoded = all.get(..s.decoded).unwrap_or_default();
let settings = all.get(s.decoded..s.pre).unwrap_or_default();
let events = all.get(s.pre..).unwrap_or_default();
let (decoded, events) = match self.index {
None => (decoded, events),
Some(i) => (s.owned(decoded, i), s.owned(events, i)),
};
decoded.iter().chain(settings).chain(events)
}
#[must_use]
pub fn has_errors(&self) -> bool {
let s = &self.shared;
if self
.index
.is_some_and(|i| s.error_docs.binary_search(&i).is_ok())
|| s.settings_error()
{
return true;
}
let error = |d: &Diagnostic| d.severity == Severity::Error;
match (self.index, s.edited) {
(None, true) => s.tree.has_errors() || s.meaning().list.iter().any(error),
(Some(i), true) => s.edited_doc_events(i).0.iter().any(error),
(index, false) => !s.marks(index).is_empty() || s.runs(index).len() > 0,
}
}
fn own_errors(&self) -> Vec<(Code, Span)> {
let s = &self.shared;
let mut out: Vec<(Code, Span)> = s
.settings_diags
.iter()
.filter(|d| d.severity == Severity::Error)
.map(|d| (d.code, d.primary.span))
.collect();
let key = |d: &Diagnostic| (d.code, d.primary.span);
match (self.index, s.edited) {
(None, true) => {
out.extend(
s.tree
.diagnostics()
.filter(|d| d.severity == Severity::Error)
.map(|d| key(&d)),
);
let found = s.meaning();
out.extend(
found
.list
.iter()
.filter(|d| d.severity == Severity::Error)
.map(key),
);
out.extend(found.dropped_errors);
}
(Some(i), true) => out.extend(
s.edited_doc_events(i)
.0
.iter()
.filter(|d| d.severity == Severity::Error)
.map(key),
),
(index, false) => {
let code = invalid_code(s.origin.encoder.encoding());
out.extend(s.runs(index).map(|r| (code, r.text)));
out.extend(s.marks(index).iter().map(|m| (m.2, Span::new(m.0, m.1))));
}
}
out
}
#[must_use]
pub fn syntax(&self) -> &SyntaxTree {
&self.shared.tree
}
fn doc_node(&self) -> Option<TreeNode<'_>> {
self.shared.tree.get(self.doc?).ok()
}
#[must_use]
pub fn span(&self) -> Span {
if self.index.is_none() {
return self.shared.tree.root().span();
}
self.doc_node()
.map_or(Span::empty(0), |d| self.shared.region_of(d))
}
#[must_use]
pub fn root(&self) -> Option<NodeRef<'_>> {
let doc = self.doc_node()?;
doc.children()
.find(|c| c.kind().is_yaml_node())
.map(|n| NodeRef::new(self, view::presented(n)))
}
pub fn errors(&self) -> impl Iterator<Item = NodeRef<'_>> + '_ {
let top = if self.index.is_none() {
Some(self.shared.tree.root())
} else {
self.doc_node()
};
top.into_iter()
.flat_map(TreeNode::descendants)
.filter(|n| n.kind() == SyntaxKind::Error)
.map(|n| NodeRef::new(self, n))
}
#[must_use]
pub fn get<K: AsKey>(&self, key: K) -> Option<NodeRef<'_>> {
self.root()?.get(key)
}
#[must_use]
pub fn get_path<P: IntoPath>(&self, path: P) -> Option<NodeRef<'_>> {
self.root()?.get_path(path)
}
pub(super) fn walk_path(&self, path: &Path) -> Result<NodeRef<'_>, EditError> {
let root = self
.root()
.ok_or_else(|| EditError::NotFound { at: Path::new() })?;
root.walk(path)
}
#[must_use]
pub fn id_of<P: IntoPath>(&self, path: P) -> Option<NodeId> {
self.get_path(path).map(|n| n.id())
}
pub fn node(&self, id: NodeId) -> Result<NodeRef<'_>, EditError> {
let stale = EditError::StaleNode;
let n = self.shared.tree.get(id.0).map_err(|_| stale.clone())?;
let inside = self
.doc_node()
.is_some_and(|d| n.ancestors().any(|a| a == d));
if !inside || !(n.kind().is_yaml_node() || n.kind() == SyntaxKind::Error) {
return Err(stale);
}
Ok(NodeRef::new(self, n))
}
pub fn to_value(&self) -> Result<Value, Error> {
let tree = &self.shared.tree;
let settings = &self.shared.settings;
if let Some(e) = self.shared.pre_error(self.index) {
return Err(e);
}
let doc = self.doc_node().filter(|_| self.index.is_some());
let source = || {
match doc {
Some(d) => TreeSource::over(tree, d),
None => TreeSource::new(tree),
}
.with_doc_tags(&self.shared.tags)
};
let lines = Some(&self.shared.lines);
crate::de::read_discarding::<_, Value>(source, || self.shared.text(), settings, lines)
}
#[must_use]
pub fn to_bytes(&self) -> Vec<u8> {
let text = self.to_string();
let origin = &self.shared.origin;
let base = if self.index.is_none() {
0
} else {
self.span().start
};
let end = base.saturating_add(text.len());
let mut out = Vec::with_capacity(text.len());
let mut at = 0usize;
let lo = origin.invalid.first_at(base);
let inside = origin
.invalid
.iter()
.skip(lo)
.take_while(|r| r.text.start <= end)
.filter(|r| r.text.end <= end);
for run in inside {
let start = run.text.start.saturating_sub(base);
let before = text.get(at..start).unwrap_or("");
out.extend(origin.encoder.encode(before));
out.extend_from_slice(run.bytes);
at = run.text.end.saturating_sub(base);
}
out.extend(origin.encoder.encode(text.get(at..).unwrap_or("")));
out
}
#[must_use]
pub fn encoding(&self) -> Encoding {
self.shared.origin.encoder.encoding()
}
fn nodes_before(&self, document: TreeNode<'_>, node: TreeNode<'_>) -> usize {
let list = self.shared.node_events.get_or_init(|| {
self.shared
.tree
.root()
.descendants()
.enumerate()
.filter(|(_, n)| crate::construct::tree::is_node_event(n.kind()))
.filter_map(|(i, _)| u32::try_from(i).ok())
.collect()
});
let before =
|at: usize| list.partition_point(|&p| usize::try_from(p).is_ok_and(|p| p < at));
before(node.preorder_index()).saturating_sub(before(document.preorder_index()))
}
fn anchors(&self) -> &anchors::Index {
self.shared.anchor_index()
}
fn doc_range(&self) -> (usize, usize) {
match self.doc_node() {
Some(d) => (
d.preorder_index(),
place::next_token(d).map_or(usize::MAX, TreeNode::preorder_index),
),
None => (0, usize::MAX),
}
}
}
impl core::fmt::Display for Document {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let tree = &self.shared.tree;
let Some(d) = self.doc_node().filter(|_| self.index.is_some()) else {
if self.index.is_none() {
for n in tree.root().descendants() {
if n.is_token() {
f.write_str(&n.text())?;
}
}
}
return Ok(());
};
let r = self.shared.region_of(d);
let is_doc = |n: &TreeNode<'_>| n.kind() == SyntaxKind::Document;
let mut first = d;
while let Some(p) = first.prev_sibling().filter(|p| !is_doc(p)) {
first = p;
}
let mut at = Some(first);
while let Some(top) = at {
if top != d && is_doc(&top) {
break;
}
let mut pos = top.span().start;
for n in top.descendants().filter(|n| n.is_token()) {
let text = n.text();
let start = pos;
pos = pos.saturating_add(text.len());
if r.start <= start && pos <= r.end {
f.write_str(&text)?;
}
}
at = top.next_sibling();
}
Ok(())
}
}
impl TryFrom<&Document> for Value {
type Error = Error;
fn try_from(doc: &Document) -> Result<Value, Error> {
doc.to_value()
}
}
#[cfg(test)]
mod tests;