use std::collections::HashMap;
use crate::buffer::{BufferId, BufferManager};
use crate::heap_types::LispString;
use super::error::{EvalResult, Flow, signal};
use super::hashtab::hash_key_to_visible_value;
use super::intern::{SymId, 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_runtime_string_owned()
.expect("ValueKind::String must carry LispString payload")),
other => Err(signal(
"wrong-type-argument",
vec![Value::symbol("stringp"), *val],
)),
}
}
fn expect_lisp_string(value: &Value) -> Result<crate::heap_types::LispString, Flow> {
value.as_lisp_string().cloned().ok_or_else(|| {
signal(
"wrong-type-argument",
vec![Value::symbol("stringp"), *value],
)
})
}
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| buffer.name_value())
.unwrap_or(first),
_ => first,
}
}
fn strip_read_buffer_prompt_suffix(prompt: &str) -> &str {
if let Some(stripped) = prompt.strip_suffix(": ") {
stripped
} else if let Some(stripped) = prompt.strip_suffix(':') {
stripped
} else if let Some(stripped) = prompt.strip_suffix(' ') {
stripped
} else {
prompt
}
}
fn format_default_prompt(format: &str, default: &str) -> String {
let mut output = String::new();
let mut chars = format.chars().peekable();
while let Some(ch) = chars.next() {
if ch == '%' {
match chars.peek().copied() {
Some('s') => {
chars.next();
output.push_str(default);
}
Some('%') => {
chars.next();
output.push('%');
}
_ => output.push(ch),
}
} else {
output.push(ch);
}
}
output
}
fn read_buffer_prompt(obarray: &Obarray, raw_prompt: Value, default: Value) -> Value {
if default.is_nil() {
return raw_prompt;
}
let Some(prompt) = raw_prompt.as_runtime_string_owned() else {
return raw_prompt;
};
let prompt_base = strip_read_buffer_prompt_suffix(&prompt);
let mut formatted = String::from(prompt_base);
if let Some(default_text) = default.as_runtime_string_owned()
&& !default_text.is_empty()
{
let default_prompt_format = obarray
.symbol_value("minibuffer-default-prompt-format")
.and_then(|value| (*value).as_runtime_string_owned())
.unwrap_or_else(|| " (default %s)".to_string());
formatted.push_str(&format_default_prompt(
&default_prompt_format,
&default_text,
));
}
formatted.push_str(": ");
Value::string(formatted)
}
pub enum CompletionTable {
List(Vec<LispString>),
Function(Box<dyn Fn(&LispString) -> Vec<LispString>>),
FileNames { directory: LispString },
BufferNames,
SymbolNames,
Alist(Vec<(LispString, Value)>),
}
impl CompletionTable {
fn candidates(&self, input: &LispString) -> Vec<LispString> {
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: &LispString) -> Vec<LispString> {
let Some(dir) = dir.as_utf8_str() else {
return Vec::new();
};
match std::fs::read_dir(dir) {
Ok(entries) => entries
.filter_map(|e| e.ok())
.map(|e| LispString::from_utf8(&e.file_name().to_string_lossy()))
.collect(),
Err(_) => Vec::new(),
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CompletionStyle {
Prefix,
Substring,
Flex,
Basic,
}
pub struct CompletionResult {
pub matches: Vec<LispString>,
pub common_prefix: Option<LispString>,
pub exhaustive: bool,
}
pub struct MinibufferState {
pub buffer_id: BufferId,
pub prompt: LispString,
pub prompt_end: usize,
pub initial_input: LispString,
pub history: Vec<LispString>,
pub history_position: Option<usize>,
pub content: LispString,
pub cursor_pos: usize,
pub completion_table: Option<CompletionTable>,
pub require_match: Value,
pub default_value: Option<LispString>,
pub active: bool,
pub depth: usize,
pub command_loop_depth: usize,
}
impl MinibufferState {
fn new(buffer_id: BufferId, prompt: LispString, initial: LispString, depth: usize) -> Self {
let cursor_pos = initial.byte_len();
let prompt_end = prompt.sbytes();
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(crate) fn install_minibuffer_buffer_text(
buf: &mut crate::buffer::Buffer,
prompt: &LispString,
initial: Option<&LispString>,
) -> usize {
buf.widen();
let text_len = buf.total_bytes();
if text_len > 0 {
buf.delete_region(0, text_len);
}
buf.goto_byte(0);
buf.insert_lisp_string(prompt);
let prompt_end = buf.total_bytes();
if prompt_end > 0 {
buf.text
.text_props_put_property(0, prompt_end, Value::symbol("field"), Value::T);
buf.text
.text_props_put_property(0, prompt_end, Value::symbol("front-sticky"), Value::T);
buf.text
.text_props_put_property(0, prompt_end, Value::symbol("rear-nonsticky"), Value::T);
}
if let Some(initial) = initial {
buf.insert_lisp_string(initial);
}
let total_len = buf.total_bytes();
buf.widen();
buf.goto_byte(total_len);
prompt_end
}
pub struct MinibufferHistory {
histories: HashMap<SymId, Vec<LispString>>,
}
impl MinibufferHistory {
pub fn new() -> Self {
Self {
histories: HashMap::new(),
}
}
pub fn get(&self, name: SymId) -> &[LispString] {
match self.histories.get(&name) {
Some(v) => v.as_slice(),
None => &[],
}
}
pub fn add(&mut self, name: SymId, value: LispString, max_length: usize) {
let list = self.histories.entry(name).or_default();
if list.first() != Some(&value) {
list.insert(0, value);
}
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_lisp(
&mut self,
buffer_id: BufferId,
prompt: &LispString,
initial: Option<&LispString>,
history_name: Option<SymId>,
) -> 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_string = initial
.cloned()
.unwrap_or_else(|| LispString::from_utf8(""));
let mut state = MinibufferState::new(buffer_id, prompt.clone(), initial_string, 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(crate) fn read_from_minibuffer(
&mut self,
buffer_id: BufferId,
prompt: &str,
initial: Option<&str>,
history_name: Option<SymId>,
) -> Result<&mut MinibufferState, Flow> {
let prompt = LispString::from_utf8(prompt);
let initial = initial.map(LispString::from_utf8);
self.read_from_minibuffer_lisp(buffer_id, &prompt, initial.as_ref(), history_name)
}
pub fn try_complete(&self, state: &MinibufferState) -> CompletionResult {
match &state.completion_table {
Some(table) => {
let matches = self.all_completions(&state.content, 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: &LispString, table: &CompletionTable) -> Vec<LispString> {
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: &LispString,
table: &CompletionTable,
) -> Option<LispString> {
let matches = self.all_completions(prefix, table);
compute_common_prefix(&matches)
}
pub fn test_completion(&self, string: &LispString, table: &CompletionTable) -> bool {
let candidates = table.candidates(string);
candidates.iter().any(|candidate| candidate == string)
}
pub fn exit_minibuffer(&mut self) -> Option<LispString> {
if let Some(mut state) = self.state_stack.pop() {
state.active = false;
let result = if state.content.is_empty() {
state
.default_value
.clone()
.unwrap_or_else(|| LispString::from_utf8(""))
} 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<LispString> {
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 = state.content.byte_len();
Some(entry)
}
pub fn history_next(&mut self) -> Option<LispString> {
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.content.byte_len();
Some(state.content.clone())
}
Some(p) => {
let new_pos = p - 1;
state.history_position = Some(new_pos);
state.content = state.history[new_pos].clone();
state.cursor_pos = state.content.byte_len();
Some(state.content.clone())
}
}
}
pub fn add_to_history(&mut self, name: SymId, value: &str, max_length: usize) {
self.add_to_history_lisp(name, LispString::from_utf8(value), max_length);
}
pub fn add_to_history_lisp(&mut self, name: SymId, value: LispString, 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: &LispString, candidates: &[LispString]) -> Vec<LispString> {
candidates
.iter()
.filter(|candidate| lisp_string_matches_prefix(input, candidate, true))
.cloned()
.collect()
}
fn substring_match(input: &LispString, candidates: &[LispString]) -> Vec<LispString> {
candidates
.iter()
.filter(|candidate| lisp_string_matches_substring(input, candidate, true))
.cloned()
.collect()
}
fn flex_match(input: &LispString, candidates: &[LispString]) -> Vec<LispString> {
candidates
.iter()
.filter(|candidate| is_flex_match(input, candidate))
.cloned()
.collect()
}
fn is_flex_match(input: &LispString, candidate: &LispString) -> bool {
let input_codes = completion_char_codes(input);
let candidate_codes = completion_char_codes(candidate);
let mut cursor = 0usize;
for code in input_codes {
let folded = completion_fold_char(code, true);
while cursor < candidate_codes.len()
&& completion_fold_char(candidate_codes[cursor], true) != folded
{
cursor += 1;
}
if cursor == candidate_codes.len() {
return false;
}
cursor += 1;
}
true
}
fn basic_match(input: &LispString, candidates: &[LispString]) -> Vec<LispString> {
candidates
.iter()
.filter(|candidate| lisp_string_matches_prefix(input, candidate, false))
.cloned()
.collect()
}
fn lisp_string_prefix_chars(string: &LispString, chars: usize) -> LispString {
let end_byte = if string.is_multibyte() {
crate::emacs_core::emacs_char::char_to_byte_pos(string.as_bytes(), chars)
} else {
chars.min(string.byte_len())
};
string
.slice(0, end_byte)
.unwrap_or_else(|| LispString::from_utf8(""))
}
fn lisp_string_matches_prefix(
input: &LispString,
candidate: &LispString,
ignore_case: bool,
) -> bool {
let input_codes = completion_char_codes(input);
let candidate_codes = completion_char_codes(candidate);
if input_codes.len() > candidate_codes.len() {
return false;
}
input_codes
.iter()
.zip(candidate_codes.iter())
.all(|(left, right)| {
completion_fold_char(*left, ignore_case) == completion_fold_char(*right, ignore_case)
})
}
fn lisp_string_matches_substring(
input: &LispString,
candidate: &LispString,
ignore_case: bool,
) -> bool {
let needle = completion_char_codes(input);
let haystack = completion_char_codes(candidate);
if needle.is_empty() {
return true;
}
if needle.len() > haystack.len() {
return false;
}
haystack.windows(needle.len()).any(|window| {
window.iter().zip(needle.iter()).all(|(left, right)| {
completion_fold_char(*left, ignore_case) == completion_fold_char(*right, ignore_case)
})
})
}
fn compute_common_prefix(strings: &[LispString]) -> Option<LispString> {
if strings.is_empty() {
return None;
}
let first = &strings[0];
let mut prefix = completion_char_codes(first);
for string in &strings[1..] {
let other = completion_char_codes(string);
let max = prefix.len().min(other.len());
let mut common = 0;
while common < max && prefix[common] == other[common] {
common += 1;
}
prefix.truncate(common);
if prefix.is_empty() {
return Some(LispString::from_utf8(""));
}
}
Some(lisp_string_prefix_chars(first, prefix.len()))
}
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,
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, 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,
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, 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.obarray(), 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(
obarray: &Obarray,
buffers: &BufferManager,
args: &[Value],
) -> [Value; 7] {
let default =
normalize_buffer_reader_default(buffers, args.get(1).copied().unwrap_or(Value::NIL));
let prompt = read_buffer_prompt(obarray, args[0], default);
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| b.name_value())
.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_runtime_string_owned()
.expect("ValueKind::String must carry LispString payload");
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,
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, 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,
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, 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::heap_string(state.prompt.clone()))
.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((current_id, point_min, point_max)) = eval.buffers.current_buffer().map(|buffer| {
(
buffer.id,
buffer.point_min_char() as i64 + 1,
buffer.point_max_char() as i64 + 1,
)
}) else {
return Ok(Value::fixnum(1));
};
let Some(state) = eval.minibuffers.current() else {
return Ok(Value::fixnum(point_min));
};
if state.buffer_id != current_id {
return Ok(Value::fixnum(point_min));
}
let prompt_end =
super::builtins::dispatch_builtin(eval, "field-end", vec![Value::fixnum(point_min)])
.expect("field-end builtin must exist")?;
let prompt_end_pos = prompt_end.as_fixnum().unwrap_or(point_min);
if prompt_end_pos == point_max {
let buffer = eval
.buffers
.get(current_id)
.expect("current minibuffer must remain available");
let point_min_byte = buffer.lisp_pos_to_accessible_byte(point_min);
if buffer
.text
.text_props_get_property(point_min_byte, Value::symbol("field"))
.is_none()
{
return Ok(Value::fixnum(point_min));
}
}
Ok(prompt_end)
}
pub(crate) fn builtin_minibuffer_contents_ctx(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("minibuffer-contents", &args, 0)?;
Ok(Value::heap_string(minibuffer_contents_lisp_string(eval)?))
}
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)?;
Ok(Value::heap_string(minibuffer_contents_lisp_string(eval)?))
}
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_runtime_string_owned()));
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.recursive_command_loop_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.recursive_command_loop_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_lisp_string(eval: &mut super::eval::Context) -> Result<LispString, Flow> {
let Some((point_max, current_id)) = eval
.buffers
.current_buffer()
.map(|buffer| (buffer.point_max(), buffer.id))
else {
return Ok(LispString::from_utf8(""));
};
let prompt_end = builtin_minibuffer_prompt_end_ctx(eval, vec![])?;
let prompt_end_pos = prompt_end.as_fixnum().unwrap_or(1);
let buffer = eval
.buffers
.get(current_id)
.expect("current buffer must remain available");
let start = buffer.lisp_pos_to_accessible_byte(prompt_end_pos);
Ok(buffer.buffer_substring_lisp_string(start, point_max))
}
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_utf8_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.recursive_command_loop_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 => completion_display_string_from_value(item),
ValueKind::Symbol(id) => Some(resolve_sym(id).to_owned()),
ValueKind::Cons => {
let pair_car = item.cons_car();
completion_display_string_from_value(&pair_car)
}
_ => None,
})
.collect()
}
ValueKind::Veclike(VecLikeType::Vector) => {
let vec = val.as_vector_data().unwrap().clone();
vec.iter()
.filter_map(|item| match item.kind() {
ValueKind::String => completion_display_string_from_value(item),
ValueKind::Symbol(id) => Some(resolve_sym(id).to_owned()),
_ => None,
})
.collect()
}
_ => Vec::new(),
}
}
#[derive(Clone)]
pub(crate) struct CompletionCandidate {
completion: CompletionText,
predicate_arg: Value,
predicate_extra_arg: Option<Value>,
}
#[derive(Clone)]
enum CompletionText {
OriginalString {
value: Value,
string: crate::heap_types::LispString,
},
Generated {
string: crate::heap_types::LispString,
},
}
impl CompletionText {
fn lisp_string(&self) -> &crate::heap_types::LispString {
match self {
Self::OriginalString { string, .. } | Self::Generated { string } => string,
}
}
fn as_result_value(&self) -> Value {
match self {
Self::OriginalString { value, .. } => *value,
Self::Generated { string } => Value::heap_string(string.clone()),
}
}
fn substring_value(&self, end_chars: usize) -> Value {
match self {
Self::OriginalString { value, string } if end_chars >= string.schars() => *value,
_ => {
let string = self.lisp_string();
let end_chars = end_chars.min(string.schars());
let end_byte = if string.is_multibyte() {
crate::emacs_core::emacs_char::char_to_byte_pos(string.as_bytes(), end_chars)
} else {
end_chars.min(string.byte_len())
};
let sliced = string
.slice(0, end_byte)
.expect("validated completion prefix slice");
Value::heap_string(sliced)
}
}
}
fn searched_string(&self) -> super::regex::SearchedString {
match self {
Self::OriginalString { value, .. } => super::regex::SearchedString::Heap(*value),
Self::Generated { string } => super::regex::SearchedString::Owned(string.clone()),
}
}
}
fn completion_text_from_value(value: &Value) -> Option<CompletionText> {
match value.kind() {
ValueKind::String => {
value
.as_lisp_string()
.cloned()
.map(|string| CompletionText::OriginalString {
value: *value,
string,
})
}
ValueKind::Symbol(id) => Some(CompletionText::Generated {
string: crate::heap_types::LispString::from_utf8(resolve_sym(id)),
}),
ValueKind::Nil => Some(CompletionText::Generated {
string: crate::heap_types::LispString::from_utf8("nil"),
}),
ValueKind::T => Some(CompletionText::Generated {
string: crate::heap_types::LispString::from_utf8("t"),
}),
_ => None,
}
}
fn completion_display_string_from_value(value: &Value) -> Option<String> {
let completion = completion_text_from_value(value)?;
let string = completion.lisp_string();
Some(
string
.as_utf8_str()
.map(|text| text.to_owned())
.unwrap_or_else(|| crate::emacs_core::emacs_char::to_utf8_lossy(string.as_bytes())),
)
}
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_text_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_text_from_value(&item).map(|completion| CompletionCandidate {
completion,
predicate_arg: item,
predicate_extra_arg: None,
})
})
.collect()
}
fn completion_char_codes(string: &crate::heap_types::LispString) -> Vec<u32> {
super::builtins::lisp_string_char_codes(string)
}
fn completion_fold_char(code: u32, ignore_case: bool) -> u32 {
if !ignore_case {
return code;
}
crate::emacs_core::builtins::downcase_char_code_emacs_compat(code as i64) as u32
}
fn completion_text_matches_prefix(
prefix: &crate::heap_types::LispString,
completion: &CompletionText,
ignore_case: bool,
) -> bool {
let prefix_codes = completion_char_codes(prefix);
let completion_codes = completion_char_codes(completion.lisp_string());
if prefix_codes.len() > completion_codes.len() {
return false;
}
prefix_codes
.iter()
.zip(completion_codes.iter())
.all(|(left, right)| {
completion_fold_char(*left, ignore_case) == completion_fold_char(*right, ignore_case)
})
}
fn completion_text_equals_string(
completion: &CompletionText,
string: &crate::heap_types::LispString,
ignore_case: bool,
) -> bool {
let left = completion_char_codes(completion.lisp_string());
let right = completion_char_codes(string);
left.len() == right.len()
&& left.iter().zip(right.iter()).all(|(l, r)| {
completion_fold_char(*l, ignore_case) == completion_fold_char(*r, ignore_case)
})
}
fn completion_common_prefix_len(matches: &[&CompletionCandidate], ignore_case: bool) -> usize {
let Some((first, rest)) = matches.split_first() else {
return 0;
};
let mut prefix = completion_char_codes(first.completion.lisp_string());
for candidate in rest {
let other = completion_char_codes(candidate.completion.lisp_string());
let max = prefix.len().min(other.len());
let mut common = 0;
while common < max
&& completion_fold_char(prefix[common], ignore_case)
== completion_fold_char(other[common], ignore_case)
{
common += 1;
}
prefix.truncate(common);
if prefix.is_empty() {
break;
}
}
prefix.len()
}
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 entries: Vec<(crate::heap_types::LispString, Value)> = obarray
.global_member_ids()
.map(|id| {
(
crate::emacs_core::intern::resolve_sym_lisp_string(id).clone(),
Value::from_sym_id(id),
)
})
.collect();
entries.sort_by(|(left, _), (right, _)| {
left.as_bytes()
.cmp(right.as_bytes())
.then(left.is_multibyte().cmp(&right.is_multibyte()))
});
entries.dedup_by(|(left_name, left_sym), (right_name, right_sym)| {
left_name == right_name && left_sym.bits() == right_sym.bits()
});
entries
.into_iter()
.map(|(name, sym)| CompletionCandidate {
completion: CompletionText::Generated { string: name },
predicate_arg: sym,
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: &[crate::heap_types::LispString],
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_lisp_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_text_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);
}
}
if matches.is_empty() {
return Ok(Value::NIL);
}
if matches.len() == 1 && completion_text_equals_string(&matches[0].completion, &string, false) {
return Ok(Value::T);
}
Ok(matches[0]
.completion
.substring_value(completion_common_prefix_len(&matches, ignore_case)))
}
pub(crate) fn builtin_all_completions_with_candidates(
args: &[Value],
candidates: Option<Vec<CompletionCandidate>>,
ignore_case: bool,
regexps: &[crate::heap_types::LispString],
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_lisp_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 matching_completions: Vec<CompletionText> = Vec::new();
for candidate in &candidates {
if !completion_text_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)? {
matching_completions.push(candidate.completion.clone());
}
}
let matches: Vec<Value> = matching_completions
.into_iter()
.map(|completion| completion.as_result_value())
.collect();
Ok(Value::list(matches))
}
pub(crate) fn builtin_test_completion_with_candidates(
args: &[Value],
candidates: Option<Vec<CompletionCandidate>>,
ignore_case: bool,
regexps: &[crate::heap_types::LispString],
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_lisp_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")],
);
};
for candidate in &candidates {
if !completion_text_equals_string(&candidate.completion, &string, ignore_case) {
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_text_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_text_from_value(&visible_key) {
candidates.push(CompletionCandidate {
completion,
predicate_arg: visible_key,
predicate_extra_arg: Some(value),
});
}
}
candidates
}
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_lisp_list_from_obarray(obarray)
.into_iter()
.map(|regexp| {
regexp
.as_utf8_str()
.map(|text| text.to_owned())
.unwrap_or_else(|| crate::emacs_core::emacs_char::to_utf8_lossy(regexp.as_bytes()))
})
.collect()
}
pub(crate) fn completion_regexp_lisp_list_from_obarray(
obarray: &Obarray,
) -> Vec<crate::heap_types::LispString> {
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| item.as_lisp_string().cloned())
.collect()
}
fn matches_completion_regexps(
candidate: &CompletionText,
regexps: &[crate::heap_types::LispString],
) -> bool {
for re in regexps {
let mut md = None;
match super::regex::string_match_full_with_case_fold_source_lisp_pattern_posix(
re,
candidate.lisp_string(),
candidate.searched_string(),
0,
false,
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_lisp_list_from_obarray(&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_lisp_list_from_obarray(&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_lisp_list_from_obarray(&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;