use crate::emacs_core::value::{Value, ValueKind};
use crate::heap_types::LispString;
pub fn undo_list_is_disabled(undo_list: &Value) -> bool {
undo_list.is_t()
}
pub fn undo_list_record_insert(undo_list: &mut Value, beg: usize, len: usize, pt: usize) {
if undo_list_is_disabled(undo_list) || len == 0 {
return;
}
let at_boundary = undo_list.is_nil() || (undo_list.is_cons() && undo_list.cons_car().is_nil());
if at_boundary && pt != beg {
undo_list_record_point(undo_list, pt);
}
let beg1 = (beg + 1) as i64;
let end1 = (beg + len + 1) as i64;
if undo_list.is_cons() {
let head = undo_list.cons_car();
if head.is_cons() {
let car = head.cons_car();
let cdr = head.cons_cdr();
if let (Some(prev_beg), Some(prev_end)) = (car.as_fixnum(), cdr.as_fixnum()) {
if prev_end == beg1 {
head.set_cdr(Value::fixnum(prev_end + len as i64));
return;
}
if prev_beg == end1 {
head.set_car(Value::fixnum(beg1));
return;
}
let _ = (prev_beg, prev_end); }
}
}
let entry = Value::cons(Value::fixnum(beg1), Value::fixnum(end1));
*undo_list = Value::cons(entry, *undo_list);
}
pub fn undo_list_record_delete(undo_list: &mut Value, beg: usize, text: LispString, pt: usize) {
if undo_list_is_disabled(undo_list) || text.is_empty() {
return;
}
let at_boundary = undo_list.is_nil() || (undo_list.is_cons() && undo_list.cons_car().is_nil());
if at_boundary && pt != beg {
undo_list_record_point(undo_list, pt);
}
let pos1 = (beg + 1) as i64;
let stored_pos = if pt == beg + text.sbytes() {
-pos1
} else {
pos1
};
let entry = Value::cons(Value::heap_string(text), Value::fixnum(stored_pos));
*undo_list = Value::cons(entry, *undo_list);
}
pub fn undo_list_record_point(undo_list: &mut Value, pt: usize) {
if undo_list_is_disabled(undo_list) {
return;
}
let pt1 = Value::fixnum((pt + 1) as i64);
if undo_list.is_cons() {
let head = undo_list.cons_car();
if head == pt1 {
return;
}
}
*undo_list = Value::cons(pt1, *undo_list);
}
pub fn undo_list_record_property_change(
undo_list: &mut Value,
prop: Value,
val: Value,
beg: usize,
end: usize,
) {
if undo_list_is_disabled(undo_list) || beg >= end {
return;
}
let beg1 = Value::fixnum((beg + 1) as i64);
let end1 = Value::fixnum((end + 1) as i64);
let inner = Value::cons(beg1, end1);
let inner = Value::cons(val, inner);
let inner = Value::cons(prop, inner);
let entry = Value::cons(Value::NIL, inner);
*undo_list = Value::cons(entry, *undo_list);
}
pub fn undo_list_record_first_change(undo_list: &mut Value) {
if undo_list_is_disabled(undo_list) {
return;
}
let entry = Value::cons(Value::T, Value::fixnum(0));
*undo_list = Value::cons(entry, *undo_list);
}
pub fn undo_list_boundary(undo_list: &mut Value) {
if undo_list_is_disabled(undo_list) {
return;
}
if undo_list.is_nil() {
return;
}
if undo_list.is_cons() && undo_list.cons_car().is_nil() {
return;
}
*undo_list = Value::cons(Value::NIL, *undo_list);
}
pub fn undo_list_pop_group(undo_list: &mut Value) -> Vec<Value> {
while undo_list.is_cons() && undo_list.cons_car().is_nil() {
*undo_list = undo_list.cons_cdr();
}
let mut group = Vec::new();
while undo_list.is_cons() {
let head = undo_list.cons_car();
if head.is_nil() {
break;
}
group.push(head);
*undo_list = undo_list.cons_cdr();
}
group
}
pub fn undo_list_is_empty(undo_list: &Value) -> bool {
undo_list.is_nil()
}
pub fn undo_list_contains_boundary(undo_list: &Value) -> bool {
let mut cursor = *undo_list;
while cursor.is_cons() {
if cursor.cons_car().is_nil() {
return true;
}
cursor = cursor.cons_cdr();
}
false
}
pub fn undo_list_has_trailing_boundary(undo_list: &Value) -> bool {
undo_list.is_cons() && undo_list.cons_car().is_nil()
}
fn undo_entry_size(entry: &Value) -> usize {
match entry.kind() {
ValueKind::Nil => 0,
ValueKind::Fixnum(_) => 8,
ValueKind::String => entry.as_lisp_string().map(|s| s.sbytes()).unwrap_or(8),
_ if entry.is_cons() => {
let car = entry.cons_car();
let cdr = entry.cons_cdr();
let car_size = match car.kind() {
ValueKind::String => car.as_lisp_string().map(|s| s.sbytes()).unwrap_or(8),
_ => 8,
};
let cdr_size = match cdr.kind() {
ValueKind::String => cdr.as_lisp_string().map(|s| s.sbytes()).unwrap_or(8),
_ => 8,
};
16 + car_size + cdr_size
}
_ => 8,
}
}
pub fn truncate_undo_list(undo_list: Value, undo_limit: usize, undo_strong_limit: usize) -> Value {
if undo_list_is_disabled(&undo_list) || undo_list.is_nil() {
return undo_list;
}
let mut total_size: usize = 0;
let mut past_limit = false;
let mut scan = undo_list;
while scan.is_cons() {
let entry = scan.cons_car();
total_size += undo_entry_size(&entry) + 16;
if total_size > undo_strong_limit {
scan.set_cdr(Value::NIL);
return undo_list;
}
if total_size > undo_limit {
past_limit = true;
}
if past_limit && entry.is_nil() {
scan.set_cdr(Value::NIL);
return undo_list;
}
scan = scan.cons_cdr();
}
undo_list
}
#[cfg(test)]
#[path = "undo_test.rs"]
mod tests;