Skip to content

Commit 4ae77ba

Browse files
committed
primitive-undo: visible-portion check + long-format apply parity
Mirror GNU lisp/simple.el:3702-3722 for (apply ...) entries. Long format (apply DELTA START END FUN . ARGS) now: - validates START..END is fully inside (point-min)..(point-max) and signals "Changes to be undone are outside visible portion of buffer" otherwise, matching the existing checks for (BEG . END), (TEXT . POS), and (nil PROP VAL BEG . END); - dispatches FUN with ARGS (not the whole DELTA START END FUN list). Short format (apply FUN . ARGS) is unchanged.
1 parent cd74c9e commit 4ae77ba

1 file changed

Lines changed: 59 additions & 9 deletions

File tree

neovm-core/src/emacs_core/undo.rs

Lines changed: 59 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -296,17 +296,67 @@ fn primitive_undo_inner(
296296
}
297297
}
298298
}
299-
// (apply FUN . ARGS) — call FUN with ARGS.
299+
// (apply FUN . ARGS) or
300+
// (apply DELTA START END FUN . ARGS) — call FUN with ARGS.
301+
// Mirrors GNU lisp/simple.el:3702-3722.
300302
_ if car.as_symbol_name() == Some("apply") => {
301303
if cdr.is_cons() {
302-
let fun = cdr.cons_car();
303-
let rest = cdr.cons_cdr();
304-
let mut fargs = Vec::new();
305-
let mut cursor = rest;
306-
while cursor.is_cons() {
307-
fargs.push(cursor.cons_car());
308-
cursor = cursor.cons_cdr();
309-
}
304+
let fun_args_head = cdr.cons_car();
305+
let (fun, fargs) = if fun_args_head.as_fixnum().is_some() {
306+
// Long format: (apply DELTA START END FUN . ARGS).
307+
// Validate that START..END is fully inside the
308+
// visible portion of the buffer.
309+
let rest1 = cdr.cons_cdr(); // (START END FUN . ARGS)
310+
let mut start_v = Value::NIL;
311+
let mut rest2 = Value::NIL;
312+
if rest1.is_cons() {
313+
start_v = rest1.cons_car();
314+
rest2 = rest1.cons_cdr(); // (END FUN . ARGS)
315+
}
316+
let mut end_v = Value::NIL;
317+
let mut rest3 = Value::NIL;
318+
if rest2.is_cons() {
319+
end_v = rest2.cons_car();
320+
rest3 = rest2.cons_cdr(); // (FUN . ARGS)
321+
}
322+
if let (Some(start1), Some(end1)) =
323+
(start_v.as_fixnum(), end_v.as_fixnum())
324+
{
325+
let start_byte = (start1 - 1).max(0) as usize;
326+
let end_byte = (end1 - 1).max(0) as usize;
327+
if let Some(buf) = ctx.buffers.get(buf_id) {
328+
if start_byte < buf.begv_byte || end_byte > buf.zv_byte {
329+
return Err(signal(
330+
"error",
331+
vec![Value::string(
332+
"Changes to be undone are outside visible portion of buffer",
333+
)],
334+
));
335+
}
336+
}
337+
}
338+
if !rest3.is_cons() {
339+
continue;
340+
}
341+
let fun = rest3.cons_car();
342+
let mut fargs = Vec::new();
343+
let mut cursor = rest3.cons_cdr();
344+
while cursor.is_cons() {
345+
fargs.push(cursor.cons_car());
346+
cursor = cursor.cons_cdr();
347+
}
348+
(fun, fargs)
349+
} else {
350+
// Short format: (apply FUN . ARGS).
351+
let fun = cdr.cons_car();
352+
let mut fargs = Vec::new();
353+
let mut cursor = cdr.cons_cdr();
354+
while cursor.is_cons() {
355+
fargs.push(cursor.cons_car());
356+
cursor = cursor.cons_cdr();
357+
}
358+
(fun, fargs)
359+
};
310360
// Best-effort: ignore errors from undo apply calls.
311361
let _ = ctx.funcall_general(fun, fargs);
312362
}

0 commit comments

Comments
 (0)