use std::collections::HashMap;
use crate::buffer::{BufferId, BufferManager};
use super::error::{EvalResult, Flow, signal};
use super::hashtab::hash_key_to_visible_value;
use super::intern::resolve_sym;
use super::reader::KeyboardInputRuntime;
use super::symbol::Obarray;
use super::value::{Value, ValueKind, VecLikeType};
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_range_args(name: &str, args: &[Value], min: usize, max: usize) -> Result<(), Flow> {
if args.len() < min || args.len() > max {
Err(signal(
"wrong-number-of-arguments",
vec![Value::symbol(name), Value::fixnum(args.len() as i64)],
))
} else {
Ok(())
}
}
fn expect_string(val: &Value) -> Result<String, Flow> {
match val.kind() {
ValueKind::String => Ok(val.as_str().unwrap().to_owned()),
other => Err(signal(
"wrong-type-argument",
vec![Value::symbol("stringp"), *val],
)),
}
}
fn first_default_value(default: Value) -> Value {
match default.kind() {
ValueKind::Cons => default.cons_car(),
other => default,
}
}
fn normalize_symbol_reader_default(default: Value) -> Value {
match first_default_value(default).kind() {
ValueKind::Symbol(id) => Value::string(resolve_sym(id)),
other => first_default_value(default),
}
}
fn normalize_buffer_reader_default(buffers: &BufferManager, default: Value) -> Value {
let first = first_default_value(default);
match first.kind() {
ValueKind::Veclike(VecLikeType::Buffer) => first
.as_buffer_id()
.and_then(|id| buffers.get(id))
.map(|buffer| Value::string(&buffer.name))
.unwrap_or(first),
_ => first,
}
}
pub enum CompletionTable {
List(Vec<String>),
Function(Box<dyn Fn(&str) -> Vec<String>>),
FileNames { directory: String },
BufferNames,
SymbolNames,
Alist(Vec<(String, Value)>),
}
impl CompletionTable {
fn candidates(&self, input: &str) -> Vec<String> {
match self {
CompletionTable::List(v) => v.clone(),
CompletionTable::Function(f) => f(input),
CompletionTable::FileNames { directory } => list_files_in_dir(directory),
CompletionTable::BufferNames => Vec::new(),
CompletionTable::SymbolNames => Vec::new(),
CompletionTable::Alist(pairs) => pairs.iter().map(|(k, _)| k.clone()).collect(),
}
}
}
fn list_files_in_dir(dir: &str) -> Vec<String> {
match std::fs::read_dir(dir) {
Ok(entries) => entries
.filter_map(|e| e.ok())
.map(|e| e.file_name().to_string_lossy().into_owned())
.collect(),
Err(_) => Vec::new(),
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CompletionStyle {
Prefix,
Substring,
Flex,
Basic,
}
pub struct CompletionResult {
pub matches: Vec<String>,
pub common_prefix: Option<String>,
pub exhaustive: bool,
}
pub struct MinibufferState {
pub buffer_id: BufferId,
pub prompt: String,
pub prompt_end: usize,
pub initial_input: String,
pub history: Vec<String>,
pub history_position: Option<usize>,
pub content: String,
pub cursor_pos: usize,
pub completion_table: Option<CompletionTable>,
pub require_match: Value,
pub default_value: Option<String>,
pub active: bool,
pub depth: usize,
pub command_loop_depth: usize,
}
impl MinibufferState {
fn new(buffer_id: BufferId, prompt: String, initial: String, depth: usize) -> Self {
let cursor_pos = initial.len();
let prompt_end = prompt.len();
Self {
buffer_id,
prompt,
prompt_end,
initial_input: initial.clone(),
history: Vec::new(),
history_position: None,
content: initial,
cursor_pos,
completion_table: None,
require_match: Value::NIL,
default_value: None,
active: true,
depth,
command_loop_depth: 0,
}
}
}
pub struct MinibufferHistory {
histories: HashMap<String, Vec<String>>,
}
impl MinibufferHistory {
pub fn new() -> Self {
Self {
histories: HashMap::new(),
}
}
pub fn get(&self, name: &str) -> &[String] {
match self.histories.get(name) {
Some(v) => v.as_slice(),
None => &[],
}
}
pub fn add(&mut self, name: &str, value: &str, max_length: usize) {
let list = self.histories.entry(name.to_string()).or_default();
if list.first().map(|s| s.as_str()) != Some(value) {
list.insert(0, value.to_string());
}
if list.len() > max_length {
list.truncate(max_length);
}
}
}
impl Default for MinibufferHistory {
fn default() -> Self {
Self::new()
}
}
pub struct MinibufferManager {
state_stack: Vec<MinibufferState>,
history: MinibufferHistory,
completion_style: CompletionStyle,
enable_recursive: bool,
#[cfg(test)]
max_depth: usize,
}
impl MinibufferManager {
pub fn new() -> Self {
Self {
state_stack: Vec::new(),
history: MinibufferHistory::new(),
completion_style: CompletionStyle::Prefix,
enable_recursive: true,
#[cfg(test)]
max_depth: 10,
}
}
pub(crate) fn read_from_minibuffer(
&mut self,
buffer_id: BufferId,
prompt: &str,
initial: Option<&str>,
history_name: Option<&str>,
) -> Result<&mut MinibufferState, Flow> {
let new_depth = self.state_stack.len() + 1;
#[cfg(test)]
if new_depth > self.max_depth {
return Err(signal(
"error",
vec![Value::string(
"Command attempted to use minibuffer while in minibuffer",
)],
));
}
if !self.enable_recursive && !self.state_stack.is_empty() {
return Err(signal(
"error",
vec![Value::string(
"Command attempted to use minibuffer while in minibuffer",
)],
));
}
let initial_str = initial.unwrap_or("").to_string();
let mut state = MinibufferState::new(buffer_id, prompt.to_string(), initial_str, new_depth);
if let Some(name) = history_name {
state.history = self.history.get(name).to_vec();
}
self.state_stack.push(state);
Ok(self.state_stack.last_mut().unwrap())
}
pub fn try_complete(&self, state: &MinibufferState) -> CompletionResult {
match &state.completion_table {
Some(table) => {
let input = &state.content;
let matches = self.all_completions(input, table);
let common = compute_common_prefix(&matches);
let exhaustive = !matches!(table, CompletionTable::Function(_));
CompletionResult {
matches,
common_prefix: common,
exhaustive,
}
}
None => CompletionResult {
matches: Vec::new(),
common_prefix: None,
exhaustive: true,
},
}
}
pub fn all_completions(&self, prefix: &str, table: &CompletionTable) -> Vec<String> {
let candidates = table.candidates(prefix);
match self.completion_style {
CompletionStyle::Prefix => prefix_match(prefix, &candidates),
CompletionStyle::Substring => substring_match(prefix, &candidates),
CompletionStyle::Flex => flex_match(prefix, &candidates),
CompletionStyle::Basic => basic_match(prefix, &candidates),
}
}
pub fn try_completion_string(&self, prefix: &str, table: &CompletionTable) -> Option<String> {
let matches = self.all_completions(prefix, table);
compute_common_prefix(&matches)
}
pub fn test_completion(&self, string: &str, table: &CompletionTable) -> bool {
let candidates = table.candidates(string);
candidates.iter().any(|c| c == string)
}
pub fn exit_minibuffer(&mut self) -> Option<String> {
if let Some(mut state) = self.state_stack.pop() {
state.active = false;
let result = if state.content.is_empty() {
state.default_value.unwrap_or_default()
} else {
state.content.clone()
};
Some(result)
} else {
None
}
}
pub fn abort_minibuffer(&mut self) {
if let Some(mut state) = self.state_stack.pop() {
state.active = false;
}
}
pub fn history_previous(&mut self) -> Option<String> {
let state = self.state_stack.last_mut()?;
let history = &state.history;
if history.is_empty() {
return None;
}
let new_pos = match state.history_position {
None => 0,
Some(p) => {
if p + 1 < history.len() {
p + 1
} else {
return None; }
}
};
state.history_position = Some(new_pos);
let entry = history[new_pos].clone();
state.content = entry.clone();
state.cursor_pos = entry.len();
Some(entry)
}
pub fn history_next(&mut self) -> Option<String> {
let state = self.state_stack.last_mut()?;
match state.history_position {
None => None,
Some(0) => {
state.history_position = None;
state.content = state.initial_input.clone();
state.cursor_pos = state.initial_input.len();
Some(state.initial_input.clone())
}
Some(p) => {
let new_pos = p - 1;
state.history_position = Some(new_pos);
let entry = state.history[new_pos].clone();
state.content = entry.clone();
state.cursor_pos = entry.len();
Some(entry)
}
}
}
pub fn add_to_history(&mut self, name: &str, value: &str, max_length: usize) {
self.history.add(name, value, max_length);
}
pub fn history_length_from_obarray(obarray: &Obarray) -> usize {
match obarray.symbol_value("history-length") {
Some(v) if v.is_fixnum() && v.xfixnum() > 0 => v.xfixnum() as usize,
_ => 100,
}
}
pub fn current(&self) -> Option<&MinibufferState> {
self.state_stack.last()
}
pub fn current_mut(&mut self) -> Option<&mut MinibufferState> {
self.state_stack.last_mut()
}
pub fn depth(&self) -> usize {
self.state_stack.len()
}
pub fn is_active(&self) -> bool {
self.state_stack.last().is_some_and(|s| s.active)
}
pub fn has_buffer(&self, buffer_id: BufferId) -> bool {
self.state_stack
.iter()
.any(|state| state.buffer_id == buffer_id)
}
pub fn set_completion_style(&mut self, style: CompletionStyle) {
self.completion_style = style;
}
pub fn set_enable_recursive(&mut self, enable: bool) {
self.enable_recursive = enable;
}
}
impl Default for MinibufferManager {
fn default() -> Self {
Self::new()
}
}
fn prefix_match(input: &str, candidates: &[String]) -> Vec<String> {
let lower_input = input.to_lowercase();
candidates
.iter()
.filter(|c| c.to_lowercase().starts_with(&lower_input))
.cloned()
.collect()
}
fn substring_match(input: &str, candidates: &[String]) -> Vec<String> {
let lower_input = input.to_lowercase();
candidates
.iter()
.filter(|c| c.to_lowercase().contains(&lower_input))
.cloned()
.collect()
}
fn flex_match(input: &str, candidates: &[String]) -> Vec<String> {
candidates
.iter()
.filter(|c| is_flex_match(input, c))
.cloned()
.collect()
}
fn is_flex_match(input: &str, candidate: &str) -> bool {
let mut chars = candidate.chars().flat_map(|c| c.to_lowercase());
for ic in input.chars().flat_map(|c| c.to_lowercase()) {
loop {
match chars.next() {
Some(cc) if cc == ic => break,
Some(_) => continue,
None => return false,
}
}
}
true
}
fn basic_match(input: &str, candidates: &[String]) -> Vec<String> {
candidates
.iter()
.filter(|c| c.starts_with(input))
.cloned()
.collect()
}
fn compute_common_prefix(strings: &[String]) -> Option<String> {
if strings.is_empty() {
return None;
}
let first = &strings[0];
let mut prefix_len = first.len();
for s in &strings[1..] {
prefix_len = first
.chars()
.zip(s.chars())
.take(prefix_len)
.take_while(|(a, b)| a == b)
.count();
if prefix_len == 0 {
return Some(String::new());
}
}
Some(first.chars().take(prefix_len).collect())
}
pub(crate) fn builtin_read_file_name(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
builtin_read_file_name_in_runtime(eval, &args)?;
finish_read_file_name_in_eval(eval, &args)
}
pub(crate) fn builtin_read_directory_name(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
builtin_read_directory_name_in_runtime(eval, &args)?;
finish_read_directory_name_in_eval(eval, &args)
}
fn validate_file_name_reader_args(name: &str, args: &[Value], max: usize) -> Result<(), Flow> {
expect_min_args(name, args, 1)?;
expect_max_args(name, args, max)?;
let _prompt = expect_string(&args[0])?;
if let Some(dir) = args.get(1)
&& !dir.is_nil()
{
let _ = expect_string(dir)?;
}
if let Some(default) = args.get(2)
&& !default.is_nil()
{
let _ = expect_string(default)?;
}
if let Some(initial) = args.get(4)
&& !initial.is_nil()
{
let _ = expect_string(initial)?;
}
Ok(())
}
fn file_name_reader_minibuffer_args(args: &[Value]) -> [Value; 6] {
let prompt = args[0];
let initial = args.get(4).copied().unwrap_or(Value::NIL);
let default = args.get(2).copied().unwrap_or(Value::NIL);
let effective_initial = if initial.is_nil() {
args.get(1).copied().unwrap_or(Value::NIL)
} else {
initial
};
[
prompt,
effective_initial,
Value::NIL,
Value::NIL,
Value::NIL,
default,
]
}
pub(crate) fn builtin_read_file_name_in_runtime(
runtime: &impl KeyboardInputRuntime,
args: &[Value],
) -> Result<(), Flow> {
validate_file_name_reader_args("read-file-name", args, 6)?;
if runtime.has_input_receiver() {
Ok(())
} else {
Err(end_of_file_stdin_error())
}
}
pub(crate) fn finish_read_file_name_with_minibuffer(
args: &[Value],
mut read_from_minibuffer: impl FnMut(&[Value]) -> EvalResult,
) -> EvalResult {
let minibuffer_args = file_name_reader_minibuffer_args(args);
read_from_minibuffer(&minibuffer_args)
}
pub(crate) fn finish_read_file_name_in_eval(
eval: &mut super::eval::Context,
args: &[Value],
) -> EvalResult {
finish_read_file_name_with_minibuffer(args, |minibuffer_args| {
super::reader::finish_read_from_minibuffer_in_eval(eval, minibuffer_args)
})
}
pub(crate) fn finish_read_file_name_in_vm_runtime(
shared: &mut super::eval::Context,
vm_gc_roots: &[Value],
args: &[Value],
) -> EvalResult {
builtin_read_file_name_in_runtime(shared, args)?;
finish_read_file_name_with_minibuffer(args, |minibuffer_args| {
super::reader::finish_read_from_minibuffer_in_vm_runtime(
shared,
vm_gc_roots,
minibuffer_args,
)
})
}
pub(crate) fn builtin_read_directory_name_in_runtime(
runtime: &impl KeyboardInputRuntime,
args: &[Value],
) -> Result<(), Flow> {
validate_file_name_reader_args("read-directory-name", args, 5)?;
if runtime.has_input_receiver() {
Ok(())
} else {
Err(end_of_file_stdin_error())
}
}
pub(crate) fn finish_read_directory_name_in_eval(
eval: &mut super::eval::Context,
args: &[Value],
) -> EvalResult {
finish_read_file_name_with_minibuffer(args, |minibuffer_args| {
super::reader::finish_read_from_minibuffer_in_eval(eval, minibuffer_args)
})
}
pub(crate) fn finish_read_directory_name_in_vm_runtime(
shared: &mut super::eval::Context,
vm_gc_roots: &[Value],
args: &[Value],
) -> EvalResult {
builtin_read_directory_name_in_runtime(shared, args)?;
finish_read_file_name_with_minibuffer(args, |minibuffer_args| {
super::reader::finish_read_from_minibuffer_in_vm_runtime(
shared,
vm_gc_roots,
minibuffer_args,
)
})
}
pub(crate) fn builtin_read_buffer(eval: &mut super::eval::Context, args: Vec<Value>) -> EvalResult {
builtin_read_buffer_in_runtime(eval, &args)?;
finish_read_buffer_in_eval(eval, &args)
}
pub(crate) fn finish_read_buffer_in_eval(
eval: &mut super::eval::Context,
args: &[Value],
) -> EvalResult {
let completing_args = read_buffer_completing_args(eval.buffer_manager(), args);
super::reader::finish_completing_read_in_eval(eval, &completing_args)
}
pub(crate) fn builtin_read_buffer_in_runtime(
runtime: &impl KeyboardInputRuntime,
args: &[Value],
) -> Result<(), Flow> {
expect_min_args("read-buffer", args, 1)?;
expect_max_args("read-buffer", args, 4)?;
let _prompt = expect_string(&args[0])?;
if runtime.has_input_receiver() {
Ok(())
} else {
Err(end_of_file_stdin_error())
}
}
pub(crate) fn read_buffer_completing_args(buffers: &BufferManager, args: &[Value]) -> [Value; 7] {
let prompt = args[0];
let default =
normalize_buffer_reader_default(buffers, args.get(1).copied().unwrap_or(Value::NIL));
let require_match = args.get(2).copied().unwrap_or(Value::NIL);
let predicate = args.get(3).copied().unwrap_or(Value::NIL);
let buf_ids = buffers.buffer_list();
let buffer_names: Vec<Value> = buf_ids
.iter()
.filter_map(|id| buffers.get(*id))
.map(|b| Value::string(&b.name))
.collect();
let collection = Value::list(buffer_names);
[
prompt,
collection,
predicate,
require_match,
Value::NIL,
Value::NIL,
default,
]
}
pub(crate) fn builtin_read_command(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
builtin_read_command_in_runtime(eval, &args)?;
finish_read_command_in_eval(eval, &args)
}
pub(crate) fn finish_read_command_in_eval(
eval: &mut super::eval::Context,
args: &[Value],
) -> EvalResult {
finish_read_command_with_minibuffer(args, |minibuffer_args| {
super::reader::finish_read_from_minibuffer_in_eval(eval, minibuffer_args)
})
}
pub(crate) fn builtin_read_command_in_runtime(
runtime: &impl KeyboardInputRuntime,
args: &[Value],
) -> Result<(), Flow> {
expect_min_args("read-command", args, 1)?;
expect_max_args("read-command", args, 2)?;
let _prompt = expect_string(&args[0])?;
if runtime.has_input_receiver() {
Ok(())
} else {
Err(end_of_file_stdin_error())
}
}
fn symbol_reader_minibuffer_args(args: &[Value]) -> [Value; 6] {
let prompt = args[0];
let default = normalize_symbol_reader_default(args.get(1).copied().unwrap_or(Value::NIL));
[
prompt,
Value::NIL,
Value::NIL,
Value::NIL,
Value::NIL,
default,
]
}
fn intern_symbol_reader_result(result: Value) -> Value {
if result.is_string() {
let name = result.as_str().unwrap().to_owned();
return Value::symbol(&name);
}
result
}
fn finish_symbol_reader_with_minibuffer(
args: &[Value],
mut read_from_minibuffer: impl FnMut(&[Value]) -> EvalResult,
) -> EvalResult {
let minibuffer_args = symbol_reader_minibuffer_args(args);
let result = read_from_minibuffer(&minibuffer_args)?;
Ok(intern_symbol_reader_result(result))
}
pub(crate) fn finish_read_command_with_minibuffer(
args: &[Value],
read_from_minibuffer: impl FnMut(&[Value]) -> EvalResult,
) -> EvalResult {
finish_symbol_reader_with_minibuffer(args, read_from_minibuffer)
}
pub(crate) fn finish_read_command_in_vm_runtime(
shared: &mut super::eval::Context,
vm_gc_roots: &[Value],
args: &[Value],
) -> EvalResult {
builtin_read_command_in_runtime(shared, args)?;
finish_read_command_with_minibuffer(args, |minibuffer_args| {
super::reader::finish_read_from_minibuffer_in_vm_runtime(
shared,
vm_gc_roots,
minibuffer_args,
)
})
}
pub(crate) fn builtin_read_variable(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
builtin_read_variable_in_runtime(eval, &args)?;
finish_read_variable_in_eval(eval, &args)
}
pub(crate) fn finish_read_variable_in_eval(
eval: &mut super::eval::Context,
args: &[Value],
) -> EvalResult {
finish_read_variable_with_minibuffer(args, |minibuffer_args| {
super::reader::finish_read_from_minibuffer_in_eval(eval, minibuffer_args)
})
}
pub(crate) fn builtin_read_variable_in_runtime(
runtime: &impl KeyboardInputRuntime,
args: &[Value],
) -> Result<(), Flow> {
expect_min_args("read-variable", args, 1)?;
expect_max_args("read-variable", args, 2)?;
let _prompt = expect_string(&args[0])?;
if runtime.has_input_receiver() {
Ok(())
} else {
Err(end_of_file_stdin_error())
}
}
pub(crate) fn finish_read_variable_with_minibuffer(
args: &[Value],
read_from_minibuffer: impl FnMut(&[Value]) -> EvalResult,
) -> EvalResult {
finish_symbol_reader_with_minibuffer(args, read_from_minibuffer)
}
pub(crate) fn finish_read_variable_in_vm_runtime(
shared: &mut super::eval::Context,
vm_gc_roots: &[Value],
args: &[Value],
) -> EvalResult {
builtin_read_variable_in_runtime(shared, args)?;
finish_read_variable_with_minibuffer(args, |minibuffer_args| {
super::reader::finish_read_from_minibuffer_in_vm_runtime(
shared,
vm_gc_roots,
minibuffer_args,
)
})
}
pub(crate) fn builtin_minibuffer_prompt(args: Vec<Value>) -> EvalResult {
expect_args("minibuffer-prompt", &args, 0)?;
Ok(Value::NIL)
}
pub(crate) fn builtin_minibuffer_prompt_ctx(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("minibuffer-prompt", &args, 0)?;
Ok(eval
.minibuffers
.current()
.map(|state| Value::string(&state.prompt))
.unwrap_or(Value::NIL))
}
pub(crate) fn builtin_minibuffer_prompt_end_ctx(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("minibuffer-prompt-end", &args, 0)?;
let Some(buffer) = eval.buffers.current_buffer() else {
return Ok(Value::fixnum(1));
};
let point_min = buffer.point_min_char() as i64 + 1;
let Some(state) = eval.minibuffers.current() else {
return Ok(Value::fixnum(point_min));
};
if state.buffer_id != buffer.id {
return Ok(Value::fixnum(point_min));
}
let prompt_end_byte = state.prompt_end.min(buffer.text.len());
let prompt_end_char = buffer.text.byte_to_char(prompt_end_byte) as i64 + 1;
Ok(Value::fixnum(prompt_end_char.max(point_min)))
}
pub(crate) fn builtin_minibuffer_contents_ctx(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("minibuffer-contents", &args, 0)?;
let text = minibuffer_contents_string(&eval.minibuffers, &eval.buffers);
Ok(Value::string(text))
}
pub(crate) fn builtin_minibuffer_contents_no_properties_ctx(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("minibuffer-contents-no-properties", &args, 0)?;
let text = minibuffer_contents_string(&eval.minibuffers, &eval.buffers);
Ok(Value::string(text))
}
pub(crate) fn builtin_minibuffer_depth(args: Vec<Value>) -> EvalResult {
expect_args("minibuffer-depth", &args, 0)?;
Ok(Value::fixnum(0))
}
pub(crate) fn builtin_minibuffer_depth_ctx(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("minibuffer-depth", &args, 0)?;
Ok(Value::fixnum(eval.minibuffers.depth() as i64))
}
pub(crate) fn builtin_minibufferp(args: Vec<Value>) -> EvalResult {
validate_minibufferp_args(&args)?;
Ok(Value::NIL)
}
pub(crate) fn builtin_minibufferp_ctx(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
validate_minibufferp_args(&args)?;
let live_only = args.get(1).is_some_and(|v| v.is_truthy());
let Some(buffer_id) = resolve_minibuffer_buffer_arg(&eval.buffers, args.first())? else {
return Ok(Value::NIL);
};
let is_live = eval.minibuffers.has_buffer(buffer_id);
let is_minibuffer = is_live
|| eval
.buffers
.get(buffer_id)
.is_some_and(|buffer| is_minibuffer_buffer_name(&buffer.name));
Ok(Value::bool_val(if live_only {
is_live
} else {
is_minibuffer
}))
}
pub(crate) fn builtin_minibuffer_innermost_command_loop_p_ctx(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_range_args("minibuffer-innermost-command-loop-p", &args, 0, 1)?;
let Some(buffer_id) = resolve_minibuffer_buffer_arg(&eval.buffers, args.first())? else {
return Ok(Value::NIL);
};
let recursive_depth = eval.command_loop.recursive_depth;
let command_loop_depth = eval
.minibuffers
.state_stack
.iter()
.find(|state| state.buffer_id == buffer_id)
.map(|state| state.command_loop_depth);
Ok(Value::bool_val(
command_loop_depth.is_some_and(|depth| depth == recursive_depth),
))
}
pub(crate) fn builtin_innermost_minibuffer_p_ctx(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_range_args("innermost-minibuffer-p", &args, 0, 1)?;
let Some(buffer_id) = resolve_minibuffer_buffer_arg(&eval.buffers, args.first())? else {
return Ok(Value::NIL);
};
Ok(Value::bool_val(
eval.minibuffers
.current()
.is_some_and(|state| state.buffer_id == buffer_id),
))
}
fn validate_minibufferp_args(args: &[Value]) -> Result<(), Flow> {
if args.len() > 2 {
return Err(signal(
"wrong-number-of-arguments",
vec![
Value::symbol("minibufferp"),
Value::fixnum(args.len() as i64),
],
));
}
if let Some(bufferish) = args.first() {
match bufferish.kind() {
ValueKind::Nil | ValueKind::String | ValueKind::Veclike(VecLikeType::Buffer) => {}
_ => {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("bufferp"), *bufferish],
));
}
}
}
Ok(())
}
pub(crate) fn builtin_recursive_edit(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("recursive-edit", &args, 0)?;
eval.recursive_edit_inner()
}
pub(crate) fn builtin_top_level(args: Vec<Value>) -> EvalResult {
expect_args("top-level", &args, 0)?;
Err(Flow::Throw {
tag: Value::symbol("top-level"),
value: Value::NIL,
})
}
pub(crate) fn builtin_exit_recursive_edit(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("exit-recursive-edit", &args, 0)?;
if eval.command_loop.recursive_depth == 0 && eval.minibuffers.depth() == 0 {
return Err(signal(
"user-error",
vec![Value::string("No recursive edit is in progress")],
));
}
Err(Flow::Throw {
tag: Value::symbol("exit"),
value: Value::NIL,
})
}
pub(crate) fn builtin_exit_minibuffer(args: Vec<Value>) -> EvalResult {
expect_args("exit-minibuffer", &args, 0)?;
Err(Flow::Throw {
tag: Value::symbol("exit"),
value: Value::NIL,
})
}
pub(crate) fn builtin_abort_minibuffers(args: Vec<Value>) -> EvalResult {
expect_args("abort-minibuffers", &args, 0)?;
Err(signal("error", vec![Value::string("Not in a minibuffer")]))
}
pub(crate) fn builtin_abort_minibuffers_ctx(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("abort-minibuffers", &args, 0)?;
let in_active_minibuffer = eval
.buffers
.current_buffer_id()
.is_some_and(|buffer_id| eval.minibuffers.has_buffer(buffer_id));
if !in_active_minibuffer {
return Err(signal("error", vec![Value::string("Not in a minibuffer")]));
}
Err(Flow::Throw {
tag: Value::symbol("exit"),
value: Value::T,
})
}
fn minibuffer_contents_string(minibuffers: &MinibufferManager, buffers: &BufferManager) -> String {
let Some(buffer) = buffers.current_buffer() else {
return String::new();
};
if let Some(state) = minibuffers.current()
&& state.buffer_id == buffer.id
{
return buffer.buffer_substring(state.prompt_end.min(buffer.text.len()), buffer.text.len());
}
buffer.buffer_string()
}
fn resolve_minibuffer_buffer_arg(
buffers: &BufferManager,
bufferish: Option<&Value>,
) -> Result<Option<BufferId>, Flow> {
let Some(val) = bufferish else {
return Ok(buffers.current_buffer_id());
};
match val.kind() {
ValueKind::Nil => Ok(buffers.current_buffer_id()),
ValueKind::Veclike(VecLikeType::Buffer) => Ok(val.as_buffer_id()),
ValueKind::String => Ok(val
.as_str()
.and_then(|name| buffers.find_buffer_by_name(name))),
_ => Err(signal(
"wrong-type-argument",
vec![Value::symbol("bufferp"), *val],
)),
}
}
fn is_minibuffer_buffer_name(name: &str) -> bool {
name.starts_with(" *Minibuf-") && name.ends_with('*')
}
pub(crate) fn builtin_abort_recursive_edit(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("abort-recursive-edit", &args, 0)?;
if eval.command_loop.recursive_depth == 0 && eval.minibuffers.depth() == 0 {
return Err(signal(
"user-error",
vec![Value::string("No recursive edit is in progress")],
));
}
Err(Flow::Throw {
tag: Value::symbol("exit"),
value: Value::T,
})
}
fn value_to_string_list(val: &Value) -> Vec<String> {
match val.kind() {
ValueKind::Nil => Vec::new(),
ValueKind::Cons => {
let items = match super::value::list_to_vec(val) {
Some(v) => v,
None => return Vec::new(),
};
items
.iter()
.filter_map(|item| match item.kind() {
ValueKind::String => Some(item.as_str().unwrap().to_owned()),
ValueKind::Symbol(id) => Some(resolve_sym(id).to_owned()),
ValueKind::Cons => {
let pair_car = item.cons_car();
let pair_cdr = item.cons_cdr();
match pair_car.kind() {
ValueKind::String => Some(item.as_str().unwrap().to_owned()),
ValueKind::Symbol(id) => Some(resolve_sym(id).to_owned()),
_ => None,
}
}
_ => None,
})
.collect()
}
ValueKind::Veclike(VecLikeType::Vector) => {
let vec = val.as_vector_data().unwrap().clone();
vec.iter()
.filter_map(|item| match item.kind() {
ValueKind::String => Some(item.as_str().unwrap().to_owned()),
ValueKind::Symbol(id) => Some(resolve_sym(id).to_owned()),
_ => None,
})
.collect()
}
_ => Vec::new(),
}
}
#[derive(Clone)]
pub(crate) struct CompletionCandidate {
completion: String,
predicate_arg: Value,
predicate_extra_arg: Option<Value>,
}
fn completion_string_from_value(value: &Value) -> Option<String> {
match value.kind() {
ValueKind::String => Some(value.as_str().unwrap().to_owned()),
ValueKind::Symbol(id) => Some(resolve_sym(id).to_owned()),
ValueKind::Nil => Some("nil".to_owned()),
ValueKind::T => Some("t".to_owned()),
_ => None,
}
}
fn completion_candidates_from_list_value(collection: &Value) -> Vec<CompletionCandidate> {
let items = match super::value::list_to_vec(collection) {
Some(items) => items,
None => return Vec::new(),
};
items
.into_iter()
.filter_map(|item| {
let key = match item.kind() {
ValueKind::Cons => item.cons_car(),
other => item,
};
completion_string_from_value(&key).map(|completion| CompletionCandidate {
completion,
predicate_arg: item,
predicate_extra_arg: None,
})
})
.collect()
}
fn completion_candidates_from_vector_value(collection: &Value) -> Vec<CompletionCandidate> {
let Some(items) = collection.as_vector_data() else {
return Vec::new();
};
let items = items.clone();
items
.into_iter()
.filter_map(|item| {
completion_string_from_value(&item).map(|completion| CompletionCandidate {
completion,
predicate_arg: item,
predicate_extra_arg: None,
})
})
.collect()
}
fn is_global_obarray_proxy_in_state(obarray: &Obarray, value: &Value) -> bool {
obarray
.symbol_value("obarray")
.is_some_and(|proxy| *proxy == *value)
}
fn completion_candidates_from_global_obarray_in_state(
obarray: &Obarray,
) -> Vec<CompletionCandidate> {
let mut names: Vec<String> = obarray
.all_symbols()
.into_iter()
.map(|name| name.to_owned())
.collect();
names.sort();
names.dedup();
names
.into_iter()
.map(|name| CompletionCandidate {
completion: name.clone(),
predicate_arg: Value::symbol(name),
predicate_extra_arg: None,
})
.collect()
}
pub(crate) fn completion_candidates_from_collection_in_state(
ctx: &crate::emacs_core::eval::Context,
collection: &Value,
) -> Result<Option<Vec<CompletionCandidate>>, Flow> {
let obarray = &ctx.obarray;
Ok(match collection.kind() {
ValueKind::Nil | ValueKind::Cons => Some(completion_candidates_from_list_value(collection)),
ValueKind::Veclike(VecLikeType::HashTable) => {
Some(completion_candidates_from_hash_table(*collection))
}
ValueKind::Veclike(VecLikeType::Vector)
if is_global_obarray_proxy_in_state(obarray, collection) =>
{
Some(completion_candidates_from_global_obarray_in_state(obarray))
}
ValueKind::Veclike(VecLikeType::Vector) => {
super::builtins::symbols::expect_obarray_vector_id(collection)?;
Some(completion_candidates_from_custom_obarray(*collection))
}
_ => None,
})
}
fn completion_candidates_from_collection(
eval: &super::eval::Context,
collection: &Value,
) -> Result<Option<Vec<CompletionCandidate>>, Flow> {
completion_candidates_from_collection_in_state(eval, collection)
}
fn completion_predicate_matches_with(
predicate: Value,
candidate: &CompletionCandidate,
mut apply: impl FnMut(Value, Vec<Value>) -> EvalResult,
) -> Result<bool, Flow> {
if predicate.is_nil() {
return Ok(true);
}
let result = match candidate.predicate_extra_arg {
Some(extra) => apply(predicate, vec![candidate.predicate_arg, extra])?,
None => apply(predicate, vec![candidate.predicate_arg])?,
};
Ok(result.is_truthy())
}
pub(crate) fn builtin_try_completion_with_candidates(
args: &[Value],
candidates: Option<Vec<CompletionCandidate>>,
ignore_case: bool,
regexps: &[String],
mut apply: impl FnMut(Value, Vec<Value>) -> EvalResult,
) -> EvalResult {
expect_min_args("try-completion", args, 2)?;
expect_max_args("try-completion", args, 3)?;
let string = expect_string(&args[0])?;
let predicate = args.get(2).copied().unwrap_or(Value::NIL);
let collection = args[1];
let Some(candidates) = candidates else {
return apply(collection, vec![args[0], predicate, Value::NIL]);
};
let mut matches = Vec::new();
for candidate in &candidates {
if !completion_matches_prefix(&string, &candidate.completion, ignore_case) {
continue;
}
if !regexps.is_empty() && !matches_completion_regexps(&candidate.completion, regexps) {
continue;
}
if completion_predicate_matches_with(predicate, candidate, &mut apply)? {
matches.push(candidate.completion.clone());
}
}
if matches.is_empty() {
return Ok(Value::NIL);
}
if matches.len() == 1 && matches[0] == string {
return Ok(Value::T);
}
match compute_common_prefix(&matches) {
Some(prefix) => Ok(Value::string(prefix)),
None => Ok(Value::NIL),
}
}
pub(crate) fn builtin_all_completions_with_candidates(
args: &[Value],
candidates: Option<Vec<CompletionCandidate>>,
ignore_case: bool,
regexps: &[String],
mut apply: impl FnMut(Value, Vec<Value>) -> EvalResult,
) -> EvalResult {
expect_min_args("all-completions", args, 2)?;
expect_max_args("all-completions", args, 4)?;
let string = expect_string(&args[0])?;
let predicate = args.get(2).copied().unwrap_or(Value::NIL);
let collection = args[1];
let Some(candidates) = candidates else {
return apply(collection, vec![args[0], predicate, Value::T]);
};
let mut matches = Vec::new();
for candidate in &candidates {
if !completion_matches_prefix(&string, &candidate.completion, ignore_case) {
continue;
}
if !regexps.is_empty() && !matches_completion_regexps(&candidate.completion, regexps) {
continue;
}
if completion_predicate_matches_with(predicate, candidate, &mut apply)? {
matches.push(Value::string(candidate.completion.clone()));
}
}
Ok(Value::list(matches))
}
pub(crate) fn builtin_test_completion_with_candidates(
args: &[Value],
candidates: Option<Vec<CompletionCandidate>>,
ignore_case: bool,
regexps: &[String],
mut apply: impl FnMut(Value, Vec<Value>) -> EvalResult,
) -> EvalResult {
expect_min_args("test-completion", args, 2)?;
expect_max_args("test-completion", args, 3)?;
let string = expect_string(&args[0])?;
let predicate = args.get(2).copied().unwrap_or(Value::NIL);
let collection = args[1];
let Some(candidates) = candidates else {
return apply(
collection,
vec![args[0], predicate, Value::symbol("lambda")],
);
};
let matches = |a: &str, b: &str| -> bool {
if ignore_case {
a.to_lowercase() == b.to_lowercase()
} else {
a == b
}
};
for candidate in &candidates {
if !matches(&candidate.completion, &string) {
continue;
}
if !regexps.is_empty() && !matches_completion_regexps(&candidate.completion, regexps) {
continue;
}
if completion_predicate_matches_with(predicate, candidate, &mut apply)? {
return Ok(Value::T);
}
}
Ok(Value::NIL)
}
fn completion_candidates_from_custom_obarray(collection: Value) -> Vec<CompletionCandidate> {
let slots = collection.as_vector_data().unwrap().clone();
let mut candidates = Vec::new();
for slot in slots {
let mut current = slot;
loop {
match current.kind() {
ValueKind::Nil => break,
ValueKind::Cons => {
let pair_car = current.cons_car();
let pair_cdr = current.cons_cdr();
if let Some(completion) = completion_string_from_value(&pair_car) {
candidates.push(CompletionCandidate {
completion,
predicate_arg: pair_car,
predicate_extra_arg: None,
});
}
current = pair_cdr;
}
_ => break,
}
}
}
candidates
}
fn completion_candidates_from_hash_table(collection: Value) -> Vec<CompletionCandidate> {
let table = collection.as_hash_table().unwrap().clone();
let mut candidates = Vec::new();
for key in &table.insertion_order {
let Some(value) = table.data.get(key).copied() else {
continue;
};
let visible_key = hash_key_to_visible_value(&table, key);
if let Some(completion) = completion_string_from_value(&visible_key) {
candidates.push(CompletionCandidate {
completion,
predicate_arg: visible_key,
predicate_extra_arg: Some(value),
});
}
}
candidates
}
fn completion_matches_prefix(prefix: &str, completion: &str, ignore_case: bool) -> bool {
if ignore_case {
let lp = prefix.to_lowercase();
let lc = completion.to_lowercase();
lc.starts_with(&lp)
} else {
completion.starts_with(prefix)
}
}
fn completion_ignore_case(obarray: &Obarray) -> bool {
obarray
.symbol_value("completion-ignore-case")
.is_some_and(|v| v.is_truthy())
}
pub(crate) fn completion_regexp_list_from_obarray(obarray: &Obarray) -> Vec<String> {
completion_regexp_list(obarray)
}
fn completion_regexp_list(obarray: &Obarray) -> Vec<String> {
let Some(val) = obarray.symbol_value("completion-regexp-list").copied() else {
return Vec::new();
};
let Some(items) = super::value::list_to_vec(&val) else {
return Vec::new();
};
items
.iter()
.filter_map(|item| match item.kind() {
ValueKind::String => Some(item.as_str().unwrap().to_owned()),
_ => None,
})
.collect()
}
fn matches_completion_regexps(candidate: &str, regexps: &[String]) -> bool {
for re in regexps {
let mut md = None;
match super::regex::string_match_full_with_case_fold(re, candidate, 0, false, &mut md) {
Ok(Some(_)) => {} _ => return false, }
}
true
}
pub(crate) fn builtin_try_completion(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let candidates = completion_candidates_from_collection(eval, &args[1])?;
let ignore_case = completion_ignore_case(&eval.obarray);
let regexps = completion_regexp_list(&eval.obarray);
builtin_try_completion_with_candidates(
&args,
candidates,
ignore_case,
®exps,
|function, call_args| eval.apply(function, call_args),
)
}
pub(crate) fn builtin_all_completions(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let candidates = completion_candidates_from_collection(eval, &args[1])?;
let ignore_case = completion_ignore_case(&eval.obarray);
let regexps = completion_regexp_list(&eval.obarray);
builtin_all_completions_with_candidates(
&args,
candidates,
ignore_case,
®exps,
|function, call_args| eval.apply(function, call_args),
)
}
pub(crate) fn builtin_test_completion(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let candidates = completion_candidates_from_collection(eval, &args[1])?;
let ignore_case = completion_ignore_case(&eval.obarray);
let regexps = completion_regexp_list(&eval.obarray);
builtin_test_completion_with_candidates(
&args,
candidates,
ignore_case,
®exps,
|function, call_args| eval.apply(function, call_args),
)
}
fn end_of_file_stdin_error() -> Flow {
signal(
"end-of-file",
vec![Value::string("Error reading from stdin")],
)
}
#[cfg(test)]
#[path = "minibuffer_test.rs"]
mod tests;