use super::*;
use crate::emacs_core::symbol::Obarray;
use super::keymap::{
KeyEvent, collect_minor_mode_map_entries_in_state, collect_minor_mode_maps_in_state,
current_active_maps_for_position, current_active_maps_for_position_read_only,
ensure_global_keymap_in_obarray, expand_meta_prefix_char_events_in_obarray,
get_keymap_in_obarray, get_keymap_in_runtime, is_list_keymap, key_event_to_emacs_event,
list_keymap_accessible, list_keymap_copy, list_keymap_define_seq_in_obarray,
list_keymap_define_seq_in_obarray_ex, list_keymap_inherits_from, list_keymap_parent,
list_keymap_set_parent, lookup_key_in_keymaps_in_obarray, make_list_keymap,
make_sparse_list_keymap, maybe_keymap_in_obarray, maybe_keymap_in_runtime,
};
fn map_keymap_binding_value(binding: Value) -> Value {
if binding == Value::T {
Value::NIL
} else {
binding
}
}
pub(crate) fn expect_keymap_in_obarray(obarray: &Obarray, value: &Value) -> Result<Value, Flow> {
get_keymap_in_obarray(obarray, value, true)
}
fn expect_keymap(eval: &mut super::eval::Context, value: &Value) -> EvalResult {
get_keymap_in_runtime(eval, value, true, true)
}
fn ensure_global_keymap(eval: &mut super::eval::Context) -> Value {
ensure_global_keymap_in_obarray(&mut eval.obarray)
}
fn call_help_describe_map_tree(
eval: &mut super::eval::Context,
startmap: Value,
partial: Value,
shadow: Value,
prefix: Value,
title: Value,
nomenu: Value,
transl: Value,
always_title: Value,
mention_shadow: Value,
buffer: Value,
) -> Result<Value, Flow> {
eval.apply(
Value::symbol("help--describe-map-tree"),
vec![
startmap,
partial,
shadow,
prefix,
title,
nomenu,
transl,
always_title,
mention_shadow,
buffer,
],
)
}
pub(crate) fn expect_key_events(value: &Value) -> Result<Vec<Value>, Flow> {
match value.kind() {
ValueKind::Veclike(VecLikeType::Vector) => {
let items = value.as_vector_data().unwrap().clone();
let mut events = Vec::with_capacity(items.len());
for item in &items {
match item.kind() {
ValueKind::Fixnum(_) => events.push(*item),
ValueKind::Symbol(_) => events.push(*item),
ValueKind::Nil => events.push(Value::symbol("nil")),
ValueKind::T => events.push(Value::symbol("t")),
ValueKind::Cons => {
match super::kbd::key_events_from_designator(&Value::vector(vec![*item])) {
Ok(ke) => {
for e in &ke {
events.push(key_event_to_emacs_event(e));
}
}
Err(super::kbd::KeyDesignatorError::Parse(msg)) => {
return Err(signal("error", vec![Value::string(msg)]));
}
Err(super::kbd::KeyDesignatorError::WrongType(other)) => {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("arrayp"), other],
));
}
}
}
other => {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("arrayp"), *value],
));
}
}
}
Ok(events)
}
_ => {
let key_events = expect_key_description(value)?;
Ok(key_events.iter().map(key_event_to_emacs_event).collect())
}
}
}
fn expect_key_description(value: &Value) -> Result<Vec<KeyEvent>, Flow> {
match super::kbd::key_events_from_designator(value) {
Ok(events) => Ok(events),
Err(super::kbd::KeyDesignatorError::WrongType(other)) => Err(signal(
"wrong-type-argument",
vec![Value::symbol("arrayp"), other],
)),
Err(super::kbd::KeyDesignatorError::Parse(msg)) => {
Err(signal("error", vec![Value::string(msg)]))
}
}
}
pub(super) fn builtin_accessible_keymaps(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
builtin_accessible_keymaps_impl(eval.obarray(), &args)
}
pub(crate) fn builtin_accessible_keymaps_impl(obarray: &Obarray, args: &[Value]) -> EvalResult {
use crate::emacs_core::value::{ValueKind, VecLikeType};
expect_min_args("accessible-keymaps", &args, 1)?;
expect_max_args("accessible-keymaps", &args, 2)?;
let keymap = expect_keymap_in_obarray(obarray, &args[0])?;
let mut all_out = Vec::new();
let mut prefix = Vec::new();
let mut seen = Vec::new();
list_keymap_accessible(&keymap, &mut prefix, &mut all_out, &mut seen);
if let Some(prefix_arg) = args.get(1) {
if !prefix_arg.is_nil() {
let prefix_events: Vec<Value> = match prefix_arg.kind() {
ValueKind::String => {
expect_key_events(prefix_arg)?
}
ValueKind::Veclike(VecLikeType::Vector) => {
prefix_arg.as_vector_data().unwrap().clone()
}
ValueKind::Cons => {
return Err(super::error::signal(
"wrong-type-argument",
vec![Value::symbol("arrayp"), *prefix_arg],
));
}
_ => {
return Err(super::error::signal(
"wrong-type-argument",
vec![Value::symbol("sequencep"), *prefix_arg],
));
}
};
let filtered: Vec<Value> = all_out
.into_iter()
.filter(|entry| {
if entry.is_cons() {
let pair_car = entry.cons_car();
let pair_cdr = entry.cons_cdr();
if pair_car.is_vector() {
let entry_prefix = pair_car.as_vector_data().unwrap().clone();
if entry_prefix.len() >= prefix_events.len() {
return entry_prefix[..prefix_events.len()] == prefix_events[..];
}
}
}
false
})
.collect();
if filtered.is_empty() {
return Ok(Value::NIL);
}
return Ok(Value::list(filtered));
}
}
Ok(Value::list(all_out))
}
pub(super) fn builtin_make_keymap(
_eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
builtin_make_keymap_pure(&args)
}
pub(crate) fn builtin_make_keymap_pure(args: &[Value]) -> EvalResult {
expect_max_args("make-keymap", &args, 1)?;
Ok(make_list_keymap())
}
pub(super) fn builtin_make_sparse_keymap(
_eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_max_args("make-sparse-keymap", &args, 1)?;
if let Some(prompt) = args.first() {
if prompt.is_string() {
return Ok(Value::cons(
Value::symbol("keymap"),
Value::cons(*prompt, Value::NIL),
));
}
}
Ok(make_sparse_list_keymap())
}
pub(super) fn builtin_copy_keymap(eval: &mut super::eval::Context, args: Vec<Value>) -> EvalResult {
builtin_copy_keymap_impl(eval.obarray(), &args)
}
pub(crate) fn builtin_copy_keymap_impl(obarray: &Obarray, args: &[Value]) -> EvalResult {
expect_args("copy-keymap", &args, 1)?;
let keymap = expect_keymap_in_obarray(obarray, &args[0])?;
Ok(list_keymap_copy(&keymap))
}
pub(super) fn builtin_define_key(eval: &mut super::eval::Context, args: Vec<Value>) -> EvalResult {
expect_min_args("define-key", &args, 3)?;
expect_max_args("define-key", &args, 4)?;
let keymap = expect_keymap(eval, &args[0])?;
let mut events = expect_key_events(&args[1])?;
let def = args[2];
let remove = args.get(3).is_some_and(|v| v.is_truthy());
if let Some(expanded) = expand_meta_prefix_char_events_in_obarray(eval.obarray(), &events) {
events = expanded;
}
if let Err(msg) =
list_keymap_define_seq_in_obarray_ex(eval.obarray(), keymap, &events, def, remove)
{
return Err(signal("error", vec![Value::string(msg)]));
}
Ok(def)
}
pub(super) fn builtin_lookup_key(eval: &mut super::eval::Context, args: Vec<Value>) -> EvalResult {
expect_min_args("lookup-key", &args, 2)?;
expect_max_args("lookup-key", &args, 3)?;
let t_ok = args.get(2).is_some_and(|v| v.is_truthy());
let events = expect_key_events(&args[1])?;
let keymaps = resolve_lookup_keymaps_in_runtime(eval, &args[0])?;
if events.is_empty() {
return Ok(keymaps.first().copied().unwrap_or(Value::NIL));
}
Ok(lookup_key_with_menu_compat(
eval.obarray(),
&keymaps,
&events,
t_ok,
))
}
pub(crate) fn builtin_lookup_key_impl(obarray: &Obarray, args: &[Value]) -> EvalResult {
expect_min_args("lookup-key", &args, 2)?;
expect_max_args("lookup-key", &args, 3)?;
let t_ok = args.get(2).is_some_and(|v| v.is_truthy());
let events = expect_key_events(&args[1])?;
let keymaps = resolve_lookup_keymaps_in_obarray(obarray, &args[0])?;
if events.is_empty() {
return Ok(keymaps.first().copied().unwrap_or(Value::NIL));
}
Ok(lookup_key_with_menu_compat(
obarray, &keymaps, &events, t_ok,
))
}
fn lookup_key_with_menu_compat(
obarray: &Obarray,
keymaps: &[Value],
events: &[Value],
t_ok: bool,
) -> Value {
let found = lookup_key_in_keymaps_in_obarray(obarray, keymaps, events, t_ok);
if is_defined_lookup_result(&found) || !is_menu_bar_key(events) {
return found;
}
let lower_events: Vec<Value> = events
.iter()
.map(|event| {
event
.as_symbol_name()
.map(|name| Value::symbol(name.to_lowercase()))
.unwrap_or(*event)
})
.collect();
let lower_found = lookup_key_in_keymaps_in_obarray(obarray, keymaps, &lower_events, t_ok);
if is_defined_lookup_result(&lower_found) {
return lower_found;
}
let dash_events: Vec<Value> = lower_events
.iter()
.map(|event| {
event
.as_symbol_name()
.filter(|name| name.contains(' '))
.map(|name| Value::symbol(name.replace(' ', "-")))
.unwrap_or(*event)
})
.collect();
let dash_found = lookup_key_in_keymaps_in_obarray(obarray, keymaps, &dash_events, t_ok);
if is_defined_lookup_result(&dash_found) {
return dash_found;
}
found
}
fn is_defined_lookup_result(value: &Value) -> bool {
!value.is_nil() && !value.is_fixnum()
}
fn is_menu_bar_key(events: &[Value]) -> bool {
events
.first()
.and_then(|event| event.as_symbol_name())
.is_some_and(|name| name == "menu-bar")
}
fn resolve_lookup_keymaps_in_runtime(
eval: &mut super::eval::Context,
value: &Value,
) -> Result<Vec<Value>, Flow> {
if is_list_keymap(value) {
return Ok(vec![*value]);
}
if value.is_nil() {
return Ok(vec![Value::NIL]);
}
if value.is_cons()
&& is_list_keymap(&maybe_keymap_in_runtime(eval, &value.cons_car(), true)?)
&& let Some(items) = list_to_vec(value)
{
if items.is_empty() {
return Ok(vec![Value::NIL]);
}
let mut resolved = Vec::with_capacity(items.len());
for item in &items {
if item.is_nil() {
resolved.push(Value::NIL);
continue;
}
let keymap = maybe_keymap_in_runtime(eval, item, true)?;
if keymap.is_nil() {
resolved.clear();
break;
}
resolved.push(keymap);
}
if !resolved.is_empty() {
return Ok(resolved);
}
}
if value.is_cons() {
return Ok(vec![*value]);
}
Ok(vec![get_keymap_in_runtime(eval, value, true, true)?])
}
fn resolve_lookup_keymaps_in_obarray(obarray: &Obarray, value: &Value) -> Result<Vec<Value>, Flow> {
if is_list_keymap(value) {
return Ok(vec![*value]);
}
if value.is_nil() {
return Ok(vec![Value::NIL]);
}
if value.is_cons()
&& maybe_keymap_in_obarray(obarray, &value.cons_car())
.is_some_and(|keymap| is_list_keymap(&keymap))
&& let Some(items) = list_to_vec(value)
{
if items.is_empty() {
return Ok(vec![Value::NIL]);
}
let mut resolved = Vec::with_capacity(items.len());
for item in &items {
if item.is_nil() {
resolved.push(Value::NIL);
continue;
}
let Some(keymap) = maybe_keymap_in_obarray(obarray, item) else {
resolved.clear();
break;
};
resolved.push(keymap);
}
if !resolved.is_empty() {
return Ok(resolved);
}
}
if value.is_cons() {
return Ok(vec![*value]);
}
Ok(vec![expect_keymap_in_obarray(obarray, value)?])
}
pub(super) fn builtin_global_set_key(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("global-set-key", &args, 2)?;
let global = ensure_global_keymap(eval);
let events = expect_key_events(&args[0])?;
let def = args[1];
if let Err(msg) = list_keymap_define_seq_in_obarray(eval.obarray(), global, &events, def) {
return Err(signal("error", vec![Value::string(msg)]));
}
Ok(def)
}
pub(super) fn builtin_local_set_key(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("local-set-key", &args, 2)?;
let local = if eval.buffers.current_local_map().is_nil() {
let km = make_sparse_list_keymap();
let _ = eval.buffers.set_current_local_map(km);
km
} else {
eval.buffers.current_local_map()
};
let events = expect_key_events(&args[0])?;
let def = args[1];
if let Err(msg) = list_keymap_define_seq_in_obarray(eval.obarray(), local, &events, def) {
return Err(signal("error", vec![Value::string(msg)]));
}
Ok(def)
}
pub(super) fn builtin_use_local_map(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("use-local-map", &args, 1)?;
let keymap = if args[0].is_nil() {
Value::NIL
} else {
expect_keymap(eval, &args[0])?
};
let _ = eval.buffers.set_current_local_map(keymap);
Ok(Value::NIL)
}
pub(super) fn builtin_use_global_map(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("use-global-map", &args, 1)?;
let keymap = get_keymap_in_runtime(eval, &args[0], true, true)?;
eval.obarray.set_symbol_value("global-map", keymap);
Ok(Value::NIL)
}
pub(crate) fn builtin_use_global_map_impl(obarray: &mut Obarray, args: &[Value]) -> EvalResult {
expect_args("use-global-map", args, 1)?;
let keymap = expect_keymap_in_obarray(obarray, &args[0])?;
obarray.set_symbol_value("global-map", keymap);
Ok(Value::NIL)
}
pub(super) fn builtin_current_local_map(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
builtin_current_local_map_impl(eval.buffers.current_local_map(), &args)
}
pub(crate) fn builtin_current_local_map_impl(
current_local_map: Value,
args: &[Value],
) -> EvalResult {
expect_args("current-local-map", args, 0)?;
Ok(current_local_map)
}
pub(super) fn builtin_current_global_map(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("current-global-map", &args, 0)?;
Ok(ensure_global_keymap(eval))
}
pub(super) fn builtin_describe_buffer_bindings(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_range_args("describe-buffer-bindings", &args, 1, 3)?;
if !args[0].is_buffer() {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("bufferp"), args[0]],
));
}
if let Some(prefixes) = args.get(1) {
if !prefixes.is_nil()
&& !(prefixes.is_cons()
|| prefixes.is_vector()
|| prefixes.is_string()
|| prefixes.is_nil())
{
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("sequencep"), *prefixes],
));
}
}
let buffer = args[0];
let prefix = args.get(1).copied().unwrap_or(Value::NIL);
let nomenu = if args.get(2).is_some_and(|v| !v.is_nil()) {
Value::NIL
} else {
Value::T
};
let Some(buffer_id) = buffer.as_buffer_id() else {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("bufferp"), buffer],
));
};
let Some(buf) = eval.buffers.get(buffer_id) else {
return Err(signal(
"error",
vec![Value::string("Selecting deleted buffer")],
));
};
let local_map = buf.local_map();
let major_mode_name = buf
.get_buffer_local("major-mode")
.and_then(|value| value.as_symbol_name())
.unwrap_or("fundamental-mode");
let minor_maps =
collect_minor_mode_map_entries_in_state(&eval.obarray, &[], &eval.buffers, Some(buffer_id));
let mut shadow = Value::NIL;
if let Some(key_translation_map) = eval.obarray.symbol_value("key-translation-map").copied() {
call_help_describe_map_tree(
eval,
key_translation_map,
Value::NIL,
shadow,
prefix,
Value::string("Key translations"),
nomenu,
Value::T,
Value::NIL,
Value::NIL,
buffer,
)?;
shadow = Value::cons(key_translation_map, shadow);
}
for (mode, keymap) in minor_maps {
let title = Value::string(format!(
"\u{c}\n`{}' Minor Mode Bindings",
resolve_sym(mode)
));
call_help_describe_map_tree(
eval,
keymap,
Value::T,
shadow,
prefix,
title,
nomenu,
Value::NIL,
Value::NIL,
Value::NIL,
buffer,
)?;
shadow = Value::cons(keymap, shadow);
}
if !local_map.is_nil() {
let title = Value::string(format!("\u{c}\n`{major_mode_name}' Major Mode Bindings"));
call_help_describe_map_tree(
eval,
local_map,
Value::T,
shadow,
prefix,
title,
nomenu,
Value::NIL,
Value::NIL,
Value::NIL,
buffer,
)?;
shadow = Value::cons(local_map, shadow);
}
let global_map = ensure_global_keymap(eval);
call_help_describe_map_tree(
eval,
global_map,
Value::T,
shadow,
prefix,
Value::string("\u{c}\nGlobal Bindings"),
nomenu,
Value::NIL,
Value::T,
Value::NIL,
buffer,
)?;
Ok(Value::NIL)
}
pub(super) fn builtin_current_active_maps(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
builtin_current_active_maps_impl(eval, &args)
}
pub(crate) fn builtin_current_active_maps_impl(
ctx: &mut crate::emacs_core::eval::Context,
args: &[Value],
) -> EvalResult {
expect_max_args("current-active-maps", &args, 2)?;
let obey_overriding_local_maps = args.first().is_some_and(|v| v.is_truthy());
let maps = current_active_maps_for_position(ctx, obey_overriding_local_maps, args.get(1))?;
Ok(Value::list(maps))
}
pub(super) fn builtin_current_minor_mode_maps(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
builtin_current_minor_mode_maps_impl(eval, &args)
}
pub(crate) fn builtin_current_minor_mode_maps_impl(
ctx: &crate::emacs_core::eval::Context,
args: &[Value],
) -> EvalResult {
expect_args("current-minor-mode-maps", &args, 0)?;
let maps = collect_minor_mode_maps_in_state(
&ctx.obarray,
&[],
&ctx.buffers,
ctx.buffers.current_buffer_id(),
);
if maps.is_empty() {
Ok(Value::NIL)
} else {
Ok(Value::list(maps))
}
}
pub(crate) struct KeymapIterationPlan {
pub(crate) bindings: Vec<(Value, Value)>,
pub(crate) parent: Value,
}
pub(crate) fn plan_keymap_iteration(keymap: Value) -> KeymapIterationPlan {
let mut bindings = Vec::new();
let mut parent = Value::NIL;
let mut cursor = if is_list_keymap(&keymap) {
keymap.cons_cdr()
} else {
keymap
};
let mut steps = 0usize;
while cursor.is_cons() {
steps += 1;
if steps > 100_000 {
break;
}
if is_list_keymap(&cursor) {
parent = cursor;
break;
}
let entry = cursor.cons_car();
if is_list_keymap(&entry) {
parent = entry;
break;
}
match entry.kind() {
ValueKind::Cons => {
let pair_car = entry.cons_car();
let pair_cdr = entry.cons_cdr();
bindings.push((pair_car, map_keymap_binding_value(pair_cdr)));
}
ValueKind::Veclike(VecLikeType::Vector) => {
if crate::emacs_core::chartable::is_char_table(&entry) {
let _ = crate::emacs_core::chartable::for_each_char_table_mapping(
&entry,
|event, binding| {
bindings.push((event, map_keymap_binding_value(binding)));
Ok(())
},
);
} else {
let items = entry.as_vector_data().unwrap().clone();
for (idx, binding) in items.iter().enumerate() {
bindings.push((
Value::fixnum(idx as i64),
map_keymap_binding_value(*binding),
));
}
}
}
_ => {}
}
cursor = cursor.cons_cdr();
}
KeymapIterationPlan { bindings, parent }
}
pub(crate) fn execute_keymap_iteration_callbacks(
eval: &mut super::eval::Context,
function: Value,
bindings: &[(Value, Value)],
) -> Result<(), Flow> {
for (event, binding) in bindings {
eval.apply(function, vec![*event, *binding])?;
}
Ok(())
}
pub(super) fn builtin_map_keymap(eval: &mut super::eval::Context, args: Vec<Value>) -> EvalResult {
expect_min_args("map-keymap", &args, 2)?;
expect_max_args("map-keymap", &args, 3)?;
let function = args[0];
let mut keymap = expect_keymap(eval, &args[1])?;
loop {
keymap = map_keymap_internal_impl(eval, function, keymap)?;
if keymap.is_nil() {
break;
}
if !is_list_keymap(&keymap) {
break;
}
}
Ok(Value::NIL)
}
pub(super) fn builtin_map_keymap_internal(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("map-keymap-internal", &args, 2)?;
let function = args[0];
let keymap = expect_keymap(eval, &args[1])?;
map_keymap_internal_impl(eval, function, keymap)
}
fn map_keymap_internal_impl(
eval: &mut super::eval::Context,
function: Value,
keymap: Value,
) -> EvalResult {
let plan = plan_keymap_iteration(keymap);
execute_keymap_iteration_callbacks(eval, function, &plan.bindings)?;
Ok(plan.parent)
}
pub(super) fn builtin_keymap_parent(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("keymap-parent", &args, 1)?;
let keymap = get_keymap_in_runtime(eval, &args[0], true, true)?;
Ok(list_keymap_parent(&keymap))
}
pub(crate) fn builtin_keymap_parent_impl(obarray: &Obarray, args: &[Value]) -> EvalResult {
expect_args("keymap-parent", &args, 1)?;
let keymap = get_keymap_in_obarray(obarray, &args[0], true)?;
Ok(list_keymap_parent(&keymap))
}
pub(super) fn builtin_set_keymap_parent(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("set-keymap-parent", &args, 2)?;
let keymap = get_keymap_in_runtime(eval, &args[0], true, true)?;
let parent = if args[1].is_nil() {
Value::NIL
} else {
get_keymap_in_runtime(eval, &args[1], true, false)?
};
if !parent.is_nil() && list_keymap_inherits_from(&parent, &keymap) {
return Err(signal(
"error",
vec![Value::string("Cyclic keymap inheritance")],
));
}
list_keymap_set_parent(keymap, parent);
Ok(parent)
}
pub(crate) fn builtin_set_keymap_parent_impl(obarray: &Obarray, args: &[Value]) -> EvalResult {
expect_args("set-keymap-parent", &args, 2)?;
let keymap = get_keymap_in_obarray(obarray, &args[0], true)?;
let parent = if args[1].is_nil() {
Value::NIL
} else {
get_keymap_in_obarray(obarray, &args[1], true)?
};
if !parent.is_nil() && list_keymap_inherits_from(&parent, &keymap) {
return Err(signal(
"error",
vec![Value::string("Cyclic keymap inheritance")],
));
}
list_keymap_set_parent(keymap, parent);
Ok(parent)
}
pub(super) fn is_lisp_keymap_object(value: &Value) -> bool {
is_list_keymap(value)
}
pub(super) fn builtin_keymapp(eval: &mut super::eval::Context, args: Vec<Value>) -> EvalResult {
builtin_keymapp_impl(eval.obarray(), &args)
}
pub(crate) fn builtin_keymapp_impl(obarray: &Obarray, args: &[Value]) -> EvalResult {
expect_args("keymapp", &args, 1)?;
Ok(maybe_keymap_in_obarray(obarray, &args[0])
.map(|_| Value::T)
.unwrap_or(Value::NIL))
}
pub(crate) fn builtin_event_convert_list(args: Vec<Value>) -> EvalResult {
expect_args("event-convert-list", &args, 1)?;
let Some(items) = list_to_vec(&args[0]) else {
return Ok(Value::NIL);
};
if items.is_empty() {
return Ok(Value::NIL);
}
if items.len() == 1 {
return Ok(items[0]);
}
let mut mod_bits = 0i64;
let mut base: Option<Value> = None;
for item in items {
if base.is_none() {
if let Some(sym) = item.as_symbol_name() {
if let Some(bit) = event_modifier_bit(sym) {
mod_bits |= bit;
continue;
}
}
base = Some(item);
} else {
return Err(signal(
"error",
vec![Value::string("Invalid event description")],
));
}
}
let Some(base) = base else {
return Ok(Value::NIL);
};
match base.kind() {
ValueKind::Fixnum(_) => {
let mut code = match base.kind() {
ValueKind::Fixnum(i) => i,
_ => unreachable!(),
};
let ctrl = (mod_bits & KEY_CHAR_CTRL) != 0;
let shift = (mod_bits & KEY_CHAR_SHIFT) != 0;
if shift && !ctrl && (97..=122).contains(&code) {
code -= 32;
mod_bits &= !KEY_CHAR_SHIFT;
}
if ctrl && code <= 31 {
mod_bits &= !KEY_CHAR_CTRL;
}
if ctrl && code != 32 && code != 63 {
if let Some(resolved) = resolve_control_code(code) {
if (65..=90).contains(&code) {
mod_bits |= KEY_CHAR_SHIFT;
}
code = resolved;
mod_bits &= !KEY_CHAR_CTRL;
}
}
Ok(Value::fixnum(code | mod_bits))
}
ValueKind::Symbol(id) => {
let name = resolve_sym(id);
if mod_bits == 0 {
Ok(Value::symbol(name))
} else {
Ok(Value::symbol(format!(
"{}{}",
event_modifier_prefix(mod_bits),
name
)))
}
}
ValueKind::Nil | ValueKind::T => {
if mod_bits == 0 {
Ok(base)
} else {
Err(signal(
"error",
vec![Value::string("Invalid event description")],
))
}
}
_ => Err(signal(
"error",
vec![Value::string("Invalid event description")],
)),
}
}
pub(super) fn builtin_text_char_description(args: Vec<Value>) -> EvalResult {
expect_args("text-char-description", &args, 1)?;
let code = match args[0].kind() {
ValueKind::Fixnum(n) if (0..=KEY_CHAR_CODE_MASK).contains(&n) => n,
_ => {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("characterp"), args[0]],
));
}
};
if (code & !KEY_CHAR_CODE_MASK) != 0 {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("characterp"), args[0]],
));
}
let rendered = match code {
0 => "^@".to_string(),
1..=26 => format!(
"^{}",
char::from_u32((code as u32) + 64).expect("control-letter rendering must be ASCII")
),
27 => "^[".to_string(),
28 => "^\\\\".to_string(),
29 => "^]".to_string(),
30 => "^^".to_string(),
31 => "^_".to_string(),
127 => "^?".to_string(),
_ => match char::from_u32(code as u32) {
Some(ch) => ch.to_string(),
None => {
if let Some(encoded) = {
use crate::emacs_core::emacs_char;
let c = code as u32;
if c > emacs_char::MAX_UNICODE_CHAR && c <= emacs_char::MAX_CHAR {
let mut buf = [0u8; emacs_char::MAX_MULTIBYTE_LENGTH];
let len = emacs_char::char_string(c, &mut buf);
Some(emacs_char::to_utf8_lossy(&buf[..len]))
} else {
None
}
} {
encoded
} else {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("characterp"), args[0]],
));
}
}
},
};
Ok(Value::string(rendered))
}
pub(super) fn parse_event_symbol_prefixes(mut name: &str) -> (Vec<Value>, &str) {
let mut mods = Vec::new();
loop {
if let Some(rest) = name.strip_prefix("C-") {
mods.push(Value::symbol("control"));
name = rest;
continue;
}
if let Some(rest) = name.strip_prefix("M-") {
mods.push(Value::symbol("meta"));
name = rest;
continue;
}
if let Some(rest) = name.strip_prefix("S-") {
mods.push(Value::symbol("shift"));
name = rest;
continue;
}
if let Some(rest) = name.strip_prefix("s-") {
mods.push(Value::symbol("super"));
name = rest;
continue;
}
if let Some(rest) = name.strip_prefix("H-") {
mods.push(Value::symbol("hyper"));
name = rest;
continue;
}
if let Some(rest) = name.strip_prefix("A-") {
mods.push(Value::symbol("alt"));
name = rest;
continue;
}
break;
}
(mods, name)
}
pub(super) fn builtin_single_key_description(args: Vec<Value>) -> EvalResult {
expect_range_args("single-key-description", &args, 1, 2)?;
let no_angles = args.get(1).is_some_and(|v| v.is_truthy());
Ok(Value::string(describe_single_key_value(
&args[0], no_angles,
)?))
}
pub(crate) fn builtin_key_description(args: Vec<Value>) -> EvalResult {
expect_range_args("key-description", &args, 1, 2)?;
let mut events = if let Some(prefix) = args.get(1) {
key_sequence_values(prefix)?
} else {
vec![]
};
events.extend(key_sequence_values(&args[0])?);
let rendered: Result<Vec<String>, Flow> = events
.iter()
.map(|event| describe_single_key_value(event, false))
.collect();
Ok(Value::string(rendered?.join(" ")))
}
pub(crate) fn builtin_recent_keys(eval: &mut super::eval::Context, args: Vec<Value>) -> EvalResult {
builtin_recent_keys_impl(eval, args)
}
pub(crate) fn builtin_recent_keys_impl(
ctx: &crate::emacs_core::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_max_args("recent-keys", &args, 1)?;
let include_commands = args.first().is_some_and(|arg| arg.is_truthy());
let events = ctx
.recent_input_events()
.iter()
.copied()
.filter(|event| include_commands || !(event.is_cons() && event.cons_car().is_nil()))
.collect::<Vec<_>>();
Ok(Value::vector(events))
}