use std::collections::{BTreeMap, HashMap};
use crate::emacs_core::value::{Value, equal_value};
use crate::gc_trace::GcTrace;
#[derive(Clone, Debug)]
pub struct PropertyInterval {
pub start: usize,
pub end: usize,
pub properties: HashMap<Value, Value>,
pub(crate) key_order: Vec<Value>,
}
impl PropertyInterval {
fn new(start: usize, end: usize) -> Self {
Self {
start,
end,
properties: HashMap::new(),
key_order: Vec::new(),
}
}
pub fn with_properties(start: usize, end: usize, properties: HashMap<Value, Value>) -> Self {
let key_order: Vec<Value> = properties.keys().copied().collect();
Self {
start,
end,
properties,
key_order,
}
}
fn insert_property(&mut self, name: Value, value: Value) -> bool {
let already_equal = self
.properties
.get(&name)
.map_or(false, |existing| equal_value(existing, &value, 0));
if already_equal {
return false;
}
let is_new = !self.properties.contains_key(&name);
self.properties.insert(name, value);
if is_new {
self.key_order.insert(0, name);
}
true
}
fn remove_property(&mut self, name: Value) -> Option<Value> {
let result = self.properties.remove(&name);
if result.is_some() {
self.key_order.retain(|k| *k != name);
}
result
}
fn is_empty_props(&self) -> bool {
self.properties.is_empty()
}
pub fn ordered_properties(&self) -> impl Iterator<Item = (Value, &Value)> {
self.key_order
.iter()
.filter_map(move |k| self.properties.get(k).map(|v| (*k, v)))
}
}
fn props_equal(a: &HashMap<Value, Value>, b: &HashMap<Value, Value>) -> bool {
if a.len() != b.len() {
return false;
}
for (key, val_a) in a {
match b.get(key) {
Some(val_b) => {
if !equal_value(val_a, val_b, 0) {
return false;
}
}
None => return false,
}
}
true
}
#[derive(Clone)]
pub struct TextPropertyTable {
intervals: BTreeMap<usize, PropertyInterval>,
}
impl TextPropertyTable {
pub fn new() -> Self {
Self {
intervals: BTreeMap::new(),
}
}
pub fn put_property(&mut self, start: usize, end: usize, name: Value, value: Value) -> bool {
if start >= end {
return false;
}
self.split_at(start);
self.split_at(end);
self.ensure_coverage(start, end);
let mut changed = false;
let keys: Vec<usize> = self
.intervals
.range(start..end)
.map(|(&key, _)| key)
.collect();
for key in keys {
if let Some(interval) = self.intervals.get_mut(&key)
&& interval.insert_property(name, value)
{
changed = true;
}
}
self.merge_adjacent_around(start, end);
changed
}
pub fn get_property(&self, pos: usize, name: Value) -> Option<&Value> {
self.interval_containing(pos)
.and_then(|interval| interval.properties.get(&name))
}
pub fn get_properties(&self, pos: usize) -> HashMap<Value, Value> {
self.interval_containing(pos)
.map(|interval| interval.properties.clone())
.unwrap_or_default()
}
pub fn get_properties_ordered(&self, pos: usize) -> Vec<(Value, Value)> {
self.interval_containing(pos)
.map(|interval| {
interval
.ordered_properties()
.map(|(k, v)| (k, *v))
.collect()
})
.unwrap_or_default()
}
pub fn remove_property(&mut self, start: usize, end: usize, name: Value) -> bool {
if start >= end {
return false;
}
self.split_at(start);
self.split_at(end);
let mut removed = false;
let keys: Vec<usize> = self
.intervals
.range(start..end)
.map(|(&key, _)| key)
.collect();
for key in keys {
if let Some(interval) = self.intervals.get_mut(&key)
&& interval.remove_property(name).is_some()
{
removed = true;
}
}
self.cleanup_range(start, end);
self.merge_adjacent_around(start, end);
removed
}
pub fn remove_all_properties(&mut self, start: usize, end: usize) {
if start >= end {
return;
}
self.split_at(start);
self.split_at(end);
let keys: Vec<usize> = self
.intervals
.range(start..end)
.map(|(&key, _)| key)
.collect();
for key in keys {
if let Some(interval) = self.intervals.get_mut(&key) {
interval.properties.clear();
interval.key_order.clear();
}
}
self.cleanup_range(start, end);
self.merge_adjacent_around(start, end);
}
pub fn next_property_change(&self, pos: usize) -> Option<usize> {
if let Some(mut interval) = self.interval_containing(pos) {
let mut end = interval.end;
while let Some((_, next)) = self
.intervals
.range((
std::ops::Bound::Excluded(interval.start),
std::ops::Bound::Unbounded,
))
.next()
{
if interval.end == next.start && props_equal(&interval.properties, &next.properties)
{
interval = next;
end = next.end;
continue;
}
break;
}
return Some(end);
}
self.intervals
.range((std::ops::Bound::Excluded(pos), std::ops::Bound::Unbounded))
.next()
.map(|(_, interval)| interval.start)
}
pub fn previous_property_change(&self, pos: usize) -> Option<usize> {
if let Some(mut interval) = self.interval_containing(pos.saturating_sub(1))
&& pos <= interval.end
&& interval.start < pos
{
let mut start = interval.start;
while let Some((_, previous)) = self.intervals.range(..interval.start).next_back() {
if previous.end == interval.start
&& props_equal(&previous.properties, &interval.properties)
{
interval = previous;
start = previous.start;
continue;
}
break;
}
return Some(start);
}
self.intervals
.range(..pos)
.next_back()
.map(|(_, interval)| interval.end)
}
pub fn adjust_for_insert(&mut self, pos: usize, len: usize) {
if len == 0 {
return;
}
let mut shifted = BTreeMap::new();
for interval in self.intervals_snapshot() {
if interval.start == pos {
let mut shifted_interval = interval;
shifted_interval.start += len;
shifted_interval.end += len;
shifted.insert(shifted_interval.start, shifted_interval);
} else if interval.start > pos {
let mut shifted_interval = interval;
shifted_interval.start += len;
shifted_interval.end += len;
shifted.insert(shifted_interval.start, shifted_interval);
} else if interval.end > pos {
let mut left = interval.clone();
left.end = pos;
if left.start < left.end {
shifted.insert(left.start, left);
}
let mut right = interval;
right.start = pos + len;
right.end += len;
if right.start < right.end {
shifted.insert(right.start, right);
}
} else {
shifted.insert(interval.start, interval);
}
}
self.intervals = shifted;
}
pub fn adjust_for_delete(&mut self, start: usize, end: usize) {
if start >= end {
return;
}
let len = end - start;
let mut shifted = BTreeMap::new();
for mut interval in self.intervals_snapshot() {
if interval.start >= end {
interval.start -= len;
interval.end -= len;
} else if interval.end <= start {
} else if interval.start >= start && interval.end <= end {
continue;
} else if interval.start < start && interval.end > end {
interval.end -= len;
} else if interval.start < start {
interval.end = start;
} else {
interval.start = start;
interval.end -= len;
}
shifted.insert(interval.start, interval);
}
self.intervals = shifted;
self.merge_adjacent();
}
fn split_at(&mut self, pos: usize) {
let Some((&start, interval)) = self.intervals.range(..pos).next_back() else {
return;
};
if !(interval.start < pos && pos < interval.end) {
return;
}
let second = PropertyInterval {
start: pos,
end: interval.end,
properties: interval.properties.clone(),
key_order: interval.key_order.clone(),
};
if let Some(first) = self.intervals.get_mut(&start) {
first.end = pos;
}
self.intervals.insert(pos, second);
}
fn ensure_coverage(&mut self, start: usize, end: usize) {
let mut gaps = Vec::new();
let mut cursor = start;
for interval in self
.intervals
.range(start..end)
.map(|(_, interval)| interval)
{
if interval.start > cursor {
gaps.push((cursor, interval.start));
}
if interval.end > cursor {
cursor = interval.end;
}
}
if cursor < end {
gaps.push((cursor, end));
}
for (gap_start, gap_end) in gaps {
self.intervals
.insert(gap_start, PropertyInterval::new(gap_start, gap_end));
}
}
fn cleanup(&mut self) {
self.intervals.retain(|_, iv| !iv.is_empty_props());
}
fn cleanup_range(&mut self, start: usize, end: usize) {
let keys: Vec<usize> = self
.intervals
.range(start..end)
.filter_map(|(&key, interval)| interval.is_empty_props().then_some(key))
.collect();
for key in keys {
self.intervals.remove(&key);
}
}
fn merge_adjacent(&mut self) {
if self.intervals.len() < 2 {
return;
}
let mut merged = BTreeMap::new();
let mut current: Option<PropertyInterval> = None;
for interval in self.intervals.values().cloned() {
match current.take() {
None => current = Some(interval),
Some(mut active) => {
if active.end == interval.start
&& props_equal(&active.properties, &interval.properties)
{
active.end = interval.end;
current = Some(active);
} else {
merged.insert(active.start, active);
current = Some(interval);
}
}
}
}
if let Some(interval) = current {
merged.insert(interval.start, interval);
}
self.intervals = merged;
}
fn merge_adjacent_around(&mut self, start: usize, end: usize) {
if self.intervals.len() < 2 {
return;
}
let mut keys = Vec::new();
if let Some((&key, _)) = self.intervals.range(..start).next_back() {
keys.push(key);
}
keys.extend(self.intervals.range(start..=end).map(|(&key, _)| key));
if let Some((&key, _)) = self
.intervals
.range((std::ops::Bound::Excluded(end), std::ops::Bound::Unbounded))
.next()
{
keys.push(key);
}
keys.sort_unstable();
keys.dedup();
if keys.len() < 2 {
return;
}
let mut intervals: Vec<PropertyInterval> = keys
.into_iter()
.filter_map(|key| self.intervals.remove(&key))
.collect();
intervals.sort_by_key(|interval| interval.start);
let mut merged: Vec<PropertyInterval> = Vec::with_capacity(intervals.len());
for interval in intervals {
if let Some(active) = merged.last_mut()
&& active.end == interval.start
&& props_equal(&active.properties, &interval.properties)
{
active.end = interval.end;
continue;
}
merged.push(interval);
}
for interval in merged {
self.intervals.insert(interval.start, interval);
}
}
fn interval_containing(&self, pos: usize) -> Option<&PropertyInterval> {
let (_, interval) = self.intervals.range(..=pos).next_back()?;
(interval.start <= pos && pos < interval.end).then_some(interval)
}
pub fn intervals_snapshot(&self) -> Vec<PropertyInterval> {
self.intervals.values().cloned().collect()
}
pub fn is_empty(&self) -> bool {
self.intervals.is_empty()
}
pub fn slice(&self, start: usize, end: usize) -> TextPropertyTable {
if start >= end {
return TextPropertyTable::new();
}
let mut result = BTreeMap::new();
for iv in self.intervals.values() {
if iv.end <= start || iv.start >= end {
continue;
}
let new_start = iv.start.max(start) - start;
let new_end = iv.end.min(end) - start;
if new_start < new_end && !iv.properties.is_empty() {
result.insert(
new_start,
PropertyInterval {
start: new_start,
end: new_end,
properties: iv.properties.clone(),
key_order: iv.key_order.clone(),
},
);
}
}
TextPropertyTable { intervals: result }
}
pub fn append_shifted(&mut self, other: &TextPropertyTable, byte_offset: usize) {
for iv in other.intervals.values() {
if iv.properties.is_empty() {
continue;
}
let start = iv.start + byte_offset;
self.intervals.insert(
start,
PropertyInterval {
start,
end: iv.end + byte_offset,
properties: iv.properties.clone(),
key_order: iv.key_order.clone(),
},
);
}
self.merge_adjacent();
}
pub(crate) fn dump_intervals(&self) -> Vec<PropertyInterval> {
self.intervals_snapshot()
}
pub(crate) fn from_dump(intervals: Vec<PropertyInterval>) -> Self {
Self {
intervals: intervals
.into_iter()
.map(|interval| (interval.start, interval))
.collect(),
}
}
pub(crate) fn for_each_root(&self, mut f: impl FnMut(Value)) {
for interval in self.intervals.values() {
for key in interval.properties.keys() {
f(*key);
}
for value in interval.properties.values() {
f(*value);
}
}
}
}
impl Default for TextPropertyTable {
fn default() -> Self {
Self::new()
}
}
impl GcTrace for TextPropertyTable {
fn trace_roots(&self, roots: &mut Vec<Value>) {
self.for_each_root(|value| roots.push(value));
}
}
#[cfg(test)]
#[path = "text_props_test.rs"]
mod tests;