use std::{collections::HashMap, convert::TryInto, rc::Rc};
use serde_derive::{Deserialize, Serialize};
use thiserror::Error;
use unicode_segmentation::UnicodeSegmentation;
use crate::{
list::list_translator::ListTranslator,
object::AnyObjectState,
ops::OpID,
view::{
jmbl_view::{JMBLViewRef, TranslatorRef},
value::ValueError,
},
Value,
};
use super::{super::ops::ObjID, text_state::TextRoot};
#[derive(Clone)]
pub struct TextView {
pub(crate) obj_id: ObjID,
ctx: JMBLViewRef,
}
pub struct RawRange {
tag: litl::Val,
starts_after: OpID,
starts_before: OpID,
ends_before: OpID,
ends_after: OpID,
inside: Option<OpID>,
}
impl Into<HashMap<String, Value>> for RawRange {
fn into(self) -> HashMap<String, Value> {
[
("tag".into(), Value::Plain(self.tag)),
(
"starts_after".into(),
Value::Plain(litl::to_val(&self.starts_after).unwrap()),
),
(
"starts_before".into(),
Value::Plain(litl::to_val(&self.starts_before).unwrap()),
),
(
"ends_before".into(),
Value::Plain(litl::to_val(&self.ends_before).unwrap()),
),
(
"ends_after".into(),
Value::Plain(litl::to_val(&self.ends_after).unwrap()),
),
(
"inside".into(),
Value::Plain(litl::to_val(&self.inside).unwrap()),
),
]
.into_iter()
.collect()
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ResolvedRange {
tag: litl::Val,
starts_after: usize,
starts_before: usize,
ends_after: usize,
ends_before: usize,
}
#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum TextNode {
Leaf {
graphemes: Vec<litl::Val>,
},
Node {
tag: litl::Val,
certain: bool,
start: usize,
end: usize,
children: Vec<TextNode>,
},
}
#[derive(Debug)]
pub struct TextValue {
ranges: Vec<ResolvedRange>,
graphemes: Vec<litl::Val>,
}
#[derive(Debug, Error)]
pub enum ParseRangeError {
#[error(transparent)]
LitlError(#[from] litl::ValDeserializerError),
#[error(transparent)]
ValueError(#[from] ValueError),
}
enum UncertaintyStrategy {
LeaveUncertain,
}
impl Default for UncertaintyStrategy {
fn default() -> Self {
UncertaintyStrategy::LeaveUncertain
}
}
impl std::convert::TryFrom<Value> for RawRange {
type Error = ParseRangeError;
fn try_from(value: Value) -> Result<Self, Self::Error> {
let map_view = value.if_map()?;
Ok(RawRange {
tag: map_view.get("tag").if_plain()?.clone(),
starts_after: litl::from_val(map_view.get("starts_after").if_plain()?.clone())?,
starts_before: litl::from_val(map_view.get("starts_before").if_plain()?.clone())?,
ends_after: litl::from_val(map_view.get("ends_after").if_plain()?.clone())?,
ends_before: litl::from_val(map_view.get("ends_before").if_plain()?.clone())?,
inside: litl::from_val(map_view.get("inside").if_plain()?.clone())?,
})
}
}
#[derive(PartialEq, Hash)]
struct TextTranslatorState {
range_translator: Rc<ListTranslator>,
grapheme_translator: Rc<ListTranslator>,
}
impl TextView {
pub(crate) fn new(obj_id: ObjID, ctx: JMBLViewRef) -> TextView {
TextView { obj_id, ctx }
}
fn text_root(&self) -> Option<TextRoot> {
let ctx = self.ctx.get();
ctx.jmbl_state
.objects
.get(&self.obj_id)
.and_then(|obj| match obj {
AnyObjectState::Text(text_obj) => Some(text_obj),
_ => None,
})
.and_then(|text_obj| text_obj.root)
}
fn translator_state(&self) -> Option<TextTranslatorState> {
self.text_root().map(|root| {
let mut ctx = self.ctx.get_mut();
if let (
Some(TranslatorRef::List(range_translator)),
Some(TranslatorRef::List(grapheme_translator)),
) = (
ctx.current_translator_for(&root.ranges),
ctx.current_translator_for(&root.graphemes),
) {
TextTranslatorState {
range_translator,
grapheme_translator,
}
} else {
unreachable!("Range and grapheme translators should exist")
}
})
}
pub fn insert_at(&self, at: usize, to_insert: &str) {
let mut grapheme_view_obj = self
.ctx
.view_for(
&self
.text_root()
.expect("expected text root for insert")
.graphemes,
)
.expect("expected view for graphemes");
let grapheme_view = grapheme_view_obj
.if_list_mut()
.expect("expected list for graphemes");
let mut idx = at;
for grapheme in to_insert.graphemes(true) {
grapheme_view.insert_at(idx, Value::str(grapheme));
idx += 1;
}
}
pub fn delete(&self, from: usize, to: usize) {
assert!(from < to);
let mut grapheme_view_obj = self
.ctx
.view_for(
&self
.text_root()
.expect("expected text root for delete")
.graphemes,
)
.expect("expected view for graphemes");
let grapheme_view = grapheme_view_obj.if_list_mut().unwrap();
for idx in (from..to).rev() {
grapheme_view.delete_at(idx);
}
}
pub fn op_before_index(&self, idx: usize) -> Option<OpID> {
let TextTranslatorState {
grapheme_translator,
..
} = self
.translator_state()
.expect("Expected translator state to get op before index");
grapheme_translator.op_before_index.get(idx).map(|op| op.id)
}
pub fn index_after_op(&self, op_id: &OpID) -> Option<usize> {
let TextTranslatorState {
grapheme_translator,
..
} = self
.translator_state()
.expect("Expected translator state to get index after op");
grapheme_translator.index_after_op_id.get(op_id).cloned()
}
pub fn add_range(&mut self, start_idx: usize, end_idx: usize, tag: litl::Val) -> Value {
let TextTranslatorState {
grapheme_translator,
..
} = self
.translator_state()
.expect("Expected translator state to add range");
let starts_after = grapheme_translator.op_before_index[start_idx.saturating_sub(1)].id;
let starts_before = grapheme_translator.op_before_index[start_idx].id;
let ends_after = grapheme_translator.op_before_index[end_idx.saturating_sub(1)].id;
let ends_before = grapheme_translator.op_before_index[end_idx].id;
let range = RawRange {
tag,
inside: None,
starts_after,
starts_before,
ends_after,
ends_before,
};
let range = self.ctx.create_map::<_, _, HashMap<_, _>>(range.into());
let range_list_id = self.text_root().expect("Should have text root").ranges;
let mut list_view = self
.ctx
.view_for(&range_list_id)
.expect("Should have ranges view");
let range_list = list_view.if_list_mut().unwrap();
range_list.push(range.clone());
range
}
pub fn val(&self) -> Option<TextValue> {
let TextTranslatorState {
range_translator,
grapheme_translator,
} = self.translator_state()?;
Some(TextValue {
graphemes: grapheme_translator.iter_items().cloned().collect(),
ranges: range_translator
.iter_items()
.filter_map(|range_id| {
if let Ok(range_id) = litl::from_val(range_id.clone()) {
let range_obj = self.ctx.obj_id_to_value(&range_id);
if let Ok(raw_range) = TryInto::<RawRange>::try_into(range_obj) {
let resolved_range = ResolvedRange {
tag: raw_range.tag,
starts_after: *grapheme_translator
.index_after_op_id
.get(&raw_range.starts_after)?,
starts_before: *grapheme_translator
.index_after_op_id
.get(&raw_range.starts_before)?,
ends_after: *grapheme_translator
.index_after_op_id
.get(&raw_range.ends_after)?,
ends_before: *grapheme_translator
.index_after_op_id
.get(&raw_range.ends_before)?,
};
Some(resolved_range)
} else {
Some(ResolvedRange {
starts_after: 0,
starts_before: 1,
ends_after: 0,
ends_before: 1,
tag: litl::Val::str("invalid range"),
})
}
} else {
None
}
})
.collect(),
})
}
pub fn val_to_litl(&self) -> litl::Val {
match self.val() {
Some(val) =>
litl::json!({
"graphemes": val.graphemes,
"ranges": val.ranges.iter().map(|range| litl::to_value(range).unwrap()).collect::<Vec<_>>(),
}).into(),
None => litl::Val::null(),
}
}
pub fn to_tree(&self) -> TextNode {
match self.val() {
Some(val) => {
let initial_nodes = val
.ranges
.into_iter()
.flat_map(|range| {
if range.starts_after < range.starts_before.saturating_sub(1) {
Some(TextNode::Node {
tag: range.tag.clone(),
certain: false,
start: range.starts_after + 1,
end: range.starts_before,
children: vec![],
})
} else {
None
}
.into_iter()
.chain(Some(TextNode::Node {
tag: range.tag.clone(),
certain: true,
start: range.starts_before,
end: range.ends_after + 1,
children: vec![],
}))
.chain(
if range.ends_after + 1 < range.ends_before {
Some(TextNode::Node {
tag: range.tag.clone(),
certain: false,
start: range.ends_after + 1,
end: range.ends_before,
children: vec![],
})
} else {
None
},
)
})
.collect();
let root_node = TextNode::Node {
tag: litl::Val::str("root"),
certain: true,
start: 0,
end: val.graphemes.len(),
children: initial_nodes,
};
root_node.fill_with_graphemes(&val.graphemes)
}
None => TextNode::Node {
tag: litl::Val::str("root"),
certain: true,
start: 0,
end: 0,
children: vec![],
},
}
}
}
impl TextNode {
fn fill_with_graphemes(&self, graphemes: &[litl::Val]) -> Self {
match self {
&TextNode::Node {
ref tag,
certain,
start,
end,
ref children,
} => {
let mut current_idx = start;
let mut new_children = Vec::new();
for child in children {
match child {
&TextNode::Node {
start: child_start,
end: child_end,
..
} => {
if child_start > current_idx {
new_children.push(TextNode::Leaf {
graphemes: graphemes[current_idx..child_start].to_vec(),
})
}
new_children.push(child.clone());
current_idx = child_end;
}
TextNode::Leaf {
graphemes: child_graphemes,
} => {
current_idx += child_graphemes.len();
new_children.push(child.clone());
}
}
}
if current_idx < end {
new_children.push(TextNode::Leaf {
graphemes: graphemes[current_idx..end].to_vec(),
})
}
TextNode::Node {
tag: tag.clone(),
certain,
start,
end,
children: new_children
.into_iter()
.map(|child| child.fill_with_graphemes(graphemes))
.collect(),
}
}
TextNode::Leaf { .. } => self.clone(),
}
}
}
impl std::fmt::Debug for TextView {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TextView")
.field("value", &self.val())
.finish()
}
}
impl std::cmp::PartialEq for TextView {
fn eq(&self, other: &Self) -> bool {
self.translator_state() == other.translator_state()
}
}
impl std::cmp::Eq for TextView {}
impl std::hash::Hash for TextView {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.translator_state().hash(state);
}
}