use super::error::{EvalResult, Flow, signal};
use super::intern::intern;
use super::textprop::lookup_buffer_text_property;
use super::value::{Value, ValueKind, lexenv_lookup};
use crate::buffer::BufferManager;
fn expect_args(name: &str, args: &[Value], n: usize) -> Result<(), Flow> {
if args.len() != n {
Err(signal(
"wrong-number-of-arguments",
vec![Value::symbol(name), Value::fixnum(args.len() as i64)],
))
} else {
Ok(())
}
}
fn expect_min_args(name: &str, args: &[Value], min: usize) -> Result<(), Flow> {
if args.len() < min {
Err(signal(
"wrong-number-of-arguments",
vec![Value::symbol(name), Value::fixnum(args.len() as i64)],
))
} else {
Ok(())
}
}
fn expect_max_args(name: &str, args: &[Value], max: usize) -> Result<(), Flow> {
if args.len() > max {
Err(signal(
"wrong-number-of-arguments",
vec![Value::symbol(name), Value::fixnum(args.len() as i64)],
))
} else {
Ok(())
}
}
fn expect_int(value: &Value) -> Result<i64, Flow> {
match value.kind() {
ValueKind::Fixnum(n) => Ok(n),
other => Err(signal(
"wrong-type-argument",
vec![Value::symbol("integer-or-marker-p"), *value],
)),
}
}
fn no_buffer() -> Flow {
signal("error", vec![Value::string("No current buffer")])
}
fn current_buffer_in_manager(buffers: &BufferManager) -> Result<&crate::buffer::Buffer, Flow> {
buffers.current_buffer().ok_or_else(no_buffer)
}
fn dynamic_or_global_symbol_value(eval: &super::eval::Context, name: &str) -> Option<Value> {
let name_id = intern(name);
if eval.lexical_binding() && !eval.obarray.is_special(name) {
if let Some(v) = lexenv_lookup(eval.lexenv, name_id) {
return Some(v);
}
}
if let Some(buf) = eval.buffers.current_buffer() {
if let Some(v) = buf.get_buffer_local(name) {
return Some(v);
}
}
eval.obarray.symbol_value(name).cloned()
}
fn char_pos_to_byte(buf: &crate::buffer::Buffer, pos: i64) -> usize {
buf.lisp_pos_to_byte(pos)
}
fn byte_to_char_pos(buf: &crate::buffer::Buffer, byte_pos: usize) -> i64 {
buf.text.emacs_byte_to_char(byte_pos) as i64 + 1
}
fn buffer_bytes(buf: &crate::buffer::Buffer) -> Vec<u8> {
let mut out = Vec::new();
buf.copy_emacs_bytes_to(0, buf.total_bytes(), &mut out);
out
}
fn count_newlines(text: &[u8], start: usize, end: usize) -> usize {
let s = start.min(text.len());
let e = end.max(start).min(text.len());
text[s..e].iter().filter(|&&b| b == b'\n').count()
}
fn move_by_lines_narrowed(
text: &[u8],
byte_pos: usize,
n: i64,
begv: usize,
zv: usize,
) -> (usize, i64) {
let zv = zv.min(text.len());
let mut pos = byte_pos.clamp(begv, zv);
let mut moved: i64 = 0;
if n >= 0 {
if n == 0 {
return (line_beginning_byte_narrowed(text, pos, begv), 0);
}
for _ in 0..n {
match text[pos..zv].iter().position(|&b| b == b'\n') {
Some(offset) => {
pos += offset + 1;
moved += 1;
}
None => {
pos = zv;
break;
}
}
}
} else {
for _ in 0..(-n) {
let bol = line_beginning_byte_narrowed(text, pos, begv);
if bol <= begv {
pos = begv;
break;
}
pos = line_beginning_byte_narrowed(text, bol - 1, begv);
moved -= 1;
}
}
(pos, moved)
}
fn line_beginning_byte_narrowed(text: &[u8], byte_pos: usize, begv: usize) -> usize {
let pos = byte_pos.min(text.len());
let start = begv.min(pos);
match text[start..pos].iter().rposition(|&b| b == b'\n') {
Some(offset) => start + offset + 1,
None => start,
}
}
fn line_end_byte_narrowed(text: &[u8], byte_pos: usize, zv: usize) -> usize {
let pos = byte_pos.min(text.len());
let end = zv.min(text.len());
match text[pos..end].iter().position(|&b| b == b'\n') {
Some(offset) => pos + offset,
None => end,
}
}
pub(crate) fn check_point_motion_hooks(
eval: &mut super::eval::Context,
old_byte: usize,
new_byte: usize,
) -> Result<(), Flow> {
if old_byte == new_byte {
return Ok(());
}
let inhibit = eval
.obarray
.symbol_value("inhibit-point-motion-hooks")
.cloned()
.unwrap_or(Value::NIL);
if inhibit.is_truthy() {
return Ok(());
}
let current_id = match eval.buffers.current_buffer_id() {
Some(id) => id,
None => return Ok(()),
};
let (old_lisp, new_lisp, leave_before, leave_after, enter_before, enter_after) = {
let buf = match eval.buffers.get(current_id) {
Some(b) => b,
None => return Ok(()),
};
let ol = buf.text.emacs_byte_to_char(old_byte) as i64 + 1;
let nl = buf.text.emacs_byte_to_char(new_byte) as i64 + 1;
let leave_before = point_motion_property(
&eval.obarray,
&eval.buffers,
buf,
old_byte,
false,
"point-left",
);
let leave_after = point_motion_property(
&eval.obarray,
&eval.buffers,
buf,
old_byte,
true,
"point-left",
);
let enter_before = point_motion_property(
&eval.obarray,
&eval.buffers,
buf,
new_byte,
false,
"point-entered",
);
let enter_after = point_motion_property(
&eval.obarray,
&eval.buffers,
buf,
new_byte,
true,
"point-entered",
);
(ol, nl, leave_before, leave_after, enter_before, enter_after)
};
if leave_before != enter_before && leave_before.is_truthy() {
eval.apply(
leave_before,
vec![Value::fixnum(old_lisp), Value::fixnum(new_lisp)],
)?;
}
if leave_after != enter_after && leave_after.is_truthy() {
eval.apply(
leave_after,
vec![Value::fixnum(old_lisp), Value::fixnum(new_lisp)],
)?;
}
if enter_before != leave_before && enter_before.is_truthy() {
eval.apply(
enter_before,
vec![Value::fixnum(old_lisp), Value::fixnum(new_lisp)],
)?;
}
if enter_after != leave_after && enter_after.is_truthy() {
eval.apply(
enter_after,
vec![Value::fixnum(old_lisp), Value::fixnum(new_lisp)],
)?;
}
Ok(())
}
fn point_motion_property(
obarray: &super::symbol::Obarray,
buffers: &BufferManager,
buf: &crate::buffer::Buffer,
point_byte: usize,
after_point: bool,
property: &str,
) -> Value {
if after_point {
if point_byte >= buf.zv_byte {
return Value::NIL;
}
lookup_buffer_text_property(obarray, buffers, buf, point_byte, Value::symbol(property))
} else {
if point_byte <= buf.begv_byte {
return Value::NIL;
}
lookup_buffer_text_property(
obarray,
buffers,
buf,
point_byte - 1,
Value::symbol(property),
)
}
}
pub(crate) fn adjust_for_intangible(
eval: &super::eval::Context,
pos: usize,
direction: i32,
) -> usize {
let inhibit = eval
.obarray
.symbol_value("inhibit-point-motion-hooks")
.cloned()
.unwrap_or(Value::NIL);
if inhibit.is_truthy() {
return pos;
}
let current_id = match eval.buffers.current_buffer_id() {
Some(id) => id,
None => return pos,
};
let buf = match eval.buffers.get(current_id) {
Some(b) => b,
None => return pos,
};
let intangible = lookup_buffer_text_property(
&eval.obarray,
&eval.buffers,
buf,
pos,
Value::symbol("intangible"),
);
if !intangible.is_truthy() {
return pos;
}
let mut cursor = pos;
if direction >= 0 {
loop {
match buf.text.text_props_next_change(cursor) {
Some(next) => {
let prop = lookup_buffer_text_property(
&eval.obarray,
&eval.buffers,
buf,
next,
Value::symbol("intangible"),
);
cursor = next;
if !prop.is_truthy() {
break;
}
}
None => {
cursor = buf.zv_byte;
break;
}
}
}
} else {
loop {
match buf.text.text_props_previous_change(cursor) {
Some(prev) => {
let check = prev.saturating_sub(1);
let prop = lookup_buffer_text_property(
&eval.obarray,
&eval.buffers,
buf,
check,
Value::symbol("intangible"),
);
cursor = prev;
if !prop.is_truthy() {
break;
}
}
None => {
cursor = buf.begv_byte;
break;
}
}
}
}
cursor
}
pub(crate) fn builtin_bobp(ctx: &mut super::eval::Context, args: Vec<Value>) -> EvalResult {
expect_args("bobp", &args, 0)?;
let buf = current_buffer_in_manager(&ctx.buffers)?;
Ok(Value::bool_val(buf.pt_byte == buf.begv_byte))
}
pub(crate) fn builtin_eobp(ctx: &mut super::eval::Context, args: Vec<Value>) -> EvalResult {
expect_args("eobp", &args, 0)?;
let buf = current_buffer_in_manager(&ctx.buffers)?;
Ok(Value::bool_val(buf.pt_byte == buf.zv_byte))
}
pub(crate) fn builtin_bolp(ctx: &mut super::eval::Context, args: Vec<Value>) -> EvalResult {
expect_args("bolp", &args, 0)?;
let buf = current_buffer_in_manager(&ctx.buffers)?;
if buf.pt_byte == buf.begv_byte {
return Ok(Value::T);
}
Ok(Value::bool_val(
buf.pt_byte == 0 || buf.char_code_before(buf.pt_byte) == Some('\n' as u32),
))
}
pub(crate) fn builtin_eolp(ctx: &mut super::eval::Context, args: Vec<Value>) -> EvalResult {
expect_args("eolp", &args, 0)?;
let buf = current_buffer_in_manager(&ctx.buffers)?;
if buf.pt_byte == buf.zv_byte {
return Ok(Value::T);
}
match buf.char_code_after(buf.pt_byte) {
Some(code) if code == '\n' as u32 => Ok(Value::T),
_ => Ok(Value::NIL),
}
}
pub(crate) fn builtin_line_beginning_position(
ctx: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_max_args("line-beginning-position", &args, 1)?;
let n = if args.is_empty() || args[0].is_nil() {
1
} else {
expect_int(&args[0])?
};
let buf = current_buffer_in_manager(&ctx.buffers)?;
let text = buffer_bytes(buf);
let begv = buf.begv_byte;
let zv = buf.zv_byte;
let mut pos = buf.pt_byte;
if n != 1 {
let delta = n - 1;
let (new_pos, _) = move_by_lines_narrowed(&text, pos, delta, begv, zv);
pos = new_pos;
}
let bol = line_beginning_byte_narrowed(&text, pos, begv);
Ok(Value::fixnum(byte_to_char_pos(buf, bol)))
}
pub(crate) fn builtin_line_end_position(
ctx: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_max_args("line-end-position", &args, 1)?;
let n = if args.is_empty() || args[0].is_nil() {
1
} else {
expect_int(&args[0])?
};
let buf = current_buffer_in_manager(&ctx.buffers)?;
let text = buffer_bytes(buf);
let begv = buf.begv_byte;
let zv = buf.zv_byte;
let mut pos = buf.pt_byte;
let mut moved = 0;
if n != 1 {
let delta = n - 1;
let (new_pos, actual_moved) = move_by_lines_narrowed(&text, pos, delta, begv, zv);
pos = new_pos;
moved = actual_moved;
}
if n != 1 && moved != n - 1 && pos == begv {
return Ok(Value::fixnum(byte_to_char_pos(buf, begv)));
}
let eol = line_end_byte_narrowed(&text, pos, zv);
Ok(Value::fixnum(byte_to_char_pos(buf, eol)))
}
pub(crate) fn builtin_line_number_at_pos(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let buf = eval.buffers.current_buffer().ok_or_else(no_buffer)?;
let byte_pos = if args.is_empty() || args[0].is_nil() {
buf.pt_byte
} else {
char_pos_to_byte(buf, expect_int(&args[0])?)
};
let _absolute = args.get(1).is_some_and(|v| v.is_truthy());
let text = buffer_bytes(buf);
let start = if _absolute { 0 } else { buf.begv_byte };
let line_num = count_newlines(&text, start, byte_pos) + 1;
Ok(Value::fixnum(line_num as i64))
}
pub(crate) fn builtin_count_lines(eval: &mut super::eval::Context, args: Vec<Value>) -> EvalResult {
expect_min_args("count-lines", &args, 2)?;
expect_max_args("count-lines", &args, 3)?;
let beg = expect_int(&args[0])?;
let end = expect_int(&args[1])?;
let buf = eval.buffers.current_buffer().ok_or_else(no_buffer)?;
let byte_beg = char_pos_to_byte(buf, beg);
let byte_end = char_pos_to_byte(buf, end);
let (s, e) = if byte_beg <= byte_end {
(byte_beg, byte_end)
} else {
(byte_end, byte_beg)
};
let text = buffer_bytes(buf);
let mut n = count_newlines(&text, s, e);
if s != e && e > 0 && buf.char_code_before(e) != Some('\n' as u32) {
n += 1;
}
Ok(Value::fixnum(n as i64))
}
pub(crate) fn builtin_forward_line(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let n = if args.is_empty() || args[0].is_nil() {
1
} else {
expect_int(&args[0])?
};
let current_id = eval.buffers.current_buffer_id().ok_or_else(no_buffer)?;
let (text, begv, zv, pt) = {
let buf = eval.buffers.get(current_id).ok_or_else(no_buffer)?;
(buffer_bytes(buf), buf.begv_byte, buf.zv_byte, buf.pt_byte)
};
let old_byte = pt;
let (new_pos, moved) = move_by_lines_narrowed(&text, pt, n, begv, zv);
let direction = if n >= 0 { 1 } else { -1 };
let adjusted = adjust_for_intangible(eval, new_pos, direction);
let _ = eval.buffers.goto_buffer_byte(current_id, adjusted);
let mut shortage = n - moved;
if shortage != 0 && n > 0 && begv < zv && new_pos != pt && new_pos > 0 {
let at_line_start = eval
.buffers
.get(current_id)
.is_some_and(|buf| buf.char_code_before(new_pos) == Some('\n' as u32));
if !at_line_start {
shortage -= 1;
}
}
check_point_motion_hooks(eval, old_byte, adjusted)?;
Ok(Value::fixnum(shortage))
}
pub(crate) fn builtin_beginning_of_line(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let n = if args.is_empty() || args[0].is_nil() {
1
} else {
expect_int(&args[0])?
};
let current_id = eval.buffers.current_buffer_id().ok_or_else(no_buffer)?;
let (text, begv, zv, pt) = {
let buf = eval.buffers.get(current_id).ok_or_else(no_buffer)?;
(buffer_bytes(buf), buf.begv_byte, buf.zv_byte, buf.pt_byte)
};
let old_byte = pt;
let mut pos = pt;
if n != 1 {
let delta = n - 1;
let (new_pos, _) = move_by_lines_narrowed(&text, pos, delta, begv, zv);
pos = new_pos;
}
let bol = line_beginning_byte_narrowed(&text, pos, begv);
let adjusted = adjust_for_intangible(eval, bol, -1);
let _ = eval.buffers.goto_buffer_byte(current_id, adjusted);
check_point_motion_hooks(eval, old_byte, adjusted)?;
Ok(Value::NIL)
}
pub(crate) fn builtin_end_of_line(eval: &mut super::eval::Context, args: Vec<Value>) -> EvalResult {
let n = if args.is_empty() || args[0].is_nil() {
1
} else {
expect_int(&args[0])?
};
let current_id = eval.buffers.current_buffer_id().ok_or_else(no_buffer)?;
let (text, begv, zv, pt) = {
let buf = eval.buffers.get(current_id).ok_or_else(no_buffer)?;
(buffer_bytes(buf), buf.begv_byte, buf.zv_byte, buf.pt_byte)
};
let old_byte = pt;
let mut pos = pt;
let mut moved = 0;
if n != 1 {
let delta = n - 1;
let (new_pos, actual_moved) = move_by_lines_narrowed(&text, pos, delta, begv, zv);
pos = new_pos;
moved = actual_moved;
}
if n != 1 && moved != n - 1 && pos == begv {
let adjusted = adjust_for_intangible(eval, begv, -1);
let _ = eval.buffers.goto_buffer_byte(current_id, adjusted);
check_point_motion_hooks(eval, old_byte, adjusted)?;
return Ok(Value::NIL);
}
let eol = line_end_byte_narrowed(&text, pos, zv);
let adjusted = adjust_for_intangible(eval, eol, 1);
let _ = eval.buffers.goto_buffer_byte(current_id, adjusted);
check_point_motion_hooks(eval, old_byte, adjusted)?;
Ok(Value::NIL)
}
pub(crate) fn builtin_forward_char(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let n = if args.is_empty() || args[0].is_nil() {
1
} else {
expect_int(&args[0])?
};
let current_id = eval.buffers.current_buffer_id().ok_or_else(no_buffer)?;
let (old_byte, cur_char, begv_char, zv_char, new_byte) = {
let buf = eval.buffers.get(current_id).ok_or_else(no_buffer)?;
let old_byte = buf.pt_byte;
let cur_char = buf.point_char();
let begv_char = buf.point_min_char();
let zv_char = buf.point_max_char();
let desired = cur_char as i64 + n;
let clamped_char = desired.clamp(begv_char as i64, zv_char as i64) as usize;
(
old_byte,
cur_char,
begv_char,
zv_char,
buf.text.char_to_emacs_byte(clamped_char),
)
};
let direction = if n >= 0 { 1 } else { -1 };
let adjusted = adjust_for_intangible(eval, new_byte, direction);
let _ = eval.buffers.goto_buffer_byte(current_id, adjusted);
let desired = cur_char as i64 + n;
if desired < begv_char as i64 {
return Err(signal("beginning-of-buffer", vec![]));
}
if desired > zv_char as i64 {
return Err(signal("end-of-buffer", vec![]));
}
check_point_motion_hooks(eval, old_byte, adjusted)?;
Ok(Value::NIL)
}
pub(crate) fn builtin_backward_char(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let n = if args.is_empty() || args[0].is_nil() {
1
} else {
expect_int(&args[0])?
};
builtin_forward_char(eval, vec![Value::fixnum(-n)])
}
fn parse_skip_chars_set(codes: &[u32]) -> (bool, Vec<u32>) {
let mut chars: Vec<u32> = Vec::new();
let mut negate = false;
let mut i = 0;
if i < codes.len() && codes[i] == '^' as u32 {
negate = true;
i += 1;
}
while i < codes.len() {
let c = if codes[i] == '\\' as u32 && i + 1 < codes.len() {
i += 1;
codes[i]
} else {
codes[i]
};
i += 1;
if i + 1 < codes.len() && codes[i] == '-' as u32 {
i += 1; let end_c = if codes[i] == '\\' as u32 && i + 1 < codes.len() {
i += 1;
codes[i]
} else {
codes[i]
};
i += 1;
if c <= end_c {
for code in c..=end_c {
if !chars.contains(&code) {
chars.push(code);
}
}
}
} else if !chars.contains(&c) {
chars.push(c);
}
}
(negate, chars)
}
pub(crate) fn builtin_skip_chars_forward(
ctx: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_min_args("skip-chars-forward", &args, 1)?;
let set_codes = match args[0].as_lisp_string() {
Some(string) => super::builtins::lisp_string_char_codes(string),
None => {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("stringp"), args[0]],
));
}
};
let (negate, char_set) = parse_skip_chars_set(&set_codes);
let current_id = ctx.buffers.current_buffer_id().ok_or_else(no_buffer)?;
let (start_pos, pos, limit, moved_chars) = {
let buf = ctx.buffers.get(current_id).ok_or_else(no_buffer)?;
let lim_byte = if args.len() > 1 && !args[1].is_nil() {
char_pos_to_byte(buf, expect_int(&args[1])?)
} else {
buf.zv_byte
};
let start_pos = buf.pt_byte;
let mut pos = buf.pt_byte;
let limit = lim_byte.min(buf.zv_byte);
while pos < limit {
if let Some(code) = buf.char_code_after(pos) {
let in_set = char_set.contains(&code);
if negate {
if in_set {
break;
}
} else if !in_set {
break;
}
pos += buf
.char_after_emacs_len(pos)
.expect("char width should exist at valid point");
} else {
break;
}
}
let moved_chars =
buf.text.emacs_byte_to_char(pos) as i64 - buf.text.emacs_byte_to_char(start_pos) as i64;
(start_pos, pos, limit, moved_chars)
};
debug_assert!(pos >= start_pos || limit <= start_pos);
let _ = ctx.buffers.goto_buffer_byte(current_id, pos);
Ok(Value::fixnum(moved_chars))
}
pub(crate) fn builtin_skip_chars_backward(
ctx: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_min_args("skip-chars-backward", &args, 1)?;
let set_codes = match args[0].as_lisp_string() {
Some(string) => super::builtins::lisp_string_char_codes(string),
None => {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("stringp"), args[0]],
));
}
};
let (negate, char_set) = parse_skip_chars_set(&set_codes);
let current_id = ctx.buffers.current_buffer_id().ok_or_else(no_buffer)?;
let (pos, moved_chars) = {
let buf = ctx.buffers.get(current_id).ok_or_else(no_buffer)?;
let limit = if args.len() > 1 && !args[1].is_nil() {
char_pos_to_byte(buf, expect_int(&args[1])?)
} else {
buf.begv_byte
};
let start_pos = buf.pt_byte;
let mut pos = buf.pt_byte;
while pos > limit {
if let Some(code) = buf.char_code_before(pos) {
let in_set = char_set.contains(&code);
if negate {
if in_set {
break;
}
} else if !in_set {
break;
}
pos -= buf
.char_before_emacs_len(pos)
.expect("char width should exist before valid point");
} else {
break;
}
}
let moved_chars =
buf.text.emacs_byte_to_char(pos) as i64 - buf.text.emacs_byte_to_char(start_pos) as i64;
(pos, moved_chars)
};
let _ = ctx.buffers.goto_buffer_byte(current_id, pos);
Ok(Value::fixnum(moved_chars))
}
pub(crate) fn builtin_mark_nav(eval: &mut super::eval::Context, args: Vec<Value>) -> EvalResult {
let _force = args.first().is_some_and(|v| v.is_truthy());
let buf = eval.buffers.current_buffer().ok_or_else(no_buffer)?;
match buf.mark() {
Some(byte_pos) => Ok(Value::fixnum(byte_to_char_pos(buf, byte_pos))),
None => Ok(Value::NIL),
}
}
pub(crate) fn builtin_region_beginning(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("region-beginning", &args, 0)?;
let buf = eval.buffers.current_buffer().ok_or_else(no_buffer)?;
let mark = buf.mark().ok_or_else(|| {
signal(
"error",
vec![Value::string(
"The mark is not set now, so there is no region",
)],
)
})?;
let pt = buf.pt_byte;
let start = pt.min(mark);
Ok(Value::fixnum(byte_to_char_pos(buf, start)))
}
pub(crate) fn builtin_region_end(eval: &mut super::eval::Context, args: Vec<Value>) -> EvalResult {
expect_args("region-end", &args, 0)?;
let buf = eval.buffers.current_buffer().ok_or_else(no_buffer)?;
let mark = buf.mark().ok_or_else(|| {
signal(
"error",
vec![Value::string(
"The mark is not set now, so there is no region",
)],
)
})?;
let pt = buf.pt_byte;
let end = pt.max(mark);
Ok(Value::fixnum(byte_to_char_pos(buf, end)))
}
pub(crate) fn builtin_transient_mark_mode(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
if args.len() > 1 {
return Err(signal(
"wrong-number-of-arguments",
vec![
Value::symbol("transient-mark-mode"),
Value::fixnum(args.len() as i64),
],
));
}
let sym_id = intern("transient-mark-mode");
let current = eval
.obarray
.symbol_value("transient-mark-mode")
.cloned()
.unwrap_or(Value::NIL);
let new_val = if args.is_empty() || args[0].is_nil() {
Value::T
} else if args[0].is_symbol_named("toggle") {
if current.is_truthy() {
Value::NIL
} else {
Value::T
}
} else {
match args[0].kind() {
ValueKind::Fixnum(n) => {
if n > 0 {
Value::T
} else {
Value::NIL
}
}
ValueKind::Float => {
let truncated = args[0].xfloat() as i64;
if truncated > 0 { Value::T } else { Value::NIL }
}
_ => Value::T,
}
};
eval.obarray.set_symbol_value_id(sym_id, new_val);
Ok(new_val)
}
#[cfg(test)]
#[path = "navigation_test.rs"]
mod tests;