use rdom_core::{Dom, NodeId, NodeType, Range};
use crate::ext::TuiExt;
use crate::runtime::selection::user_select;
pub fn serialize_selection(dom: &Dom<TuiExt>, range: &Range) -> String {
let mut out = String::new();
let mut state = WalkState::Before;
visit(dom, dom.root(), range, &mut state, &mut out);
out
}
enum WalkState {
Before,
Inside,
Done,
}
fn visit(dom: &Dom<TuiExt>, id: NodeId, range: &Range, state: &mut WalkState, out: &mut String) {
if matches!(state, WalkState::Done) {
return;
}
if user_select::has_none_ancestor(dom, id) {
return;
}
match dom.node(id).node_type() {
NodeType::Text => {
append_text(dom, id, range, state, out);
}
_ => {
let children: Vec<NodeId> = dom.node(id).child_nodes().map(|c| c.id()).collect();
for child in children {
visit(dom, child, range, state, out);
if matches!(state, WalkState::Done) {
return;
}
}
}
}
}
fn append_text(
dom: &Dom<TuiExt>,
id: NodeId,
range: &Range,
state: &mut WalkState,
out: &mut String,
) {
let Some(data) = dom.node(id).node_value() else {
return;
};
let is_start = range.start.node == id;
let is_end = range.end.node == id;
match (is_start, is_end, &*state) {
(true, true, _) => {
let slice = slice_bytes(data, range.start.offset, range.end.offset);
out.push_str(slice);
*state = WalkState::Done;
}
(true, false, _) => {
let slice = &data[range.start.offset.min(data.len())..];
out.push_str(slice);
*state = WalkState::Inside;
}
(false, true, WalkState::Inside) => {
let slice = &data[..range.end.offset.min(data.len())];
out.push_str(slice);
*state = WalkState::Done;
}
(false, false, WalkState::Inside) => {
out.push_str(data);
}
_ => {
}
}
}
fn slice_bytes(data: &str, start: usize, end: usize) -> &str {
let s = start.min(data.len());
let e = end.min(data.len()).max(s);
&data[s..e]
}