use crate::node::Node;
use crate::range::Position;
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum LeafPolicy {
#[default]
Builtin,
Types(HashSet<String>),
}
impl LeafPolicy {
pub fn from_types<I, S>(types: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
LeafPolicy::Types(types.into_iter().map(Into::into).collect())
}
fn is_leaf_type(&self, ty: &str) -> bool {
match self {
LeafPolicy::Builtin => matches!(ty, "image" | "horizontalRule" | "hardBreak"),
LeafPolicy::Types(set) => set.contains(ty),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ResolvedPos {
pub pos: usize,
pub depth: usize,
pub path: Vec<usize>,
pub parent_offset: usize,
pub index: usize,
pub text_offset: Option<TextPoint>,
}
impl ResolvedPos {
pub fn is_in_text(&self) -> bool {
self.text_offset.is_some()
}
pub fn parent<'a>(&self, root: &'a Node) -> Option<&'a Node> {
root.node_at(&self.path)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TextPoint {
pub path: Vec<usize>,
pub offset: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct PosRange {
pub from: usize,
pub to: usize,
}
impl PosRange {
pub fn new(from: usize, to: usize) -> Self {
Self { from, to }
}
pub fn collapsed(at: usize) -> Self {
Self { from: at, to: at }
}
pub fn is_empty(&self) -> bool {
self.to <= self.from
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PosError {
OutOfRange {
pos: usize,
size: usize,
},
PathNotFound {
path: Vec<usize>,
},
OffsetOutOfRange {
path: Vec<usize>,
offset: usize,
},
}
impl fmt::Display for PosError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PosError::OutOfRange { pos, size } => {
write!(f, "pos: {pos} out of range (document size {size})")
}
PosError::PathNotFound { path } => write!(f, "pos: no node at path {path:?}"),
PosError::OffsetOutOfRange { path, offset } => {
write!(f, "pos: child index {offset} out of range at {path:?}")
}
}
}
}
impl std::error::Error for PosError {}
impl Node {
pub fn node_size(&self) -> usize {
self.node_size_with(&LeafPolicy::Builtin)
}
pub fn node_size_with(&self, policy: &LeafPolicy) -> usize {
if let Some(t) = &self.text {
return t.chars().count();
}
if self.is_leaf_with(policy) {
return 1;
}
2 + self.content_size_with(policy)
}
pub fn content_size(&self) -> usize {
self.content_size_with(&LeafPolicy::Builtin)
}
pub fn content_size_with(&self, policy: &LeafPolicy) -> usize {
self.children()
.iter()
.map(|c| c.node_size_with(policy))
.sum()
}
pub fn is_leaf(&self) -> bool {
self.is_leaf_with(&LeafPolicy::Builtin)
}
pub fn is_leaf_with(&self, policy: &LeafPolicy) -> bool {
self.text.is_none()
&& self
.node_type
.as_deref()
.is_some_and(|t| policy.is_leaf_type(t))
}
pub fn resolve(&self, pos: usize) -> Result<ResolvedPos, PosError> {
self.resolve_with(pos, &LeafPolicy::Builtin)
}
pub fn resolve_with(&self, pos: usize, policy: &LeafPolicy) -> Result<ResolvedPos, PosError> {
let total = self.content_size_with(policy);
if pos > total {
return Err(PosError::OutOfRange { pos, size: total });
}
let mut path: Vec<usize> = Vec::new();
let mut offset = pos;
'walk: loop {
let node = self.node_at(&path).expect("resolved path stays valid");
let children = node.children();
let mut i = 0usize;
let mut rem = offset;
loop {
if i == children.len() || rem == 0 {
return Ok(ResolvedPos {
pos,
depth: path.len(),
path,
parent_offset: offset,
index: i,
text_offset: None,
});
}
let child = &children[i];
let cs = child.node_size_with(policy);
if rem < cs {
if child.text.is_some() {
let mut tpath = path.clone();
tpath.push(i);
return Ok(ResolvedPos {
pos,
depth: path.len(),
path,
parent_offset: offset,
index: i,
text_offset: Some(TextPoint {
path: tpath,
offset: rem,
}),
});
}
path.push(i);
offset = rem - 1;
continue 'walk;
}
rem -= cs;
i += 1;
}
}
}
pub fn pos_before(&self, path: &[usize]) -> Result<usize, PosError> {
self.pos_before_with(path, &LeafPolicy::Builtin)
}
pub fn pos_before_with(&self, path: &[usize], policy: &LeafPolicy) -> Result<usize, PosError> {
let mut acc = 0usize;
for depth in 0..path.len() {
let parent = self
.node_at(&path[..depth])
.ok_or_else(|| PosError::PathNotFound {
path: path.to_vec(),
})?;
let children = parent.children();
let i = path[depth];
if i >= children.len() {
return Err(PosError::OffsetOutOfRange {
path: path.to_vec(),
offset: i,
});
}
for child in &children[..i] {
acc += child.node_size_with(policy);
}
if depth + 1 < path.len() {
acc += 1;
}
}
Ok(acc)
}
pub fn pos_after(&self, path: &[usize]) -> Result<usize, PosError> {
self.pos_after_with(path, &LeafPolicy::Builtin)
}
pub fn pos_after_with(&self, path: &[usize], policy: &LeafPolicy) -> Result<usize, PosError> {
let before = self.pos_before_with(path, policy)?;
let node = self.node_at(path).ok_or_else(|| PosError::PathNotFound {
path: path.to_vec(),
})?;
Ok(before + node.node_size_with(policy))
}
pub fn pos_in_text(&self, text_path: &[usize], offset: usize) -> Result<usize, PosError> {
let node = self
.node_at(text_path)
.ok_or_else(|| PosError::PathNotFound {
path: text_path.to_vec(),
})?;
let len = match &node.text {
Some(t) => t.chars().count(),
None => {
return Err(PosError::OffsetOutOfRange {
path: text_path.to_vec(),
offset,
})
}
};
if offset > len {
return Err(PosError::OffsetOutOfRange {
path: text_path.to_vec(),
offset,
});
}
Ok(self.pos_before(text_path)? + offset)
}
pub fn pos_to_inline(&self, pos: usize) -> Result<(Vec<usize>, Position), PosError> {
let r = self.resolve(pos)?;
let inline = match &r.text_offset {
Some(tp) => Position::new(r.index, tp.offset),
None => Position::new(r.index, 0),
};
Ok((r.path, inline))
}
pub fn inline_to_pos(&self, block_path: &[usize], inline: Position) -> Result<usize, PosError> {
let policy = LeafPolicy::Builtin;
let mut acc = self.pos_before_with(block_path, &policy)? + 1;
let block = self
.node_at(block_path)
.ok_or_else(|| PosError::PathNotFound {
path: block_path.to_vec(),
})?;
let children = block.children();
if inline.child > children.len() {
return Err(PosError::OffsetOutOfRange {
path: block_path.to_vec(),
offset: inline.child,
});
}
let offset_ok = match children.get(inline.child) {
Some(child) => match &child.text {
Some(t) => inline.offset <= t.chars().count(),
None => inline.offset == 0,
},
None => inline.offset == 0, };
if !offset_ok {
return Err(PosError::OffsetOutOfRange {
path: block_path.to_vec(),
offset: inline.offset,
});
}
for child in &children[..inline.child] {
acc += child.node_size_with(&policy);
}
Ok(acc + inline.offset)
}
}