use rdom_core::{Dom, NodeId, Position, Range};
use crate::ext::TuiExt;
use crate::render::inline::{InlineFragment, cells_before_byte};
use crate::render::paint_pass::text::style_from_computed;
use crate::render::{Buffer, Rect, Style};
use crate::style::ComputedStyle;
pub(super) fn apply_selection_overlay(
dom: &Dom<TuiExt>,
buf: &mut Buffer,
line_y: u16,
frag_x: i32,
clip: Rect,
fragment: &InlineFragment,
range: &Range,
) {
let Some((byte_start, byte_end)) = selection_byte_range_in(dom, range, fragment.text_node)
else {
return;
};
let frag_start = fragment.source_byte_offset;
let frag_end = fragment.source_byte_offset + fragment.text.len();
let local_start = byte_start.max(frag_start);
let local_end = byte_end.min(frag_end);
if local_start >= local_end {
return;
}
let off_start = local_start - frag_start;
let off_end = local_end - frag_start;
let cell_start = cells_before_byte(&fragment.text, off_start);
let cell_end = cells_before_byte(&fragment.text, off_end);
if cell_start >= cell_end {
return;
}
if crate::style::user_select::is_unselectable(dom, fragment.text_node) {
return;
}
let overlay = match nearest_selection_style(dom, fragment.text_node) {
Some(c) => style_from_computed(c),
None => Style::new(),
};
for c in cell_start..cell_end {
let x = (frag_x + c as i32) as u16;
if x < clip.x || x >= clip.right() {
continue;
}
buf.set_style(x, line_y, overlay);
}
}
fn nearest_selection_style(dom: &Dom<TuiExt>, text_node: NodeId) -> Option<&ComputedStyle> {
let mut cur = dom.node(text_node).parent_node().map(|p| p.id());
while let Some(id) = cur {
if let Some(ext) = dom.node(id).ext()
&& let Some(sel) = ext.computed_selection.as_deref()
{
return Some(sel);
}
cur = dom.node(id).parent_node().map(|p| p.id());
}
None
}
fn selection_byte_range_in(
dom: &Dom<TuiExt>,
range: &Range,
text_node: NodeId,
) -> Option<(usize, usize)> {
let is_start = range.start.node == text_node;
let is_end = range.end.node == text_node;
if is_start && is_end {
return Some((range.start.offset, range.end.offset));
}
let text_len = dom
.node(text_node)
.node_value()
.map(|s| s.len())
.unwrap_or(0);
if is_start {
return Some((range.start.offset, text_len));
}
if is_end {
return Some((0, range.end.offset));
}
use std::cmp::Ordering;
let after_start = matches!(
dom.compare_boundary_points(range.start, Position::new(text_node, 0)),
Some(Ordering::Less | Ordering::Equal)
);
let before_end = matches!(
dom.compare_boundary_points(Position::new(text_node, text_len), range.end),
Some(Ordering::Less | Ordering::Equal)
);
if after_start && before_end {
Some((0, text_len))
} else {
None
}
}