use super::error::{EvalResult, Flow, signal};
use super::intern::resolve_sym;
use super::minibuffer::MinibufferManager;
use super::value::{Value, ValueKind, VecLikeType, list_to_vec};
use crate::buffer::{BufferId, BufferManager};
use crate::window::{
FrameId, FrameManager, Rect, SplitDirection, Window, WindowBufferDisplayDefaults, WindowId,
window_first_child_id, window_next_sibling_id, window_parent_id, window_prev_sibling_id,
};
use std::collections::HashSet;
pub(crate) use super::builtins::symbols::{
builtin_resize_mini_window_internal, builtin_set_window_new_normal,
builtin_set_window_new_pixel, builtin_set_window_new_total,
};
pub(crate) use super::builtins::{
builtin_combine_windows, builtin_uncombine_window, builtin_window_bottom_divider_width,
builtin_window_cursor_info, builtin_window_lines_pixel_dimensions, builtin_window_new_normal,
builtin_window_new_pixel, builtin_window_new_total, builtin_window_old_body_pixel_height,
builtin_window_old_body_pixel_width, builtin_window_old_pixel_height,
builtin_window_old_pixel_width, builtin_window_right_divider_width,
};
pub(crate) use super::builtins::{
builtin_coordinates_in_window_p, builtin_current_window_configuration,
builtin_run_window_configuration_change_hook, builtin_run_window_scroll_functions,
builtin_set_window_configuration, builtin_split_window_internal,
builtin_window_configuration_equal_p, builtin_window_configuration_frame,
builtin_window_configuration_p,
};
fn expect_args(name: &str, args: &[Value], n: usize) -> Result<(), Flow> {
if args.len() != n {
Err(signal(
"wrong-number-of-arguments",
vec![Value::symbol(name), Value::fixnum(args.len() as i64)],
))
} else {
Ok(())
}
}
fn expect_min_args(name: &str, args: &[Value], min: usize) -> Result<(), Flow> {
if args.len() < min {
Err(signal(
"wrong-number-of-arguments",
vec![Value::symbol(name), Value::fixnum(args.len() as i64)],
))
} else {
Ok(())
}
}
fn expect_max_args(name: &str, args: &[Value], max: usize) -> Result<(), Flow> {
if args.len() > max {
Err(signal(
"wrong-number-of-arguments",
vec![Value::symbol(name), Value::fixnum(args.len() as i64)],
))
} else {
Ok(())
}
}
fn expect_int(value: &Value) -> Result<i64, Flow> {
match value.kind() {
ValueKind::Fixnum(n) => Ok(n),
other => Err(signal(
"wrong-type-argument",
vec![Value::symbol("integerp"), *value],
)),
}
}
fn expect_number(value: &Value) -> Result<f64, Flow> {
match value.kind() {
ValueKind::Fixnum(n) => Ok(n as f64),
ValueKind::Float => Ok(value.xfloat()),
_ => Err(signal(
"wrong-type-argument",
vec![Value::symbol("numberp"), *value],
)),
}
}
#[derive(Clone, Debug)]
enum IntegerOrMarkerArg {
Int(i64),
Marker { raw: Value, position: Option<i64> },
}
fn parse_integer_or_marker_arg(value: &Value) -> Result<IntegerOrMarkerArg, Flow> {
match value.kind() {
ValueKind::Fixnum(n) => Ok(IntegerOrMarkerArg::Int(n)),
_ if value.is_marker() => {
let position = if let Some(elems) = value.as_vector_data() {
match elems.get(2).map(|v| v.kind()) {
Some(ValueKind::Fixnum(n)) => Some(n),
_ => None,
}
} else {
None
};
Ok(IntegerOrMarkerArg::Marker {
raw: *value,
position,
})
}
_ => Err(signal(
"wrong-type-argument",
vec![Value::symbol("integer-or-marker-p"), *value],
)),
}
}
fn expect_number_or_marker_count(value: &Value) -> Result<i64, Flow> {
match value.kind() {
ValueKind::Fixnum(n) => Ok(n),
ValueKind::Float => Ok(value.xfloat().floor() as i64),
_ if value.is_marker() => match parse_integer_or_marker_arg(value)? {
IntegerOrMarkerArg::Marker {
position: Some(pos),
..
} => Ok(pos),
IntegerOrMarkerArg::Marker { position: None, .. } => Err(signal(
"error",
vec![Value::string("Marker does not point anywhere")],
)),
IntegerOrMarkerArg::Int(pos) => Ok(pos),
},
_ => Err(signal(
"wrong-type-argument",
vec![Value::symbol("number-or-marker-p"), *value],
)),
}
}
fn clamped_window_position(
eval: &super::eval::Context,
fid: FrameId,
wid: WindowId,
pos: i64,
) -> Option<usize> {
clamped_window_position_in_state(&eval.frames, &eval.buffers, fid, wid, pos)
}
fn clamped_window_position_in_state(
frames: &FrameManager,
buffers: &BufferManager,
fid: FrameId,
wid: WindowId,
pos: i64,
) -> Option<usize> {
if pos <= 0 {
return None;
}
let requested = pos as usize;
let Some(Window::Leaf { buffer_id, .. }) =
frames.get(fid).and_then(|frame| frame.find_window(wid))
else {
return Some(requested);
};
let buffer_end = buffers
.get(*buffer_id)
.map(|buf| buf.text.char_count().saturating_add(1))
.unwrap_or(requested);
Some(requested.min(buffer_end.max(1)))
}
fn expect_fixnum(value: &Value) -> Result<i64, Flow> {
match value.kind() {
ValueKind::Fixnum(n) => Ok(n),
other => Err(signal(
"wrong-type-argument",
vec![Value::symbol("fixnump"), *value],
)),
}
}
fn expect_number_or_marker(value: &Value) -> Result<f64, Flow> {
match value.kind() {
ValueKind::Fixnum(n) => Ok(n as f64),
ValueKind::Float => Ok(value.xfloat()),
_ => Err(signal(
"wrong-type-argument",
vec![Value::symbol("number-or-marker-p"), *value],
)),
}
}
fn expect_margin_width(value: &Value) -> Result<usize, Flow> {
const MAX_MARGIN: i64 = 2_147_483_647;
match value.kind() {
ValueKind::Nil => Ok(0),
ValueKind::Fixnum(n) => {
if n < 0 || n > MAX_MARGIN {
return Err(signal(
"args-out-of-range",
vec![
Value::fixnum(n),
Value::fixnum(0),
Value::fixnum(MAX_MARGIN),
],
));
}
Ok(n as usize)
}
other => Err(signal(
"wrong-type-argument",
vec![Value::symbol("integerp"), *value],
)),
}
}
fn buffer_margin_width(
buffers: &BufferManager,
buffer_id: BufferId,
name: &str,
) -> Result<usize, Flow> {
let value = buffers
.get(buffer_id)
.and_then(|buffer| buffer.buffer_local_value(name))
.unwrap_or(Value::NIL);
expect_margin_width(&value)
}
fn buffer_local_value(buffers: &BufferManager, buffer_id: BufferId, name: &str) -> Value {
buffers
.get(buffer_id)
.and_then(|buffer| buffer.buffer_local_value(name))
.unwrap_or(Value::NIL)
}
fn buffer_local_optional_dimension(
buffers: &BufferManager,
buffer_id: BufferId,
name: &str,
) -> Result<Option<i32>, Flow> {
let value = buffer_local_value(buffers, buffer_id, name);
if value.is_nil() {
Ok(None)
} else {
Ok(Some(i32::try_from(expect_int(&value)?).map_err(|_| {
signal(
"args-out-of-range",
vec![value, Value::fixnum(0), Value::fixnum(i64::from(i32::MAX))],
)
})?))
}
}
fn valid_vertical_scroll_bar_type(value: Value) -> bool {
value.is_nil() || value == Value::T || matches!(value.as_symbol_name(), Some("left" | "right"))
}
fn valid_horizontal_scroll_bar_type(value: Value) -> bool {
value.is_nil() || value == Value::T || matches!(value.as_symbol_name(), Some("bottom"))
}
fn window_value(wid: WindowId) -> Value {
Value::make_window(wid.0)
}
fn resolve_window_frame_id_for_pred(
frames: &FrameManager,
wid: WindowId,
pred: &str,
) -> Option<FrameId> {
match pred {
"window-valid-p" => frames.find_valid_window_frame_id(wid),
_ => frames.find_window_frame_id(wid),
}
}
fn window_id_from_designator(value: &Value) -> Option<WindowId> {
match value.kind() {
ValueKind::Veclike(VecLikeType::Window) => Some(WindowId(value.as_window_id().unwrap())),
ValueKind::Fixnum(n) if n >= 0 => Some(WindowId(n as u64)),
_ => None,
}
}
fn resolve_window_id_with_pred(
eval: &mut super::eval::Context,
arg: Option<&Value>,
pred: &str,
) -> Result<(FrameId, WindowId), Flow> {
resolve_window_id_with_pred_in_state(&mut eval.frames, &mut eval.buffers, arg, pred)
}
fn resolve_window_id_with_pred_in_state(
frames: &mut FrameManager,
buffers: &mut BufferManager,
arg: Option<&Value>,
pred: &str,
) -> Result<(FrameId, WindowId), Flow> {
if arg.is_none_or(|v| v.is_nil()) {
let frame_id = ensure_selected_frame_id_in_state(frames, buffers);
let frame = frames
.get(frame_id)
.ok_or_else(|| signal("error", vec![Value::string("No selected frame")]))?;
return Ok((frame_id, frame.selected_window));
}
let val = arg.unwrap(); let Some(wid) = window_id_from_designator(val) else {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol(pred), *val],
));
};
if let Some(frame_id) = resolve_window_frame_id_for_pred(frames, wid, pred) {
Ok((frame_id, wid))
} else {
Err(signal(
"wrong-type-argument",
vec![Value::symbol(pred), *val],
))
}
}
fn resolve_window_id(
eval: &mut super::eval::Context,
arg: Option<&Value>,
) -> Result<(FrameId, WindowId), Flow> {
resolve_window_id_with_pred(eval, arg, "window-live-p")
}
fn resolve_window_id_in_state(
frames: &mut FrameManager,
buffers: &mut BufferManager,
arg: Option<&Value>,
) -> Result<(FrameId, WindowId), Flow> {
resolve_window_id_with_pred_in_state(frames, buffers, arg, "window-live-p")
}
fn resolve_window_object_id_with_pred(
eval: &mut super::eval::Context,
arg: Option<&Value>,
pred: &str,
) -> Result<WindowId, Flow> {
resolve_window_object_id_with_pred_in_state(&mut eval.frames, &mut eval.buffers, arg, pred)
}
fn resolve_window_object_id_with_pred_in_state(
frames: &mut FrameManager,
buffers: &mut BufferManager,
arg: Option<&Value>,
pred: &str,
) -> Result<WindowId, Flow> {
if arg.is_none_or(|v| v.is_nil()) {
let (_fid, wid) = resolve_window_id_with_pred_in_state(frames, buffers, None, pred)?;
return Ok(wid);
}
let val = arg.unwrap();
let Some(wid) = window_id_from_designator(val) else {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol(pred), *val],
));
};
if frames.is_window_object_id(wid) {
Ok(wid)
} else {
Err(signal(
"wrong-type-argument",
vec![Value::symbol(pred), *val],
))
}
}
fn resolve_window_id_or_error(
eval: &mut super::eval::Context,
arg: Option<&Value>,
) -> Result<(FrameId, WindowId), Flow> {
resolve_window_id_or_error_in_state(&mut eval.frames, &mut eval.buffers, arg)
}
fn resolve_window_id_or_error_in_state(
frames: &mut FrameManager,
buffers: &mut BufferManager,
arg: Option<&Value>,
) -> Result<(FrameId, WindowId), Flow> {
if arg.is_none_or(|v| v.is_nil()) {
return resolve_window_id_in_state(frames, buffers, arg);
}
let value = arg.unwrap();
let Some(wid) = window_id_from_designator(value) else {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("windowp"), *value],
));
};
if let Some(fid) = frames.find_window_frame_id(wid) {
Ok((fid, wid))
} else {
Err(signal(
"wrong-type-argument",
vec![Value::symbol("window-valid-p"), *value],
))
}
}
fn format_window_designator_for_error(eval: &super::eval::Context, value: &Value) -> String {
if let Some(wid) = window_id_from_designator(value) {
if eval.frames.is_window_object_id(wid) || value.is_window() {
return format!("#<window {}>", wid.0);
}
}
super::print::print_value(value)
}
fn resolve_window_id_or_window_error(
eval: &mut super::eval::Context,
arg: Option<&Value>,
live_only: bool,
) -> Result<(FrameId, WindowId), Flow> {
resolve_window_id_or_window_error_in_state(&mut eval.frames, &mut eval.buffers, arg, live_only)
}
fn format_window_designator_for_error_in_state(frames: &FrameManager, value: &Value) -> String {
if let Some(wid) = window_id_from_designator(value) {
if frames.is_window_object_id(wid) || value.is_window() {
return format!("#<window {}>", wid.0);
}
}
super::print::print_value(value)
}
fn resolve_window_id_or_window_error_in_state(
frames: &mut FrameManager,
buffers: &mut BufferManager,
arg: Option<&Value>,
live_only: bool,
) -> Result<(FrameId, WindowId), Flow> {
if arg.is_none_or(|v| v.is_nil()) {
return resolve_window_id_with_pred_in_state(frames, buffers, arg, "window-live-p");
}
let val = arg.unwrap();
let Some(wid) = window_id_from_designator(val) else {
let window_kind = if live_only { "live" } else { "valid" };
return Err(signal(
"error",
vec![Value::string(format!(
"{} is not a {} window",
format_window_designator_for_error_in_state(frames, val),
window_kind
))],
));
};
let frame_id = if live_only {
frames.find_window_frame_id(wid)
} else {
frames.find_valid_window_frame_id(wid)
};
if let Some(fid) = frame_id {
Ok((fid, wid))
} else {
let window_kind = if live_only { "live" } else { "valid" };
Err(signal(
"error",
vec![Value::string(format!(
"{} is not a {} window",
format_window_designator_for_error_in_state(frames, val),
window_kind
))],
))
}
}
fn resolve_frame_id(
eval: &mut super::eval::Context,
arg: Option<&Value>,
predicate: &str,
) -> Result<FrameId, Flow> {
resolve_frame_id_in_state(&mut eval.frames, &mut eval.buffers, arg, predicate)
}
pub(crate) fn resolve_frame_id_in_state(
frames: &mut FrameManager,
buffers: &mut BufferManager,
arg: Option<&Value>,
predicate: &str,
) -> Result<FrameId, Flow> {
if arg.is_none_or(|v| v.is_nil()) {
return Ok(ensure_selected_frame_id_in_state(frames, buffers));
}
let val = arg.unwrap();
match val.kind() {
ValueKind::Fixnum(n) => {
let fid = FrameId(n as u64);
if frames.get(fid).is_some() {
Ok(fid)
} else {
Err(signal(
"wrong-type-argument",
vec![Value::symbol(predicate), Value::fixnum(n)],
))
}
}
ValueKind::Veclike(VecLikeType::Frame) => {
let raw_id = val.as_frame_id().unwrap();
let fid = FrameId(raw_id);
if frames.get(fid).is_some() {
Ok(fid)
} else {
Err(signal(
"wrong-type-argument",
vec![Value::symbol(predicate), Value::make_frame(raw_id)],
))
}
}
_ => Err(signal(
"wrong-type-argument",
vec![Value::symbol(predicate), *val],
)),
}
}
fn resolve_frame_or_window_frame_id(
eval: &mut super::eval::Context,
arg: Option<&Value>,
predicate: &str,
) -> Result<FrameId, Flow> {
resolve_frame_or_window_frame_id_in_state(&mut eval.frames, &mut eval.buffers, arg, predicate)
}
fn resolve_frame_or_window_frame_id_in_state(
frames: &mut FrameManager,
buffers: &mut BufferManager,
arg: Option<&Value>,
predicate: &str,
) -> Result<FrameId, Flow> {
if arg.is_none_or(|v| v.is_nil()) {
return Ok(ensure_selected_frame_id_in_state(frames, buffers));
}
let val = arg.unwrap();
match val.kind() {
ValueKind::Veclike(VecLikeType::Frame) => {
let raw_id = val.as_frame_id().unwrap();
let fid = FrameId(raw_id);
if frames.get(fid).is_some() {
Ok(fid)
} else {
Err(signal(
"wrong-type-argument",
vec![Value::symbol(predicate), Value::make_frame(raw_id)],
))
}
}
ValueKind::Fixnum(n) => {
let fid = FrameId(n as u64);
if frames.get(fid).is_some() {
return Ok(fid);
}
let wid = WindowId(n as u64);
if let Some(fid) = frames.find_valid_window_frame_id(wid) {
return Ok(fid);
}
Err(signal(
"wrong-type-argument",
vec![Value::symbol(predicate), Value::fixnum(n)],
))
}
ValueKind::Veclike(VecLikeType::Window) => {
let raw_id = val.as_window_id().unwrap();
let wid = WindowId(raw_id);
if let Some(fid) = frames.find_valid_window_frame_id(wid) {
return Ok(fid);
}
Err(signal(
"wrong-type-argument",
vec![Value::symbol(predicate), Value::make_window(raw_id)],
))
}
_ => Err(signal(
"wrong-type-argument",
vec![Value::symbol(predicate), *val],
)),
}
}
fn get_leaf(frames: &FrameManager, fid: FrameId, wid: WindowId) -> Result<&Window, Flow> {
let frame = frames
.get(fid)
.ok_or_else(|| signal("error", vec![Value::string("Frame not found")]))?;
frame
.find_window(wid)
.ok_or_else(|| signal("error", vec![Value::string("Window not found")]))
}
fn get_window(frames: &FrameManager, fid: FrameId, wid: WindowId) -> Result<&Window, Flow> {
let frame = frames
.get(fid)
.ok_or_else(|| signal("error", vec![Value::string("Frame not found")]))?;
frame
.find_window(wid)
.ok_or_else(|| signal("error", vec![Value::string("Window not found")]))
}
pub(crate) fn ensure_selected_frame_id(eval: &mut super::eval::Context) -> FrameId {
ensure_selected_frame_id_in_state(&mut eval.frames, &mut eval.buffers)
}
pub(crate) fn ensure_selected_frame_id_in_state(
frames: &mut FrameManager,
buffers: &mut BufferManager,
) -> FrameId {
ensure_selected_frame_id_in_state_with_policy(frames, buffers, true)
}
pub(crate) fn seed_batch_startup_frame_in_state(
frames: &mut FrameManager,
buffers: &mut BufferManager,
) -> FrameId {
ensure_selected_frame_id_in_state_with_policy(frames, buffers, false)
}
fn ensure_selected_frame_id_in_state_with_policy(
frames: &mut FrameManager,
buffers: &mut BufferManager,
warn_on_create: bool,
) -> FrameId {
if let Some(fid) = frames.selected_frame().map(|f| f.id) {
return fid;
}
if warn_on_create {
tracing::warn!(
"ensure_selected_frame_id_in_state: no selected frame present; synthesizing fallback batch-style frame"
);
}
let buf_id = buffers
.current_buffer()
.map(|b| b.id)
.unwrap_or_else(|| buffers.create_buffer("*scratch*"));
let fid = frames.create_frame("F1", 80, 25, buf_id);
let minibuffer_buf_id = buffers
.find_buffer_by_name(" *Minibuf-0*")
.unwrap_or_else(|| buffers.create_buffer(" *Minibuf-0*"));
if let Some(frame) = frames.get_mut(fid) {
frame.char_width = 1.0;
frame.char_height = 1.0;
frame.font_pixel_size = 1.0;
frame
.parameters
.insert("width".to_string(), Value::fixnum(80));
frame
.parameters
.insert("height".to_string(), Value::fixnum(25));
frame
.root_window
.set_bounds(Rect::new(0.0, 0.0, 80.0, 24.0));
if let Some(Window::Leaf {
window_start,
point,
..
}) = frame.find_window_mut(frame.selected_window)
{
*window_start = 1;
*point = 1;
}
if let Some(minibuffer_leaf) = frame.minibuffer_leaf.as_mut() {
minibuffer_leaf.set_buffer(minibuffer_buf_id);
minibuffer_leaf.set_bounds(Rect::new(0.0, 24.0, 80.0, 1.0));
}
}
fid
}
fn window_height_lines(w: &Window, char_height: f32) -> i64 {
let h = w.bounds().height;
if char_height > 0.0 {
(h / char_height) as i64
} else {
0
}
}
fn window_width_cols(w: &Window, char_width: f32) -> i64 {
let cw = w.bounds().width;
if char_width > 0.0 {
(cw / char_width) as i64
} else {
0
}
}
fn window_height_pixels(w: &Window) -> i64 {
w.bounds().height.max(0.0) as i64
}
fn window_width_pixels(w: &Window) -> i64 {
w.bounds().width.max(0.0) as i64
}
fn window_body_horizontal_offsets_pixels(
frames: &FrameManager,
fid: FrameId,
w: &Window,
) -> (i64, i64) {
let Some(frame) = frames.get(fid) else {
return (0, 0);
};
if frame.effective_window_system().is_none() {
return (0, 0);
}
match w {
Window::Leaf { margins, .. } => {
let char_width = frame.char_width.max(1.0);
let left_margin = (margins.0 as f32 * char_width).round().max(0.0) as i64;
let right_margin = (margins.1 as f32 * char_width).round().max(0.0) as i64;
let (left_fringe, right_fringe, _, _) = frames
.window_fringes(w.id())
.unwrap_or((0, 0, false, false));
let left_scroll_bar = frames.window_left_scroll_bar_area_width(w.id());
let right_scroll_bar = frames.window_right_scroll_bar_area_width(w.id());
(
left_scroll_bar
.saturating_add(left_fringe)
.saturating_add(left_margin),
right_scroll_bar
.saturating_add(right_fringe)
.saturating_add(right_margin),
)
}
Window::Internal { .. } => (0, 0),
}
}
fn window_body_width_pixels(frames: &FrameManager, fid: FrameId, w: &Window) -> i64 {
let total = window_width_pixels(w);
let (left, right) = window_body_horizontal_offsets_pixels(frames, fid, w);
total.saturating_sub(left.saturating_add(right))
}
fn is_minibuffer_window(frames: &FrameManager, fid: FrameId, wid: WindowId) -> bool {
frames
.get(fid)
.is_some_and(|frame| frame.minibuffer_window == Some(wid))
}
fn filtered_window_prev_buffers(
prev_raw: Value,
discarded_buffers: &[Value],
) -> Result<Vec<Value>, Flow> {
let prev_entries = list_to_vec(&prev_raw).ok_or_else(|| {
signal(
"wrong-type-argument",
vec![Value::symbol("listp"), prev_raw],
)
})?;
Ok(prev_entries
.into_iter()
.filter(|entry| {
let Some(items) = list_to_vec(entry) else {
return true;
};
!items
.first()
.is_some_and(|first| discarded_buffers.iter().any(|buffer| *buffer == *first))
})
.collect())
}
fn filtered_window_next_buffers(
next_raw: Value,
discarded_buffers: &[Value],
) -> Result<Vec<Value>, Flow> {
let next_entries = list_to_vec(&next_raw).ok_or_else(|| {
signal(
"wrong-type-argument",
vec![Value::symbol("listp"), next_raw],
)
})?;
Ok(next_entries
.into_iter()
.filter(|entry| !discarded_buffers.iter().any(|buffer| *buffer == *entry))
.collect())
}
fn discard_buffers_from_window_history(
frames: &mut FrameManager,
wid: WindowId,
discarded_buffers: &[Value],
) -> Result<(), Flow> {
let prev = filtered_window_prev_buffers(frames.window_prev_buffers(wid), discarded_buffers)?;
frames.set_window_prev_buffers(wid, Value::list(prev));
let next = filtered_window_next_buffers(frames.window_next_buffers(wid), discarded_buffers)?;
frames.set_window_next_buffers(wid, Value::list(next));
Ok(())
}
fn should_record_window_history_buffer(
frames: &FrameManager,
minibuffers: &MinibufferManager,
buffers: &BufferManager,
fid: FrameId,
wid: WindowId,
buffer_id: BufferId,
) -> bool {
if is_minibuffer_window(frames, fid, wid) {
return minibuffers.has_buffer(buffer_id);
}
buffers
.get(buffer_id)
.is_some_and(|buffer| !buffer.name.starts_with(' '))
}
fn window_body_height_lines(frames: &FrameManager, fid: FrameId, wid: WindowId, w: &Window) -> i64 {
let ch = frames.get(fid).map(|f| f.char_height).unwrap_or(16.0);
let lines = window_height_lines(w, ch);
if is_minibuffer_window(frames, fid, wid) {
lines
} else {
lines.saturating_sub(1)
}
}
fn window_edges_cols_lines(w: &Window, char_width: f32, char_height: f32) -> (i64, i64, i64, i64) {
let b = w.bounds();
let left = if char_width > 0.0 {
(b.x / char_width) as i64
} else {
0
};
let top = if char_height > 0.0 {
(b.y / char_height) as i64
} else {
0
};
let right = if char_width > 0.0 {
((b.x + b.width) / char_width) as i64
} else {
0
};
let bottom = if char_height > 0.0 {
((b.y + b.height) / char_height) as i64
} else {
0
};
(left, top, right, bottom)
}
fn window_edges_pixels(w: &Window) -> (i64, i64, i64, i64) {
let b = w.bounds();
(
b.x.max(0.0) as i64,
b.y.max(0.0) as i64,
(b.x + b.width).max(0.0) as i64,
(b.y + b.height).max(0.0) as i64,
)
}
fn window_body_edges_cols_lines(
frames: &FrameManager,
fid: FrameId,
wid: WindowId,
w: &Window,
char_width: f32,
char_height: f32,
) -> (i64, i64, i64, i64) {
let (left, top, right, bottom) = window_body_edges_pixels(frames, fid, wid, w);
let left = if char_width > 0.0 {
(left as f32 / char_width).floor() as i64
} else {
0
};
let top = if char_height > 0.0 {
(top as f32 / char_height).floor() as i64
} else {
0
};
let right = if char_width > 0.0 {
(right as f32 / char_width).ceil() as i64
} else {
0
};
let bottom = if char_height > 0.0 {
(bottom as f32 / char_height).ceil() as i64
} else {
0
};
(left, top, right, bottom)
}
fn window_body_edges_pixels(
frames: &FrameManager,
fid: FrameId,
wid: WindowId,
w: &Window,
) -> (i64, i64, i64, i64) {
let (left, top, right, bottom) = window_edges_pixels(w);
let (body_left_offset, _body_right_offset) =
window_body_horizontal_offsets_pixels(frames, fid, w);
let mode_line_height = if is_minibuffer_window(frames, fid, wid) {
0
} else {
frames
.get(fid)
.map(|frame| frame.char_height.max(0.0) as i64)
.unwrap_or(0)
};
let body_left = left.saturating_add(body_left_offset);
let body_right = body_left.saturating_add(window_body_width_pixels(frames, fid, w));
let body_bottom = bottom.saturating_sub(mode_line_height);
(body_left, top, body_right.min(right), body_bottom)
}
pub(crate) fn builtin_selected_window(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_args("selected-window", &args, 0)?;
let fid = ensure_selected_frame_id_in_state(frames, buffers);
let frame = frames
.get(fid)
.ok_or_else(|| signal("error", vec![Value::string("No selected frame")]))?;
Ok(window_value(frame.selected_window))
}
pub(crate) fn builtin_old_selected_window(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("old-selected-window", &args, 0)?;
let fid = ensure_selected_frame_id(eval);
let selected_wid = eval
.frames
.get(fid)
.map(|frame| frame.selected_window)
.ok_or_else(|| signal("error", vec![Value::string("No selected frame")]))?;
let old_wid = eval.frames.old_selected_window().unwrap_or(selected_wid);
Ok(window_value(old_wid))
}
pub(crate) fn builtin_frame_selected_window(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("frame-selected-window", &args, 1)?;
let fid = resolve_frame_id_in_state(frames, buffers, args.first(), "frame-live-p")?;
let frame = frames
.get(fid)
.ok_or_else(|| signal("error", vec![Value::string("Frame not found")]))?;
Ok(window_value(frame.selected_window))
}
pub(crate) fn builtin_frame_old_selected_window(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("frame-old-selected-window", &args, 1)?;
let _ = resolve_frame_id_in_state(frames, buffers, args.first(), "frame-live-p")?;
Ok(Value::NIL)
}
pub(crate) fn builtin_set_frame_selected_window(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_min_args("set-frame-selected-window", &args, 2)?;
expect_max_args("set-frame-selected-window", &args, 3)?;
let fid = resolve_frame_id_in_state(
&mut eval.frames,
&mut eval.buffers,
args.first(),
"frame-live-p",
)?;
let wid = match window_id_from_designator(&args[1]) {
Some(wid) => {
if eval.frames.find_window_frame_id(wid).is_none() {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("window-live-p"), args[1]],
));
}
wid
}
None => {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("window-live-p"), args[1]],
));
}
};
let window_fid = eval
.frames
.find_window_frame_id(wid)
.ok_or_else(|| signal("error", vec![Value::string("Frame not found")]))?;
if window_fid != fid {
return Err(signal(
"error",
vec![Value::string(
"In `set-frame-selected-window', WINDOW is not on FRAME",
)],
));
}
let selected_fid = ensure_selected_frame_id_in_state(&mut eval.frames, &mut eval.buffers);
if fid == selected_fid {
let mut select_args = vec![window_value(wid)];
if let Some(norecord) = args.get(2) {
select_args.push(*norecord);
}
return builtin_select_window(eval, select_args);
}
let frame = eval
.frames
.get_mut(fid)
.ok_or_else(|| signal("error", vec![Value::string("Frame not found")]))?;
frame.selected_window = wid;
Ok(window_value(wid))
}
pub(crate) fn builtin_frame_first_window(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("frame-first-window", &args, 1)?;
let fid =
resolve_frame_or_window_frame_id_in_state(frames, buffers, args.first(), "frame-live-p")?;
let frame = frames
.get(fid)
.ok_or_else(|| signal("error", vec![Value::string("Frame not found")]))?;
let first = frame
.window_list()
.first()
.copied()
.unwrap_or(frame.selected_window);
Ok(window_value(first))
}
pub(crate) fn builtin_frame_root_window(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("frame-root-window", &args, 1)?;
let fid =
resolve_frame_or_window_frame_id_in_state(frames, buffers, args.first(), "frame-live-p")?;
let frame = frames
.get(fid)
.ok_or_else(|| signal("error", vec![Value::string("Frame not found")]))?;
Ok(window_value(frame.root_window.id()))
}
pub(crate) fn builtin_minibuffer_window(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("minibuffer-window", &args, 1)?;
let fid = resolve_frame_id_in_state(frames, buffers, args.first(), "frame-live-p")?;
let frame = frames
.get(fid)
.ok_or_else(|| signal("error", vec![Value::string("Frame not found")]))?;
match frame.minibuffer_window {
Some(wid) => Ok(window_value(wid)),
None => Ok(Value::NIL),
}
}
pub(crate) fn builtin_window_minibuffer_p(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("window-minibuffer-p", &args, 1)?;
let (fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-valid-p")?;
let is_minibuffer = frames
.get(fid)
.is_some_and(|frame| frame.minibuffer_window == Some(wid));
Ok(Value::bool_val(is_minibuffer))
}
pub(crate) fn builtin_minibuffer_selected_window(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("minibuffer-selected-window", &args, 0)?;
Ok(eval
.minibuffer_selected_window
.map(window_value)
.unwrap_or(Value::NIL))
}
pub(crate) fn builtin_active_minibuffer_window(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("active-minibuffer-window", &args, 0)?;
if let Some(wid) = eval.active_minibuffer_window {
return Ok(window_value(wid));
}
let Some(state) = eval.minibuffers.current() else {
return Ok(Value::NIL);
};
for frame_id in eval.frames.frame_list() {
let Some(frame) = eval.frames.get(frame_id) else {
continue;
};
if let Some(minibuffer_wid) = frame.minibuffer_window
&& let Some(window) = frame.find_window(minibuffer_wid)
&& window.buffer_id() == Some(state.buffer_id)
{
return Ok(window_value(minibuffer_wid));
}
}
Ok(Value::NIL)
}
pub(crate) fn builtin_window_frame(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("window-frame", &args, 1)?;
let (fid, _wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-valid-p")?;
Ok(Value::make_frame(fid.0))
}
pub(crate) fn builtin_window_buffer(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("window-buffer", &args, 1)?;
let resolve_buffer = |frames: &FrameManager, fid: FrameId, wid: WindowId| -> EvalResult {
let w = get_leaf(frames, fid, wid)?;
match w.buffer_id() {
Some(bid) => Ok(Value::make_buffer(bid)),
None => Ok(Value::NIL),
}
};
if args.first().is_none_or(|v| v.is_nil()) {
let (fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "windowp")?;
return resolve_buffer(frames, fid, wid);
}
let val = args.first().unwrap();
let Some(wid) = window_id_from_designator(val) else {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("windowp"), *val],
));
};
if let Some(fid) = frames.find_window_frame_id(wid) {
return resolve_buffer(frames, fid, wid);
}
if frames.is_window_object_id(wid) {
return Ok(Value::NIL);
}
Err(signal(
"wrong-type-argument",
vec![Value::symbol("windowp"), *val],
))
}
pub(crate) fn builtin_window_display_table(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("window-display-table", &args, 1)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (_fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-live-p")?;
Ok(frames.window_display_table(wid))
}
pub(crate) fn builtin_set_window_display_table(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_args("set-window-display-table", &args, 2)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (_fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-live-p")?;
let table = args[1];
frames.set_window_display_table(wid, table);
Ok(table)
}
pub(crate) fn builtin_window_cursor_type(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("window-cursor-type", &args, 1)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (_fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-live-p")?;
Ok(frames.window_cursor_type(wid))
}
pub(crate) fn builtin_set_window_cursor_type(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_args("set-window-cursor-type", &args, 2)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (_fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-live-p")?;
let cursor_type = args[1];
frames.set_window_cursor_type(wid, cursor_type);
Ok(cursor_type)
}
pub(crate) fn builtin_window_parameter(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_args("window-parameter", &args, 2)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let wid =
resolve_window_object_id_with_pred_in_state(frames, buffers, args.first(), "windowp")?;
Ok(frames.window_parameter(wid, &args[1]).unwrap_or(Value::NIL))
}
pub(crate) fn builtin_set_window_parameter(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_args("set-window-parameter", &args, 3)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let wid =
resolve_window_object_id_with_pred_in_state(frames, buffers, args.first(), "windowp")?;
let value = args[2];
frames.set_window_parameter(wid, args[1], value);
Ok(value)
}
pub(crate) fn builtin_window_parameters(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("window-parameters", &args, 1)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (_fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-valid-p")?;
Ok(frames.window_parameters_alist(wid))
}
pub(crate) fn builtin_window_parent(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("window-parent", &args, 1)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-valid-p")?;
let Some(frame) = frames.get(fid) else {
return Err(signal("error", vec![Value::string("Frame not found")]));
};
Ok(window_parent_id(frame, wid).map_or(Value::NIL, window_value))
}
pub(crate) fn builtin_window_top_child(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("window-top-child", &args, 1)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-valid-p")?;
let Some(frame) = frames.get(fid) else {
return Err(signal("error", vec![Value::string("Frame not found")]));
};
Ok(
window_first_child_id(frame, wid, SplitDirection::Vertical)
.map_or(Value::NIL, window_value),
)
}
pub(crate) fn builtin_window_left_child(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("window-left-child", &args, 1)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-valid-p")?;
let Some(frame) = frames.get(fid) else {
return Err(signal("error", vec![Value::string("Frame not found")]));
};
Ok(
window_first_child_id(frame, wid, SplitDirection::Horizontal)
.map_or(Value::NIL, window_value),
)
}
pub(crate) fn builtin_window_next_sibling(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("window-next-sibling", &args, 1)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-valid-p")?;
let Some(frame) = frames.get(fid) else {
return Err(signal("error", vec![Value::string("Frame not found")]));
};
Ok(window_next_sibling_id(frame, wid).map_or(Value::NIL, window_value))
}
pub(crate) fn builtin_window_prev_sibling(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("window-prev-sibling", &args, 1)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-valid-p")?;
let Some(frame) = frames.get(fid) else {
return Err(signal("error", vec![Value::string("Frame not found")]));
};
Ok(window_prev_sibling_id(frame, wid).map_or(Value::NIL, window_value))
}
pub(crate) fn builtin_window_normal_size(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("window-normal-size", &args, 2)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-valid-p")?;
let horizontal = args.get(1).is_some_and(|v| v.is_truthy());
let Some(frame) = frames.get(fid) else {
return Err(signal("error", vec![Value::string("Frame not found")]));
};
let window = frame
.find_window(wid)
.ok_or_else(|| signal("error", vec![Value::string("Window not found")]))?;
let Some(parent_id) = window_parent_id(frame, wid) else {
return Ok(Value::make_float(1.0));
};
let parent = frame
.find_window(parent_id)
.ok_or_else(|| signal("error", vec![Value::string("Window not found")]))?;
let ratio = match parent {
Window::Internal {
direction,
bounds: parent_bounds,
..
} => match (horizontal, direction) {
(true, SplitDirection::Horizontal) if parent_bounds.width > 0.0 => {
window.bounds().width / parent_bounds.width
}
(false, SplitDirection::Vertical) if parent_bounds.height > 0.0 => {
window.bounds().height / parent_bounds.height
}
_ => 1.0,
},
Window::Leaf { .. } => 1.0,
};
Ok(Value::make_float(ratio as f64))
}
pub(crate) fn builtin_window_start(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("window-start", &args, 1)?;
let (fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-live-p")?;
let w = get_leaf(frames, fid, wid)?;
match w {
Window::Leaf { window_start, .. } => Ok(Value::fixnum(*window_start as i64)),
_ => Ok(Value::fixnum(0)),
}
}
pub(crate) fn builtin_window_group_start(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("window-group-start", &args, 1)?;
let (fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-live-p")?;
if frames
.get(fid)
.is_some_and(|frame| frame.minibuffer_window == Some(wid))
{
return Ok(Value::fixnum(1));
}
let w = get_leaf(frames, fid, wid)?;
match w {
Window::Leaf { window_start, .. } => Ok(Value::fixnum(*window_start as i64)),
_ => Ok(Value::fixnum(1)),
}
}
fn estimated_window_end_from_body_lines(
frames: &FrameManager,
buffers: &BufferManager,
fid: FrameId,
wid: WindowId,
window_start: usize,
bounds: &Rect,
buffer_id: crate::buffer::BufferId,
) -> usize {
let Some(frame) = frames.get(fid) else {
return window_start;
};
let body_lines = if is_minibuffer_window(frames, fid, wid) {
(bounds.height / frame.char_height) as usize
} else {
((bounds.height / frame.char_height) as usize).saturating_sub(1)
};
let Some(buf) = buffers.get(buffer_id) else {
return window_start;
};
let buffer_end = buf.text.char_count().saturating_add(1);
let text = buf.text.to_string();
let start_char = window_start.saturating_sub(1);
let mut char_pos = start_char;
let mut lines_seen = 0usize;
for (i, ch) in text.char_indices().skip(start_char) {
if lines_seen >= body_lines {
let _ = i;
return (char_pos + 1).min(buffer_end);
}
char_pos = text[..=i].chars().count();
if ch == '\n' {
lines_seen += 1;
}
}
buffer_end
}
pub(crate) fn builtin_window_end(eval: &mut super::eval::Context, args: Vec<Value>) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("window-end", &args, 2)?;
let (fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-live-p")?;
let w = get_leaf(frames, fid, wid)?;
match w {
Window::Leaf {
window_start,
window_end_pos,
window_end_valid,
bounds,
buffer_id,
..
} => {
let update_requested = args.get(1).is_some_and(|arg| !arg.is_nil());
let stored_end = buffers
.get(*buffer_id)
.map(|b| b.point_max_char().saturating_add(1))
.unwrap_or(*window_start)
.saturating_sub(*window_end_pos)
.max(1);
if let Some(snapshot_end) = frames
.get(fid)
.and_then(|frame| frame.window_display_snapshot(wid))
.and_then(|snapshot| snapshot.visible_buffer_span().map(|(_, end)| end))
{
return Ok(Value::fixnum(snapshot_end as i64));
}
if !update_requested && (*window_end_valid || stored_end > *window_start) {
return Ok(Value::fixnum(stored_end as i64));
}
Ok(Value::fixnum(estimated_window_end_from_body_lines(
frames,
buffers,
fid,
wid,
*window_start,
bounds,
*buffer_id,
) as i64))
}
_ => Ok(Value::fixnum(0)),
}
}
pub(crate) fn builtin_window_point(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("window-point", &args, 1)?;
let (fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-live-p")?;
let w = get_leaf(frames, fid, wid)?;
match w {
Window::Leaf {
buffer_id, point, ..
} => {
let selected_live_window = frames.get(fid).is_some_and(|frame| {
frame.selected_window == wid && frame.selected_window != WindowId(0)
});
if selected_live_window {
if let Some(buffer) = buffers.get(*buffer_id) {
return Ok(Value::fixnum(buffer.point_char().saturating_add(1) as i64));
}
}
Ok(Value::fixnum(*point as i64))
}
_ => Ok(Value::fixnum(0)),
}
}
pub(crate) fn builtin_set_window_start(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_min_args("set-window-start", &args, 2)?;
expect_max_args("set-window-start", &args, 3)?;
let result = {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
let (fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-live-p")?;
let pos = parse_integer_or_marker_arg(&args[1])?;
let is_minibuffer = frames
.get(fid)
.is_some_and(|frame| frame.minibuffer_window == Some(wid));
match pos {
IntegerOrMarkerArg::Int(pos) => {
if !is_minibuffer {
if let Some(clamped) =
clamped_window_position_in_state(frames, buffers, fid, wid, pos)
{
if let Some(Window::Leaf { window_start, .. }) = frames
.get_mut(fid)
.and_then(|frame| frame.find_window_mut(wid))
{
*window_start = clamped;
}
}
}
Value::fixnum(pos)
}
IntegerOrMarkerArg::Marker { raw, position } => {
if !is_minibuffer {
if let Some(pos) = position {
if let Some(clamped) =
clamped_window_position_in_state(frames, buffers, fid, wid, pos)
{
if let Some(Window::Leaf { window_start, .. }) = frames
.get_mut(fid)
.and_then(|frame| frame.find_window_mut(wid))
{
*window_start = clamped;
}
}
}
}
raw
}
}
};
let _ = builtin_run_window_scroll_functions(eval, vec![]);
Ok(result)
}
pub(crate) fn builtin_set_window_group_start(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_min_args("set-window-group-start", &args, 2)?;
expect_max_args("set-window-group-start", &args, 3)?;
let (fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-live-p")?;
let pos = parse_integer_or_marker_arg(&args[1])?;
let is_minibuffer = frames
.get(fid)
.is_some_and(|frame| frame.minibuffer_window == Some(wid));
match pos {
IntegerOrMarkerArg::Int(pos) => {
if !is_minibuffer {
if let Some(clamped) =
clamped_window_position_in_state(frames, buffers, fid, wid, pos)
{
if let Some(Window::Leaf { window_start, .. }) = frames
.get_mut(fid)
.and_then(|frame| frame.find_window_mut(wid))
{
*window_start = clamped;
}
}
}
Ok(Value::fixnum(pos))
}
IntegerOrMarkerArg::Marker { raw, position } => {
if !is_minibuffer {
if let Some(pos) = position {
if let Some(clamped) =
clamped_window_position_in_state(frames, buffers, fid, wid, pos)
{
if let Some(Window::Leaf {
window_start,
point,
..
}) = frames
.get_mut(fid)
.and_then(|frame| frame.find_window_mut(wid))
{
*window_start = clamped;
*point = clamped;
}
}
}
}
Ok(raw)
}
}
}
pub(crate) fn builtin_set_window_point(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_args("set-window-point", &args, 2)?;
let (fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-live-p")?;
let pos = parse_integer_or_marker_arg(&args[1])?;
let is_minibuffer = frames
.get(fid)
.is_some_and(|frame| frame.minibuffer_window == Some(wid));
match pos {
IntegerOrMarkerArg::Int(pos) => {
if !is_minibuffer {
if let Some(clamped) =
clamped_window_position_in_state(frames, buffers, fid, wid, pos)
{
let selected_live_window = frames
.get(fid)
.is_some_and(|frame| frame.selected_window == wid);
let mut buffer_to_move = None;
if let Some(Window::Leaf {
buffer_id, point, ..
}) = frames
.get_mut(fid)
.and_then(|frame| frame.find_window_mut(wid))
{
*point = clamped;
if selected_live_window {
if let Some(buffer) = buffers.get(*buffer_id) {
buffer_to_move =
Some((*buffer_id, buffer.lisp_pos_to_byte(clamped as i64)));
}
}
}
if let Some((buffer_id, byte_pos)) = buffer_to_move {
let _ = buffers.goto_buffer_byte(buffer_id, byte_pos);
}
}
}
Ok(Value::fixnum(pos))
}
IntegerOrMarkerArg::Marker { raw, position } => {
if is_minibuffer {
return Ok(raw);
}
let pos = position.ok_or_else(|| {
signal(
"error",
vec![Value::string("Marker does not point anywhere")],
)
})?;
if let Some(clamped) = clamped_window_position_in_state(frames, buffers, fid, wid, pos)
{
let selected_live_window = frames
.get(fid)
.is_some_and(|frame| frame.selected_window == wid);
let mut buffer_to_move = None;
if let Some(Window::Leaf {
buffer_id, point, ..
}) = frames
.get_mut(fid)
.and_then(|frame| frame.find_window_mut(wid))
{
*point = clamped;
if selected_live_window {
if let Some(buffer) = buffers.get(*buffer_id) {
buffer_to_move =
Some((*buffer_id, buffer.lisp_pos_to_byte(clamped as i64)));
}
}
}
if let Some((buffer_id, byte_pos)) = buffer_to_move {
let _ = buffers.goto_buffer_byte(buffer_id, byte_pos);
}
Ok(Value::fixnum(clamped as i64))
} else {
Ok(Value::fixnum(1))
}
}
}
}
pub(crate) fn builtin_window_use_time(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("window-use-time", &args, 1)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (_fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-live-p")?;
Ok(Value::fixnum(frames.window_use_time(wid)))
}
pub(crate) fn builtin_window_bump_use_time(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("window-bump-use-time", &args, 1)?;
let selected_fid = ensure_selected_frame_id_in_state(frames, buffers);
let selected_wid = frames
.get(selected_fid)
.map(|frame| frame.selected_window)
.ok_or_else(|| signal("error", vec![Value::string("No selected frame")]))?;
let target_wid = if args.first().is_none_or(|v| v.is_nil()) {
selected_wid
} else {
let val = args.first().unwrap();
match val.kind() {
ValueKind::Veclike(VecLikeType::Window) => {
let raw_id = val.as_window_id().unwrap();
let wid = WindowId(raw_id);
if frames.find_window_frame_id(wid).is_none() {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("window-live-p"), Value::make_window(raw_id)],
));
}
wid
}
_ => {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("window-live-p"), *val],
));
}
}
};
Ok(
match frames.bump_window_use_time(selected_wid, target_wid) {
Some(use_time) => Value::fixnum(use_time),
None => Value::NIL,
},
)
}
pub(crate) fn builtin_window_old_point(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("window-old-point", &args, 1)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-live-p")?;
let w = get_leaf(frames, fid, wid)?;
match w {
Window::Leaf { old_point, .. } => Ok(Value::fixnum((*old_point).max(1) as i64)),
_ => Ok(Value::fixnum(1)),
}
}
pub(crate) fn builtin_window_old_buffer(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("window-old-buffer", &args, 1)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (_fid, _wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-live-p")?;
Ok(Value::NIL)
}
pub(crate) fn builtin_window_prev_buffers(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("window-prev-buffers", &args, 1)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (_fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-live-p")?;
Ok(frames.window_prev_buffers(wid))
}
pub(crate) fn builtin_window_next_buffers(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("window-next-buffers", &args, 1)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (_fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-live-p")?;
Ok(frames.window_next_buffers(wid))
}
pub(crate) fn builtin_set_window_prev_buffers(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_args("set-window-prev-buffers", &args, 2)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (_fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-live-p")?;
let value = args[1];
frames.set_window_prev_buffers(wid, value);
Ok(value)
}
pub(crate) fn builtin_set_window_next_buffers(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_args("set-window-next-buffers", &args, 2)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (_fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-live-p")?;
let value = args[1];
frames.set_window_next_buffers(wid, value);
Ok(value)
}
pub(crate) fn builtin_window_discard_buffer_from_window(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_min_args("window-discard-buffer-from-window", &args, 2)?;
expect_max_args("window-discard-buffer-from-window", &args, 3)?;
let buffer_id = match args.first().and_then(|v| v.as_buffer_id()) {
Some(bid) if buffers.get(bid).is_some() => bid,
_ => {
return Err(signal("error", vec![Value::string("Not a live buffer")]));
}
};
let (_fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.get(1), "window-live-p")?;
discard_buffers_from_window_history(frames, wid, &[Value::make_buffer(buffer_id)])?;
Ok(Value::NIL)
}
pub(crate) fn builtin_window_left_column(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("window-left-column", &args, 1)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-valid-p")?;
let w = get_window(frames, fid, wid)?;
let cw = frames.get(fid).map(|f| f.char_width).unwrap_or(8.0);
let left = if cw > 0.0 {
(w.bounds().x / cw) as i64
} else {
0
};
Ok(Value::fixnum(left))
}
pub(crate) fn builtin_window_top_line(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("window-top-line", &args, 1)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-valid-p")?;
let w = get_window(frames, fid, wid)?;
let ch = frames.get(fid).map(|f| f.char_height).unwrap_or(16.0);
let top = if ch > 0.0 {
(w.bounds().y / ch) as i64
} else {
0
};
Ok(Value::fixnum(top))
}
pub(crate) fn builtin_window_pixel_left(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
eval.sync_pending_resize_events();
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("window-pixel-left", &args, 1)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-valid-p")?;
let w = get_window(frames, fid, wid)?;
let cw = frames.get(fid).map(|f| f.char_width).unwrap_or(8.0);
let left = if cw > 0.0 {
(w.bounds().x / cw) as i64
} else {
0
};
Ok(Value::fixnum(left))
}
pub(crate) fn builtin_window_pixel_top(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
eval.sync_pending_resize_events();
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("window-pixel-top", &args, 1)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-valid-p")?;
let w = get_window(frames, fid, wid)?;
let ch = frames.get(fid).map(|f| f.char_height).unwrap_or(16.0);
let top = if ch > 0.0 {
(w.bounds().y / ch) as i64
} else {
0
};
Ok(Value::fixnum(top))
}
pub(crate) fn builtin_window_hscroll(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("window-hscroll", &args, 1)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-live-p")?;
let w = get_leaf(frames, fid, wid)?;
match w {
Window::Leaf { hscroll, .. } => Ok(Value::fixnum(*hscroll as i64)),
_ => Ok(Value::fixnum(0)),
}
}
pub(crate) fn builtin_set_window_hscroll(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_args("set-window-hscroll", &args, 2)?;
let (fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-live-p")?;
let cols = expect_fixnum(&args[1])?.max(0) as usize;
if let Some(Window::Leaf { hscroll, .. }) = frames
.get_mut(fid)
.and_then(|frame| frame.find_window_mut(wid))
{
*hscroll = cols;
}
Ok(Value::fixnum(cols as i64))
}
fn scroll_prefix_value(value: &Value) -> i64 {
match value.kind() {
ValueKind::Fixnum(n) => n,
ValueKind::Float => value.xfloat() as i64,
ValueKind::Symbol(id) if resolve_sym(id) == "-" => -1,
ValueKind::Cons => {
let car = value.cons_car();
match car.kind() {
ValueKind::Fixnum(n) => n,
ValueKind::Float => car.xfloat() as i64,
_ => 1,
}
}
_ => 1,
}
}
fn default_scroll_columns(eval: &super::eval::Context, fid: FrameId, wid: WindowId) -> i64 {
default_scroll_columns_in_state(&eval.frames, fid, wid)
}
fn default_scroll_columns_in_state(frames: &FrameManager, fid: FrameId, wid: WindowId) -> i64 {
let char_width = frames.get(fid).map(|f| f.char_width).unwrap_or(8.0);
let window_cols = get_leaf(frames, fid, wid)
.ok()
.map(|leaf| {
if char_width > 0.0 {
(leaf.bounds().width / char_width).floor() as i64
} else {
80
}
})
.unwrap_or(80);
(window_cols - 2).max(1)
}
pub(crate) fn builtin_scroll_left(eval: &mut super::eval::Context, args: Vec<Value>) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("scroll-left", &args, 2)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (fid, wid) = resolve_window_id_in_state(frames, buffers, None)?;
let base = frames
.get(fid)
.and_then(|frame| frame.find_window(wid))
.and_then(|window| match window {
Window::Leaf { hscroll, .. } => Some(*hscroll as i64),
_ => None,
})
.unwrap_or(0);
let delta = if args.first().is_none_or(|v| v.is_nil()) {
default_scroll_columns_in_state(frames, fid, wid)
} else {
scroll_prefix_value(args.first().unwrap())
};
let mut next = base as i128 + delta as i128;
if next < 0 {
next = 0;
}
let next = next.min(i64::MAX as i128) as i64;
if let Some(Window::Leaf { hscroll, .. }) = frames
.get_mut(fid)
.and_then(|frame| frame.find_window_mut(wid))
{
*hscroll = next as usize;
}
Ok(Value::fixnum(next))
}
pub(crate) fn builtin_scroll_right(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("scroll-right", &args, 2)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (fid, wid) = resolve_window_id_in_state(frames, buffers, None)?;
let base = frames
.get(fid)
.and_then(|frame| frame.find_window(wid))
.and_then(|window| match window {
Window::Leaf { hscroll, .. } => Some(*hscroll as i64),
_ => None,
})
.unwrap_or(0);
let delta = if args.first().is_none_or(|v| v.is_nil()) {
default_scroll_columns_in_state(frames, fid, wid)
} else {
scroll_prefix_value(args.first().unwrap())
};
let mut next = base as i128 - delta as i128;
if next < 0 {
next = 0;
}
let next = next.min(i64::MAX as i128) as i64;
if let Some(Window::Leaf { hscroll, .. }) = frames
.get_mut(fid)
.and_then(|frame| frame.find_window_mut(wid))
{
*hscroll = next as usize;
}
Ok(Value::fixnum(next))
}
pub(crate) fn builtin_window_vscroll(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("window-vscroll", &args, 2)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (_fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-live-p")?;
let pixelwise = args.get(1).is_some_and(|v| v.is_truthy());
Ok(frames
.window_vscroll(wid, pixelwise)
.unwrap_or(Value::fixnum(0)))
}
pub(crate) fn builtin_set_window_vscroll(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_min_args("set-window-vscroll", &args, 2)?;
expect_max_args("set-window-vscroll", &args, 4)?;
let (_fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-live-p")?;
let next_vscroll = expect_number(&args[1])?;
let pixelwise = args.get(2).is_some_and(|v| v.is_truthy());
let preserve = args.get(3).is_some_and(|v| v.is_truthy());
Ok(frames
.set_window_vscroll(wid, next_vscroll, pixelwise, preserve)
.unwrap_or(Value::fixnum(0)))
}
pub(crate) fn builtin_set_window_margins(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_min_args("set-window-margins", &args, 2)?;
expect_max_args("set-window-margins", &args, 3)?;
let (fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-live-p")?;
let left = expect_margin_width(&args[1])?;
let right = if let Some(arg) = args.get(2) {
expect_margin_width(arg)?
} else {
0
};
if let Some(Window::Leaf { margins, .. }) = frames
.get_mut(fid)
.and_then(|frame| frame.find_window_mut(wid))
{
let next = (left, right);
if *margins != next {
*margins = next;
return Ok(Value::T);
}
}
Ok(Value::NIL)
}
pub(crate) fn builtin_window_margins(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("window-margins", &args, 1)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-live-p")?;
let w = get_leaf(frames, fid, wid)?;
let (left, right) = match w {
Window::Leaf { margins, .. } => *margins,
_ => (0, 0),
};
let left_v = if left == 0 {
Value::NIL
} else {
Value::fixnum(left as i64)
};
let right_v = if right == 0 {
Value::NIL
} else {
Value::fixnum(right as i64)
};
Ok(Value::cons(left_v, right_v))
}
pub(crate) fn builtin_window_fringes(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("window-fringes", &args, 1)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (_fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-live-p")?;
let (left, right, outside, persistent) =
frames.window_fringes(wid).unwrap_or((0, 0, false, false));
Ok(Value::list(vec![
Value::fixnum(left),
Value::fixnum(right),
if outside { Value::T } else { Value::NIL },
if persistent { Value::T } else { Value::NIL },
]))
}
pub(crate) fn builtin_set_window_fringes(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_min_args("set-window-fringes", &args, 2)?;
expect_max_args("set-window-fringes", &args, 5)?;
let (fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-live-p")?;
if frames
.get(fid)
.is_none_or(|frame| frame.effective_window_system().is_none())
{
return Ok(Value::NIL);
}
let left = if args[1].is_nil() {
None
} else {
Some(i32::try_from(expect_int(&args[1])?).map_err(|_| {
signal(
"args-out-of-range",
vec![
args[1],
Value::fixnum(0),
Value::fixnum(i64::from(i32::MAX)),
],
)
})?)
};
let right = if let Some(arg) = args.get(2) {
if arg.is_nil() {
None
} else {
Some(i32::try_from(expect_int(arg)?).map_err(|_| {
signal(
"args-out-of-range",
vec![*arg, Value::fixnum(0), Value::fixnum(i64::from(i32::MAX))],
)
})?)
}
} else {
left
};
Ok(
if frames.set_window_fringes(
wid,
left,
right,
args.get(3).is_some_and(|value| value.is_truthy()),
args.get(4).is_some_and(|value| value.is_truthy()),
) {
Value::T
} else {
Value::NIL
},
)
}
pub(crate) fn builtin_window_scroll_bars(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("window-scroll-bars", &args, 1)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (_fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-live-p")?;
let (width, columns, vertical_type, height, lines, horizontal_type, persistent) = frames
.window_scroll_bars(wid)
.unwrap_or((Value::NIL, 0, Value::T, Value::NIL, 0, Value::T, false));
Ok(Value::list(vec![
width,
Value::fixnum(columns),
vertical_type,
height,
Value::fixnum(lines),
horizontal_type,
if persistent { Value::T } else { Value::NIL },
]))
}
pub(crate) fn builtin_set_window_scroll_bars(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_min_args("set-window-scroll-bars", &args, 1)?;
expect_max_args("set-window-scroll-bars", &args, 6)?;
let (fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-live-p")?;
if frames
.get(fid)
.is_none_or(|frame| frame.effective_window_system().is_none())
{
return Ok(Value::NIL);
}
let width = if let Some(arg) = args.get(1) {
if arg.is_nil() {
None
} else {
Some(i32::try_from(expect_int(arg)?).map_err(|_| {
signal(
"args-out-of-range",
vec![*arg, Value::fixnum(0), Value::fixnum(i64::from(i32::MAX))],
)
})?)
}
} else {
None
};
let vertical_type = args.get(2).copied().unwrap_or(Value::T);
if !(vertical_type.is_nil()
|| vertical_type == Value::T
|| matches!(vertical_type.as_symbol_name(), Some("left" | "right")))
{
return Err(signal(
"error",
vec![Value::string("Invalid type of vertical scroll bar")],
));
}
let height = if let Some(arg) = args.get(3) {
if arg.is_nil() {
None
} else {
Some(i32::try_from(expect_int(arg)?).map_err(|_| {
signal(
"args-out-of-range",
vec![*arg, Value::fixnum(0), Value::fixnum(i64::from(i32::MAX))],
)
})?)
}
} else {
None
};
let horizontal_type = args.get(4).copied().unwrap_or(Value::T);
if !(horizontal_type.is_nil()
|| horizontal_type == Value::T
|| matches!(horizontal_type.as_symbol_name(), Some("bottom")))
{
return Err(signal(
"error",
vec![Value::string("Invalid type of horizontal scroll bar")],
));
}
Ok(
if frames.set_window_scroll_bars(
wid,
width,
vertical_type,
height,
horizontal_type,
args.get(5).is_some_and(|value| value.is_truthy()),
) {
Value::T
} else {
Value::NIL
},
)
}
pub(crate) fn builtin_window_scroll_bar_width(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_max_args("window-scroll-bar-width", &args, 1)?;
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (_fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-live-p")?;
Ok(Value::fixnum(frames.window_scroll_bar_area_width(wid)))
}
pub(crate) fn builtin_window_scroll_bar_height(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_max_args("window-scroll-bar-height", &args, 1)?;
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (_fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-live-p")?;
Ok(Value::fixnum(frames.window_scroll_bar_area_height(wid)))
}
pub(crate) fn builtin_window_mode_line_height(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("window-mode-line-height", &args, 1)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-live-p")?;
let height = window_chrome_height_in_state(
frames,
fid,
wid,
WindowChromeMetric::ModeLine,
if is_minibuffer_window(frames, fid, wid) {
0
} else {
1
},
);
Ok(Value::fixnum(height))
}
pub(crate) fn builtin_window_header_line_height(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("window-header-line-height", &args, 1)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-live-p")?;
Ok(Value::fixnum(window_chrome_height_in_state(
frames,
fid,
wid,
WindowChromeMetric::HeaderLine,
0,
)))
}
pub(crate) fn builtin_window_tab_line_height(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("window-tab-line-height", &args, 1)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-live-p")?;
Ok(Value::fixnum(window_chrome_height_in_state(
frames,
fid,
wid,
WindowChromeMetric::TabLine,
0,
)))
}
#[derive(Clone, Copy)]
enum WindowChromeMetric {
ModeLine,
HeaderLine,
TabLine,
}
fn window_chrome_height_in_state(
frames: &FrameManager,
fid: FrameId,
wid: WindowId,
metric: WindowChromeMetric,
fallback: i64,
) -> i64 {
frames
.get(fid)
.and_then(|frame| frame.window_display_snapshot(wid))
.map(|snapshot| match metric {
WindowChromeMetric::ModeLine => snapshot.mode_line_height,
WindowChromeMetric::HeaderLine => snapshot.header_line_height,
WindowChromeMetric::TabLine => snapshot.tab_line_height,
})
.unwrap_or(fallback)
.max(0)
}
pub(crate) fn builtin_window_pixel_height(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
eval.sync_pending_resize_events();
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("window-pixel-height", &args, 1)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-valid-p")?;
let w = get_window(frames, fid, wid)?;
Ok(Value::fixnum(window_height_pixels(w)))
}
pub(crate) fn builtin_window_pixel_width(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
eval.sync_pending_resize_events();
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("window-pixel-width", &args, 1)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-valid-p")?;
let w = get_window(frames, fid, wid)?;
Ok(Value::fixnum(window_width_pixels(w)))
}
pub(crate) fn builtin_window_body_height(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
eval.sync_pending_resize_events();
window_body_height_impl(&mut eval.frames, &mut eval.buffers, args)
}
fn window_body_height_impl(
frames: &mut FrameManager,
buffers: &mut BufferManager,
args: Vec<Value>,
) -> EvalResult {
expect_max_args("window-body-height", &args, 2)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-live-p")?;
let w = get_leaf(frames, fid, wid)?;
let pixelwise = args.get(1).is_some_and(|v| v.is_truthy());
if pixelwise {
let total = window_height_pixels(w);
let body = if is_minibuffer_window(frames, fid, wid) {
total
} else {
let mode_line_height = frames
.get(fid)
.map(|frame| frame.char_height.max(0.0) as i64)
.unwrap_or(0);
total.saturating_sub(mode_line_height)
};
Ok(Value::fixnum(body))
} else {
let body_lines = window_body_height_lines(frames, fid, wid, w);
Ok(Value::fixnum(body_lines))
}
}
pub(crate) fn builtin_window_body_width(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
eval.sync_pending_resize_events();
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("window-body-width", &args, 2)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-live-p")?;
let w = get_leaf(frames, fid, wid)?;
let pixelwise = args.get(1).is_some_and(|v| v.is_truthy());
if pixelwise {
Ok(Value::fixnum(window_body_width_pixels(frames, fid, w)))
} else {
let cw = frames
.get(fid)
.map(|f| f.char_width.max(1.0))
.unwrap_or(8.0);
Ok(Value::fixnum(
(window_body_width_pixels(frames, fid, w) as f32 / cw).floor() as i64,
))
}
}
pub(crate) fn builtin_window_text_height(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
eval.sync_pending_resize_events();
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("window-text-height", &args, 2)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-live-p")?;
let w = get_leaf(frames, fid, wid)?;
let pixelwise = args.get(1).is_some_and(|v| v.is_truthy());
if pixelwise {
let total = window_height_pixels(w);
let body = if is_minibuffer_window(frames, fid, wid) {
total
} else {
let mode_line_height = frames
.get(fid)
.map(|frame| frame.char_height.max(0.0) as i64)
.unwrap_or(0);
total.saturating_sub(mode_line_height)
};
Ok(Value::fixnum(body))
} else {
Ok(Value::fixnum(window_body_height_lines(frames, fid, wid, w)))
}
}
pub(crate) fn builtin_window_text_width(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
eval.sync_pending_resize_events();
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("window-text-width", &args, 2)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-live-p")?;
let w = get_leaf(frames, fid, wid)?;
let pixelwise = args.get(1).is_some_and(|v| v.is_truthy());
if pixelwise {
Ok(Value::fixnum(window_body_width_pixels(frames, fid, w)))
} else {
let cw = frames
.get(fid)
.map(|f| f.char_width.max(1.0))
.unwrap_or(8.0);
Ok(Value::fixnum(
(window_body_width_pixels(frames, fid, w) as f32 / cw).floor() as i64,
))
}
}
pub(crate) fn builtin_window_edges(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("window-edges", &args, 4)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let body = args.get(1).is_some_and(|v| v.is_truthy());
let _absolute = args.get(2).is_some_and(|v| v.is_truthy());
let pixelwise = args.get(3).is_some_and(|v| v.is_truthy());
let live_only = body;
let (fid, wid) =
resolve_window_id_or_window_error_in_state(frames, buffers, args.first(), live_only)?;
let w = get_window(frames, fid, wid)?;
let frame = frames
.get(fid)
.ok_or_else(|| signal("error", vec![Value::string("Frame not found")]))?;
if pixelwise {
let (left, top, right, bottom) = if body {
window_body_edges_pixels(frames, fid, wid, w)
} else {
window_edges_pixels(w)
};
return Ok(Value::list(vec![
Value::fixnum(left),
Value::fixnum(top),
Value::fixnum(right),
Value::fixnum(bottom),
]));
}
let (left, top, right, bottom) = if body {
window_body_edges_cols_lines(frames, fid, wid, w, frame.char_width, frame.char_height)
} else {
window_edges_cols_lines(w, frame.char_width, frame.char_height)
};
Ok(Value::list(vec![
Value::fixnum(left),
Value::fixnum(top),
Value::fixnum(right),
Value::fixnum(bottom),
]))
}
pub(crate) fn builtin_window_pixel_edges(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_max_args("window-pixel-edges", &args, 1)?;
let (fid, wid) = resolve_window_id(eval, args.first())?;
let w = eval.frames.get(fid).and_then(|f| f.find_window(wid));
let Some(w) = w else {
return Ok(Value::NIL);
};
let (left, top, right, bottom) = window_edges_pixels(w);
Ok(Value::list(vec![
Value::fixnum(left),
Value::fixnum(top),
Value::fixnum(right),
Value::fixnum(bottom),
]))
}
pub(crate) fn builtin_window_absolute_pixel_edges(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
builtin_window_pixel_edges(eval, args)
}
pub(crate) fn builtin_window_total_height(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
window_total_height_impl(&mut eval.frames, &mut eval.buffers, args)
}
pub(crate) fn window_total_height_impl(
frames: &mut FrameManager,
buffers: &mut BufferManager,
args: Vec<Value>,
) -> EvalResult {
expect_max_args("window-total-height", &args, 2)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-valid-p")?;
let w = get_window(frames, fid, wid)?;
let ch = frames.get(fid).map(|f| f.char_height).unwrap_or(16.0);
Ok(Value::fixnum(window_height_lines(w, ch)))
}
pub(crate) fn builtin_window_total_width(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
window_total_width_impl(&mut eval.frames, &mut eval.buffers, args)
}
pub(crate) fn window_total_width_impl(
frames: &mut FrameManager,
buffers: &mut BufferManager,
args: Vec<Value>,
) -> EvalResult {
expect_max_args("window-total-width", &args, 2)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-valid-p")?;
let w = get_window(frames, fid, wid)?;
let cw = frames.get(fid).map(|f| f.char_width).unwrap_or(8.0);
Ok(Value::fixnum(window_width_cols(w, cw)))
}
pub(crate) fn builtin_window_list(eval: &mut super::eval::Context, args: Vec<Value>) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("window-list", &args, 3)?;
let selected_fid = ensure_selected_frame_id_in_state(frames, buffers);
let all_frames_fid = if args.get(2).is_none_or(|v| v.is_nil()) {
None
} else {
let arg = args.get(2).unwrap();
let Some(wid) = window_id_from_designator(arg) else {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("windowp"), *arg],
));
};
if let Some(fid) = frames.find_window_frame_id(wid) {
Some(fid)
} else if frames.is_window_object_id(wid) {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("window-live-p"), *arg],
));
} else {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("windowp"), *arg],
));
}
};
let mut fid = if args.first().is_none_or(|v| v.is_nil()) {
selected_fid
} else {
let val = args.first().unwrap();
match val.kind() {
ValueKind::Fixnum(n) => {
let fid = FrameId(n as u64);
if frames.get(fid).is_some() {
fid
} else {
return Err(signal(
"error",
vec![Value::string("Window is on a different frame")],
));
}
}
ValueKind::Veclike(VecLikeType::Frame) => {
let raw_id = val.as_frame_id().unwrap();
let fid = FrameId(raw_id);
if frames.get(fid).is_some() {
fid
} else {
return Err(signal(
"error",
vec![Value::string("Window is on a different frame")],
));
}
}
_ => {
return Err(signal(
"error",
vec![Value::string("Window is on a different frame")],
));
}
}
};
if let Some(all_frames_fid) = all_frames_fid {
fid = all_frames_fid;
}
let include_minibuffer = args.get(1).is_some_and(|v| *v == Value::T);
let frame = frames
.get(fid)
.ok_or_else(|| signal("error", vec![Value::string("Frame not found")]))?;
let mut ids: Vec<Value> = frame.window_list().into_iter().map(window_value).collect();
if include_minibuffer {
if let Some(minibuffer_wid) = frame.minibuffer_window {
ids.push(window_value(minibuffer_wid));
}
}
Ok(Value::list(ids))
}
pub(crate) fn builtin_window_list_1(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("window-list-1", &args, 3)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (fid, start_wid) = if args.first().is_none_or(|v| v.is_nil()) {
resolve_window_id_with_pred_in_state(frames, buffers, None, "window-live-p")?
} else {
let val = args.first().unwrap();
if let Some(raw_id) = val.as_window_id() {
let wid = WindowId(raw_id);
if let Some(fid) = frames.find_window_frame_id(wid) {
(fid, wid)
} else {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("window-live-p"), args[0]],
));
}
} else {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("window-live-p"), *val],
));
}
};
let mut frame_ids: Vec<FrameId> = if args.get(2).is_none_or(|v| v.is_nil()) {
vec![fid]
} else {
let af = args.get(2).unwrap();
if *af == Value::T {
let mut ids = frames.frame_list();
ids.sort_by_key(|f| f.0);
ids
} else if af.as_symbol_name() == Some("visible") {
let mut ids = frames.frame_list();
ids.sort_by_key(|f| f.0);
ids.into_iter()
.filter(|frame_id| frames.get(*frame_id).is_some_and(|frame| frame.visible))
.collect()
} else if af.as_fixnum() == Some(0) {
let mut ids = frames.frame_list();
ids.sort_by_key(|f| f.0);
ids.into_iter()
.filter(|frame_id| frames.get(*frame_id).is_some_and(|frame| frame.visible))
.collect()
} else if let Some(raw_id) = af.as_frame_id() {
let frame_id = FrameId(raw_id);
if frames.get(frame_id).is_none() {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("frame-live-p"), args[2]],
));
}
vec![frame_id]
} else {
vec![fid]
}
};
if frame_ids.is_empty() {
frame_ids.push(fid);
}
if let Some(start_pos) = frame_ids.iter().position(|frame_id| *frame_id == fid) {
frame_ids.rotate_left(start_pos);
}
let include_minibuffer = args.get(1).is_some_and(|v| *v == Value::T);
let mut seen_window_ids: HashSet<u64> = HashSet::new();
let mut windows: Vec<Value> = Vec::new();
for frame_id in frame_ids {
let Some(frame) = frames.get(frame_id) else {
continue;
};
let mut window_ids = frame.window_list();
if frame_id == fid {
if let Some(start_index) = window_ids.iter().position(|wid| *wid == start_wid) {
window_ids.rotate_left(start_index);
}
}
for window_id in window_ids {
if seen_window_ids.insert(window_id.0) {
windows.push(window_value(window_id));
}
}
if include_minibuffer {
if let Some(minibuffer_wid) = frame.minibuffer_window {
if seen_window_ids.insert(minibuffer_wid.0) {
windows.push(window_value(minibuffer_wid));
}
}
}
}
Ok(Value::list(windows))
}
pub(crate) fn builtin_get_buffer_window(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_max_args("get-buffer-window", &args, 2)?;
if args.first().is_none_or(|v| v.is_nil()) {
return Ok(Value::NIL);
}
let val = args.first().unwrap();
let target = match val.kind() {
ValueKind::String => {
let name_s = val.as_str().unwrap();
match eval.buffers.find_buffer_by_name(name_s) {
Some(id) => id,
None => return Ok(Value::NIL),
}
}
ValueKind::Veclike(VecLikeType::Buffer) => {
let bid = val.as_buffer_id().unwrap();
if eval.buffers.get(bid).is_none() {
return Ok(Value::NIL);
}
bid
}
_ => {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("stringp"), *val],
));
}
};
let fid = ensure_selected_frame_id(eval);
let frame = eval
.frames
.get(fid)
.ok_or_else(|| signal("error", vec![Value::string("Frame not found")]))?;
for wid in frame.window_list() {
let matches = frame
.find_window(wid)
.and_then(|w| w.buffer_id())
.is_some_and(|bid| bid == target);
if matches {
return Ok(window_value(wid));
}
}
Ok(Value::NIL)
}
pub(crate) fn builtin_window_dedicated_p(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("window-dedicated-p", &args, 1)?;
let (fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-live-p")?;
let w = get_leaf(frames, fid, wid)?;
match w {
Window::Leaf { dedicated, .. } => Ok(Value::bool_val(*dedicated)),
_ => Ok(Value::NIL),
}
}
pub(crate) fn builtin_set_window_dedicated_p(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_args("set-window-dedicated-p", &args, 2)?;
let flag = args[1].is_truthy();
let (fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-live-p")?;
if let Some(w) = frames.get_mut(fid).and_then(|f| f.find_window_mut(wid)) {
if let Window::Leaf { dedicated, .. } = w {
*dedicated = flag;
}
}
Ok(Value::bool_val(flag))
}
pub(crate) fn builtin_windowp(eval: &mut super::eval::Context, args: Vec<Value>) -> EvalResult {
let frames = &eval.frames;
expect_args("windowp", &args, 1)?;
let wid = match window_id_from_designator(&args[0]) {
Some(wid) => wid,
None => return Ok(Value::NIL),
};
Ok(Value::bool_val(frames.is_window_object_id(wid)))
}
pub(crate) fn builtin_window_valid_p(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let frames = &eval.frames;
expect_args("window-valid-p", &args, 1)?;
let wid = match window_id_from_designator(&args[0]) {
Some(wid) => wid,
None => return Ok(Value::NIL),
};
Ok(Value::bool_val(frames.is_valid_window_id(wid)))
}
pub(crate) fn builtin_window_live_p(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let frames = &eval.frames;
expect_args("window-live-p", &args, 1)?;
let wid = match window_id_from_designator(&args[0]) {
Some(wid) => wid,
None => return Ok(Value::NIL),
};
Ok(Value::bool_val(frames.is_live_window_id(wid)))
}
pub(crate) fn builtin_window_at(eval: &mut super::eval::Context, args: Vec<Value>) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_min_args("window-at", &args, 2)?;
expect_max_args("window-at", &args, 3)?;
let x = expect_number(&args[0])?;
let y = expect_number(&args[1])?;
let fid = resolve_frame_id_in_state(frames, buffers, args.get(2), "frame-live-p")?;
let frame = frames
.get(fid)
.ok_or_else(|| signal("error", vec![Value::string("Frame not found")]))?;
let total_cols = frame_total_cols(frame) as f64;
let total_lines = frame_total_lines(frame) as f64;
if x < 0.0 || y < 0.0 || x >= total_cols || y >= total_lines {
return Ok(Value::NIL);
}
let px = (x * frame.char_width as f64) as f32;
let py = (y * frame.char_height as f64) as f32;
if let Some(wid) = frame.window_at(px, py) {
return Ok(window_value(wid));
}
if let (Some(minibuffer_wid), Some(minibuffer_leaf)) =
(frame.minibuffer_window, frame.minibuffer_leaf.as_ref())
{
if minibuffer_leaf.bounds().contains(px, py) {
return Ok(window_value(minibuffer_wid));
}
}
Ok(Value::NIL)
}
pub(crate) fn split_window_internal_impl(
eval: &mut super::eval::Context,
window: Value,
size: Value,
side: Value,
) -> EvalResult {
split_window_internal_impl_in_state(&mut eval.frames, &mut eval.buffers, window, size, side)
}
pub(crate) fn split_window_internal_impl_in_state(
frames: &mut FrameManager,
buffers: &mut BufferManager,
window: Value,
size: Value,
side: Value,
) -> EvalResult {
let (fid, wid) = resolve_window_id_or_error_in_state(frames, buffers, Some(&window))?;
let direction = match side.kind() {
ValueKind::Symbol(id) if resolve_sym(id) == "right" || resolve_sym(id) == "left" => {
SplitDirection::Horizontal
}
_ => SplitDirection::Vertical,
};
let size_opt: Option<i64> = match size.kind() {
ValueKind::Fixnum(n) if n != 0 => Some(n),
_ => None,
};
let buf_id = {
let w = get_leaf(frames, fid, wid)?;
w.buffer_id().unwrap_or(BufferId(0))
};
let new_wid = frames
.split_window(fid, wid, direction, buf_id, size_opt)
.ok_or_else(|| signal("error", vec![Value::string("Cannot split window")]))?;
Ok(window_value(new_wid))
}
pub(crate) fn builtin_delete_window(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_max_args("delete-window", &args, 1)?;
let (fid, wid) =
resolve_window_id_or_error_in_state(&mut eval.frames, &mut eval.buffers, args.first())?;
if !eval.frames.delete_window(fid, wid) {
return Err(signal(
"error",
vec![Value::string("Cannot delete sole window")],
));
}
let selected_buffer = eval
.frames
.get(fid)
.and_then(|frame| frame.find_window(frame.selected_window))
.and_then(|w| w.buffer_id());
if let Some(buffer_id) = selected_buffer {
eval.buffers.switch_current(buffer_id);
}
note_selected_window_buffer_in_state(&mut eval.frames, &mut eval.buffers, fid);
let _ = builtin_run_window_configuration_change_hook(eval, vec![]);
Ok(Value::NIL)
}
pub(crate) fn builtin_delete_other_windows(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_max_args("delete-other-windows", &args, 2)?;
let (fid, keep_wid) =
resolve_window_id_or_error_in_state(&mut eval.frames, &mut eval.buffers, args.first())?;
let frame = eval
.frames
.get(fid)
.ok_or_else(|| signal("error", vec![Value::string("Frame not found")]))?;
let all_ids: Vec<WindowId> = frame.window_list();
let to_delete: Vec<WindowId> = all_ids.into_iter().filter(|&w| w != keep_wid).collect();
for wid in to_delete {
let _ = eval.frames.delete_window(fid, wid);
}
let selected_buffer = if let Some(f) = eval.frames.get_mut(fid) {
f.select_window(keep_wid);
f.find_window(keep_wid).and_then(|w| w.buffer_id())
} else {
None
};
if let Some(buffer_id) = selected_buffer {
eval.buffers.switch_current(buffer_id);
}
note_selected_window_buffer_in_state(&mut eval.frames, &mut eval.buffers, fid);
let _ = builtin_run_window_configuration_change_hook(eval, vec![]);
Ok(Value::NIL)
}
pub(crate) fn builtin_delete_window_internal(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_args("delete-window-internal", &args, 1)?;
let wid =
resolve_window_object_id_with_pred_in_state(frames, buffers, args.first(), "windowp")?;
if !frames.is_valid_window_id(wid) {
return Ok(Value::NIL);
}
let fid = frames
.find_valid_window_frame_id(wid)
.ok_or_else(|| signal("error", vec![Value::string("Frame not found")]))?;
let frame = frames
.get(fid)
.ok_or_else(|| signal("error", vec![Value::string("Frame not found")]))?;
let is_minibuffer = frame.minibuffer_window == Some(wid);
let is_sole_ordinary_window = frame.window_list().len() <= 1;
if is_minibuffer || is_sole_ordinary_window {
return Err(signal(
"error",
vec![Value::string(
"Attempt to delete minibuffer or sole ordinary window",
)],
));
}
if frames.delete_window(fid, wid) {
Ok(Value::NIL)
} else {
Err(signal("error", vec![Value::string("Deletion failed")]))
}
}
pub(crate) fn builtin_delete_other_windows_internal(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("delete-other-windows-internal", &args, 2)?;
let (fid, keep_wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-valid-p")?;
let frame = frames
.get(fid)
.ok_or_else(|| signal("error", vec![Value::string("Frame not found")]))?;
let all_ids: Vec<WindowId> = frame.window_list();
let to_delete: Vec<WindowId> = all_ids.into_iter().filter(|&w| w != keep_wid).collect();
for wid in to_delete {
let _ = frames.delete_window(fid, wid);
}
let selected_buffer = if let Some(f) = frames.get_mut(fid) {
f.select_window(keep_wid);
f.find_window(keep_wid).and_then(|w| w.buffer_id())
} else {
None
};
if let Some(buffer_id) = selected_buffer {
buffers.switch_current(buffer_id);
}
Ok(Value::NIL)
}
fn remember_selected_window_point_in_state(
frames: &mut FrameManager,
buffers: &BufferManager,
fid: FrameId,
) {
let Some(frame) = frames.get(fid) else {
return;
};
let selected_wid = frame.selected_window;
let Some(buffer_id) = frame
.find_window(selected_wid)
.and_then(|window| window.buffer_id())
else {
return;
};
if buffers.current_buffer_id() != Some(buffer_id) {
return;
}
let Some(point) = buffers
.get(buffer_id)
.map(|buffer| buffer.point_char().saturating_add(1))
else {
return;
};
if let Some(Window::Leaf {
point: window_point,
..
}) = frames
.get_mut(fid)
.and_then(|frame| frame.find_window_mut(selected_wid))
{
*window_point = point;
}
}
fn sync_selected_window_buffer_in_state(
frames: &FrameManager,
buffers: &mut BufferManager,
fid: FrameId,
) {
let Some((buffer_id, point)) = frames
.get(fid)
.and_then(|frame| frame.find_window(frame.selected_window))
.and_then(|window| match window {
Window::Leaf {
buffer_id, point, ..
} => Some((*buffer_id, *point)),
Window::Internal { .. } => None,
})
else {
return;
};
buffers.switch_current(buffer_id);
if let Some(buffer) = buffers.get(buffer_id) {
let byte_pos = buffer.lisp_pos_to_byte(point as i64);
let _ = buffers.goto_buffer_byte(buffer_id, byte_pos);
}
}
fn selected_window_buffer_state_in_frame(
frames: &FrameManager,
fid: FrameId,
) -> Option<(WindowId, BufferId)> {
let frame = frames.get(fid)?;
let selected_wid = frame.selected_window;
let buffer_id = frame.find_window(selected_wid)?.buffer_id()?;
Some((selected_wid, buffer_id))
}
fn note_selected_window_buffer_in_state(
frames: &FrameManager,
buffers: &mut BufferManager,
fid: FrameId,
) {
let Some((selected_wid, buffer_id)) = selected_window_buffer_state_in_frame(frames, fid) else {
return;
};
if let Some(buffer) = buffers.get_mut(buffer_id) {
buffer.last_selected_window = Some(selected_wid);
}
}
fn record_buffer_display_in_state(buffers: &mut BufferManager, buffer_id: BufferId) -> EvalResult {
let display_time = super::timefns::builtin_current_time(vec![])?;
let Some(buffer) = buffers.get_mut(buffer_id) else {
return Ok(Value::NIL);
};
if let Some(count) = buffer
.buffer_local_value("buffer-display-count")
.and_then(|v| v.as_fixnum())
{
buffer.set_buffer_local(
"buffer-display-count",
Value::fixnum(count.saturating_add(1)),
);
}
buffer.set_buffer_local("buffer-display-time", display_time);
Ok(Value::NIL)
}
fn window_displays_buffer(frames: &FrameManager, window_id: WindowId, buffer_id: BufferId) -> bool {
frames
.find_window_frame_id(window_id)
.and_then(|frame_id| frames.get(frame_id))
.and_then(|frame| frame.find_window(window_id))
.and_then(Window::buffer_id)
== Some(buffer_id)
}
pub(crate) fn builtin_select_window(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_min_args("select-window", &args, 1)?;
expect_max_args("select-window", &args, 2)?;
let wid = match args.first().and_then(window_id_from_designator) {
Some(wid) => wid,
None => {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("window-live-p"), args[0]],
));
}
};
let (record_selection, run_buffer_list_hook) = {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
let fid = ensure_selected_frame_id_in_state(frames, buffers);
let record_selection = args.get(1).is_none_or(|v| v.is_nil());
remember_selected_window_point_in_state(frames, buffers, fid);
{
let frame = frames
.get_mut(fid)
.ok_or_else(|| signal("error", vec![Value::string("No selected frame")]))?;
if !frame.select_window(wid) {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("window-live-p"), args[0]],
));
}
}
if record_selection {
let _ = frames.note_window_selected(wid);
}
sync_selected_window_buffer_in_state(frames, buffers, fid);
note_selected_window_buffer_in_state(frames, buffers, fid);
let run_buffer_list_hook = record_selection
&& selected_window_buffer_state_in_frame(frames, fid)
.is_some_and(|(_, buffer_id)| !buffers.buffer_hooks_inhibited(buffer_id));
(record_selection, run_buffer_list_hook)
};
if record_selection && run_buffer_list_hook {
super::builtins::run_buffer_list_update_hook(eval)?;
}
Ok(window_value(wid))
}
pub(crate) fn builtin_other_window(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_min_args("other-window", &args, 1)?;
expect_max_args("other-window", &args, 3)?;
let count = expect_number_or_marker_count(&args[0])?;
let run_buffer_list_hook = {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let Some(fid) = frames.selected_frame().map(|f| f.id) else {
return Ok(Value::NIL);
};
let Some(frame) = frames.get(fid) else {
return Ok(Value::NIL);
};
let list = frame.window_list();
if list.is_empty() {
return Ok(Value::NIL);
}
let cur = frame.selected_window;
let cur_idx = list.iter().position(|w| *w == cur).unwrap_or(0);
let len = list.len() as i64;
let new_idx = ((cur_idx as i64 + count) % len + len) % len;
let new_wid = list[new_idx as usize];
remember_selected_window_point_in_state(frames, buffers, fid);
let switched = if let Some(frame) = frames.get_mut(fid) {
frame.select_window(new_wid)
} else {
false
};
if switched {
let _ = frames.note_window_selected(new_wid);
};
sync_selected_window_buffer_in_state(frames, buffers, fid);
note_selected_window_buffer_in_state(frames, buffers, fid);
selected_window_buffer_state_in_frame(frames, fid)
.is_some_and(|(_, buffer_id)| !buffers.buffer_hooks_inhibited(buffer_id))
};
if run_buffer_list_hook {
super::builtins::run_buffer_list_update_hook(eval)?;
}
Ok(Value::NIL)
}
pub(crate) fn builtin_other_window_for_scrolling(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_args("other-window-for-scrolling", &args, 0)?;
let fid = ensure_selected_frame_id_in_state(frames, buffers);
let frame = frames
.get(fid)
.ok_or_else(|| signal("error", vec![Value::string("No selected frame")]))?;
let windows = frame.window_list();
if windows.len() <= 1 {
return Err(signal(
"error",
vec![Value::string("There is no other window")],
));
}
let selected = frame.selected_window;
let other = windows
.into_iter()
.find(|wid| *wid != selected)
.unwrap_or(selected);
Ok(window_value(other))
}
pub(crate) fn builtin_next_window(eval: &mut super::eval::Context, args: Vec<Value>) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("next-window", &args, 3)?;
let (fid, wid) = resolve_window_id_in_state(frames, buffers, args.first())?;
let frame = frames
.get(fid)
.ok_or_else(|| signal("error", vec![Value::string("Frame not found")]))?;
let list = frame.window_list();
if list.is_empty() {
return Ok(Value::NIL);
}
let idx = list.iter().position(|w| *w == wid).unwrap_or(0);
let next = (idx + 1) % list.len();
Ok(window_value(list[next]))
}
pub(crate) fn builtin_previous_window(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("previous-window", &args, 3)?;
let (fid, wid) = resolve_window_id_in_state(frames, buffers, args.first())?;
let frame = frames
.get(fid)
.ok_or_else(|| signal("error", vec![Value::string("Frame not found")]))?;
let list = frame.window_list();
if list.is_empty() {
return Ok(Value::NIL);
}
let idx = list.iter().position(|w| *w == wid).unwrap_or(0);
let prev = if idx == 0 { list.len() - 1 } else { idx - 1 };
Ok(window_value(list[prev]))
}
pub(crate) fn builtin_set_window_buffer(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_min_args("set-window-buffer", &args, 2)?;
expect_max_args("set-window-buffer", &args, 3)?;
let run_buffer_list_hook = {
let (frames, buffers, minibuffers) =
(&mut eval.frames, &mut eval.buffers, &eval.minibuffers);
let (fid, wid) = resolve_window_id_in_state(frames, buffers, args.first())?;
let buf_id = match args[1].kind() {
ValueKind::Veclike(VecLikeType::Buffer) => {
let bid = args[1].as_buffer_id().unwrap();
if buffers.get(bid).is_none() {
return Err(signal(
"error",
vec![Value::string("Attempt to display deleted buffer")],
));
}
bid
}
ValueKind::String => {
let name_s = args[1].as_str().unwrap();
match buffers.find_buffer_by_name(name_s) {
Some(id) => id,
None => {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("bufferp"), Value::NIL],
));
}
}
}
_ => {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("stringp"), args[1]],
));
}
};
let keep_margins = args.get(2).is_some_and(|arg| !arg.is_nil());
let selected_fid = ensure_selected_frame_id_in_state(frames, buffers);
let next_margins = if keep_margins {
None
} else {
Some((
buffer_margin_width(buffers, buf_id, "left-margin-width")?,
buffer_margin_width(buffers, buf_id, "right-margin-width")?,
))
};
let next_fringes = if keep_margins {
None
} else {
Some((
buffer_local_optional_dimension(buffers, buf_id, "left-fringe-width")?,
buffer_local_optional_dimension(buffers, buf_id, "right-fringe-width")?,
buffer_local_value(buffers, buf_id, "fringes-outside-margins").is_truthy(),
))
};
let next_scroll_bars = if keep_margins {
None
} else {
let vertical_type = buffer_local_value(buffers, buf_id, "vertical-scroll-bar");
if !valid_vertical_scroll_bar_type(vertical_type) {
return Err(signal(
"error",
vec![Value::string("Invalid type of vertical scroll bar")],
));
}
let horizontal_type = buffer_local_value(buffers, buf_id, "horizontal-scroll-bar");
if !valid_horizontal_scroll_bar_type(horizontal_type) {
return Err(signal(
"error",
vec![Value::string("Invalid type of horizontal scroll bar")],
));
}
Some((
buffer_local_optional_dimension(buffers, buf_id, "scroll-bar-width")?,
vertical_type,
buffer_local_optional_dimension(buffers, buf_id, "scroll-bar-height")?,
horizontal_type,
))
};
let mut old_state = None;
if let Some(Window::Leaf {
buffer_id,
window_start,
point,
dedicated,
..
}) = frames.get_mut(fid).and_then(|f| f.find_window_mut(wid))
{
old_state = Some((*buffer_id, *window_start, *point, *dedicated));
}
if let Some((old_buffer_id, old_window_start, old_point, dedicated)) = old_state {
if dedicated && old_buffer_id != buf_id {
let old_buffer_name = buffers
.get(old_buffer_id)
.map(|buffer| buffer.name.clone())
.unwrap_or_else(|| "*deleted*".to_string());
return Err(signal(
"error",
vec![Value::string(format!(
"Window is dedicated to ‘{old_buffer_name}’"
))],
));
}
if let Some(buffer) = buffers.get_mut(old_buffer_id) {
buffer.last_window_start = old_window_start.max(1);
}
let selected_buffer_id = frames
.get(selected_fid)
.and_then(|frame| frame.find_window(frame.selected_window))
.and_then(Window::buffer_id);
let old_buffer_last_selected_window = buffers
.get(old_buffer_id)
.and_then(|buffer| buffer.last_selected_window);
let preserve_old_buffer_point = selected_buffer_id == Some(old_buffer_id)
|| old_buffer_last_selected_window.is_some_and(|last_selected_window| {
last_selected_window != wid
&& window_displays_buffer(frames, last_selected_window, old_buffer_id)
});
if !preserve_old_buffer_point && let Some(buffer) = buffers.get_mut(old_buffer_id) {
buffer.goto_char(old_point.saturating_sub(1));
}
if old_buffer_id != buf_id
&& let Some(buffer) = buffers.get_mut(old_buffer_id)
&& buffer.last_selected_window == Some(wid)
{
buffer.last_selected_window = None;
}
if old_buffer_id != buf_id {
let old_buffer_value = Value::make_buffer(old_buffer_id);
let new_buffer_value = Value::make_buffer(buf_id);
let old_window_start_pos = old_window_start.max(1) as i64;
let old_point_pos = old_point.max(1) as i64;
let history_entry = Value::list(vec![
old_buffer_value,
super::marker::make_marker_value(
Some(old_buffer_id),
Some(old_window_start_pos),
false,
),
super::marker::make_marker_value(
Some(old_buffer_id),
Some(old_point_pos),
false,
),
]);
let filtered_prev = filtered_window_prev_buffers(
frames.window_prev_buffers(wid),
&[old_buffer_value, new_buffer_value],
)?;
frames.set_window_next_buffers(wid, Value::NIL);
if should_record_window_history_buffer(
frames,
minibuffers,
buffers,
fid,
wid,
old_buffer_id,
) {
let mut next_prev = Vec::with_capacity(filtered_prev.len() + 1);
next_prev.push(history_entry);
next_prev.extend(filtered_prev);
frames.set_window_prev_buffers(wid, Value::list(next_prev));
} else {
frames.set_window_prev_buffers(wid, Value::list(filtered_prev));
}
} else {
discard_buffers_from_window_history(frames, wid, &[Value::make_buffer(buf_id)])?;
}
}
let selected_window = frames.get(fid).map(|frame| frame.selected_window);
let same_buffer = old_state.is_some_and(|(old_buffer_id, _, _, _)| old_buffer_id == buf_id);
let (next_window_start, next_point) = if same_buffer && keep_margins {
old_state
.map(|(_, window_start, point, _)| (window_start.max(1), point.max(1)))
.unwrap_or((1, 1))
} else {
buffers
.get(buf_id)
.map(|buf| {
(
buf.last_window_start.max(1),
buf.point_char().saturating_add(1).max(1),
)
})
.unwrap_or((1, 1))
};
frames.apply_set_window_buffer_state(
wid,
buf_id,
next_window_start,
next_point,
same_buffer && keep_margins,
WindowBufferDisplayDefaults {
margins: next_margins,
fringes: next_fringes,
scroll_bars: next_scroll_bars,
},
);
record_buffer_display_in_state(buffers, buf_id)?;
if selected_window == Some(wid)
&& let Some(buffer) = buffers.get_mut(buf_id)
{
buffer.last_selected_window = Some(wid);
}
!is_minibuffer_window(frames, fid, wid) && !buffers.buffer_hooks_inhibited(buf_id)
};
if run_buffer_list_hook {
super::builtins::run_buffer_list_update_hook(eval)?;
}
Ok(Value::NIL)
}
pub(crate) fn builtin_switch_to_buffer(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_min_args("switch-to-buffer", &args, 1)?;
expect_max_args("switch-to-buffer", &args, 3)?;
let record_selection = args.get(1).is_none_or(|v| v.is_nil());
let (buf_id, run_buffer_list_hook) = {
let buf_id = match args[0].kind() {
ValueKind::Veclike(VecLikeType::Buffer) => {
let bid = args[0].as_buffer_id().unwrap();
if eval.buffers.get(bid).is_none() {
return Err(signal(
"error",
vec![Value::string("Attempt to display deleted buffer")],
));
}
bid
}
ValueKind::String => {
let name_s = args[0].as_str().unwrap();
match eval.buffers.find_buffer_by_name(name_s) {
Some(id) => id,
None => eval.buffers.create_buffer(name_s),
}
}
_ => {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("stringp"), args[0]],
));
}
};
let fid = ensure_selected_frame_id(eval);
let sel_wid = eval
.frames
.get(fid)
.map(|f| f.selected_window)
.ok_or_else(|| signal("error", vec![Value::string("No selected window")]))?;
if let Some(w) = eval
.frames
.get_mut(fid)
.and_then(|f| f.find_window_mut(sel_wid))
{
w.set_buffer(buf_id);
}
eval.switch_current_buffer(buf_id)?;
if let Some(buffer) = eval.buffers.get_mut(buf_id) {
buffer.last_selected_window = Some(sel_wid);
}
(
buf_id,
record_selection
&& !is_minibuffer_window(&eval.frames, fid, sel_wid)
&& !eval.buffers.buffer_hooks_inhibited(buf_id),
)
};
if run_buffer_list_hook {
super::builtins::run_buffer_list_update_hook(eval)?;
}
Ok(Value::make_buffer(buf_id))
}
pub(crate) fn builtin_display_buffer(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_min_args("display-buffer", &args, 1)?;
expect_max_args("display-buffer", &args, 3)?;
let buf_id = match args[0].kind() {
ValueKind::Veclike(VecLikeType::Buffer) => {
let bid = args[0].as_buffer_id().unwrap();
if eval.buffers.get(bid).is_none() {
return Err(signal("error", vec![Value::string("Invalid buffer")]));
}
bid
}
ValueKind::String => {
let name_s = args[0].as_str().unwrap();
match eval.buffers.find_buffer_by_name(name_s) {
Some(id) => id,
None => return Err(signal("error", vec![Value::string("Invalid buffer")])),
}
}
_ => {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("stringp"), args[0]],
));
}
};
let fid = ensure_selected_frame_id(eval);
let sel_wid = eval
.frames
.get(fid)
.map(|f| f.selected_window)
.ok_or_else(|| signal("error", vec![Value::string("No selected window")]))?;
let action = args.get(1).copied().unwrap_or(Value::NIL);
let action_contains = |name: &str| -> bool {
if action.is_nil() {
return false;
}
let car = match action.kind() {
ValueKind::Cons => {
let snap_car = action.cons_car();
let snap_cdr = action.cons_cdr();
snap_car
}
_ => return false,
};
if let Some(sym_name) = car.as_symbol_name() {
if sym_name == name {
return true;
}
}
let mut cursor = car;
while cursor.is_cons() {
let snap_car = cursor.cons_car();
let snap_cdr = cursor.cons_cdr();
if let Some(sym_name) = snap_car.as_symbol_name() {
if sym_name == name {
return true;
}
}
cursor = snap_cdr;
}
false
};
{
let frame = eval
.frames
.get(fid)
.ok_or_else(|| signal("error", vec![Value::string("Frame not found")]))?;
for wid in frame.window_list() {
if let Some(w) = frame.find_window(wid) {
if w.buffer_id() == Some(buf_id) {
return Ok(window_value(wid));
}
}
}
}
if action_contains("display-buffer-same-window") {
if let Some(w) = eval
.frames
.get_mut(fid)
.and_then(|f| f.find_window_mut(sel_wid))
{
w.set_buffer(buf_id);
}
return Ok(window_value(sel_wid));
}
if action_contains("display-buffer-pop-up-window") {
let new_wid = eval
.frames
.split_window(fid, sel_wid, SplitDirection::Vertical, buf_id, None)
.ok_or_else(|| signal("error", vec![Value::string("Cannot split window")]))?;
return Ok(window_value(new_wid));
}
{
let window_list = eval
.frames
.get(fid)
.map(|f| f.window_list())
.unwrap_or_default();
if let Some(&other_wid) = window_list.iter().find(|&&wid| wid != sel_wid) {
if let Some(w) = eval
.frames
.get_mut(fid)
.and_then(|f| f.find_window_mut(other_wid))
{
w.set_buffer(buf_id);
}
return Ok(window_value(other_wid));
}
let new_wid = eval
.frames
.split_window(fid, sel_wid, SplitDirection::Vertical, buf_id, None)
.ok_or_else(|| signal("error", vec![Value::string("Cannot split window")]))?;
Ok(window_value(new_wid))
}
}
pub(crate) fn builtin_pop_to_buffer(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_min_args("pop-to-buffer", &args, 1)?;
expect_max_args("pop-to-buffer", &args, 3)?;
let buf_id = match args[0].kind() {
ValueKind::Veclike(VecLikeType::Buffer) => {
let bid = args[0].as_buffer_id().unwrap();
if eval.buffers.get(bid).is_none() {
return Err(signal("error", vec![Value::string("Invalid buffer")]));
}
bid
}
ValueKind::String => {
let name_s = args[0].as_str().unwrap();
match eval.buffers.find_buffer_by_name(name_s) {
Some(id) => id,
None => eval.buffers.create_buffer(name_s),
}
}
_ => {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("stringp"), args[0]],
));
}
};
let fid = ensure_selected_frame_id(eval);
let sel_wid = eval
.frames
.get(fid)
.map(|f| f.selected_window)
.ok_or_else(|| signal("error", vec![Value::string("No selected window")]))?;
if let Some(w) = eval
.frames
.get_mut(fid)
.and_then(|f| f.find_window_mut(sel_wid))
{
w.set_buffer(buf_id);
}
eval.switch_current_buffer(buf_id)?;
Ok(Value::make_buffer(buf_id))
}
const MIN_FRAME_COLS: i64 = 10;
const MIN_FRAME_TEXT_LINES: i64 = 5;
const FRAME_TEXT_LINES_PARAM: &str = "neovm--frame-text-lines";
fn frame_total_cols(frame: &crate::window::Frame) -> i64 {
frame
.parameters
.get("width")
.and_then(|v| v.as_int())
.unwrap_or(frame.columns() as i64)
}
fn frame_text_cols(frame: &crate::window::Frame) -> i64 {
frame_total_cols(frame)
}
fn frame_uses_window_system_pixels(frame: &crate::window::Frame) -> bool {
frame.effective_window_system().is_some()
}
fn frame_non_text_height_pixels(frame: &crate::window::Frame) -> u32 {
let minibuffer_height = frame
.minibuffer_leaf
.as_ref()
.map(|leaf| leaf.bounds().height.max(0.0).round() as u32)
.unwrap_or(0);
frame
.menu_bar_height
.saturating_add(frame.tool_bar_height)
.saturating_add(frame.tab_bar_height)
.saturating_add(minibuffer_height)
}
fn frame_text_width_pixels(frame: &crate::window::Frame) -> u32 {
frame.width
}
fn frame_text_height_pixels(frame: &crate::window::Frame) -> u32 {
frame
.height
.saturating_sub(frame_non_text_height_pixels(frame))
.max(1)
}
fn check_frame_pixels(value: &Value, pixelwise: bool, item_size: f32) -> Result<u32, Flow> {
let size = expect_int(value)?;
if size <= 0 {
return Err(signal(
"args-out-of-range",
vec![*value, Value::fixnum(1), Value::fixnum(i64::from(i32::MAX))],
));
}
let unit = if pixelwise {
1
} else {
item_size.max(1.0).round() as i64
};
let pixels = size.checked_mul(unit).ok_or_else(|| {
signal(
"args-out-of-range",
vec![*value, Value::fixnum(1), Value::fixnum(i64::from(i32::MAX))],
)
})?;
if pixels <= 0 || pixels > u32::MAX as i64 {
return Err(signal(
"args-out-of-range",
vec![*value, Value::fixnum(1), Value::fixnum(i64::from(i32::MAX))],
));
}
Ok(pixels as u32)
}
fn frame_total_lines(frame: &crate::window::Frame) -> i64 {
frame
.parameters
.get("height")
.and_then(|v| v.as_int())
.unwrap_or(frame.lines() as i64)
}
fn frame_text_lines(frame: &crate::window::Frame) -> i64 {
frame
.parameters
.get(FRAME_TEXT_LINES_PARAM)
.and_then(|v| v.as_int())
.unwrap_or_else(|| frame_total_lines(frame))
}
fn clamp_frame_dimension(value: i64, minimum: i64) -> i64 {
value.max(minimum).min(u32::MAX as i64)
}
fn set_frame_text_size(frame: &mut crate::window::Frame, cols: i64, text_lines: i64) {
let cols = clamp_frame_dimension(cols, MIN_FRAME_COLS);
let text_lines = clamp_frame_dimension(text_lines, MIN_FRAME_TEXT_LINES);
let total_lines = text_lines.saturating_add(1).min(u32::MAX as i64);
frame
.parameters
.insert("width".to_string(), Value::fixnum(cols));
frame
.parameters
.insert("height".to_string(), Value::fixnum(total_lines));
frame.parameters.insert(
FRAME_TEXT_LINES_PARAM.to_string(),
Value::fixnum(text_lines),
);
}
fn resize_live_gui_frame(
frames: &mut FrameManager,
display_host: &mut Option<Box<dyn super::eval::DisplayHost>>,
fid: FrameId,
text_width_px: u32,
text_height_px: u32,
pretend: bool,
) -> Result<(), Flow> {
let (total_width_px, total_height_px, title, cols, text_lines) = {
let frame = frames
.get(fid)
.ok_or_else(|| signal("error", vec![Value::string("Frame not found")]))?;
let char_width = frame.char_width.max(1.0);
let char_height = frame.char_height.max(1.0);
let cols = ((text_width_px as f32) / char_width).floor().max(1.0) as i64;
let text_lines = ((text_height_px as f32) / char_height).floor().max(1.0) as i64;
let non_text_height = frame_non_text_height_pixels(frame);
let title = if frame.title.is_empty() {
frame.name.clone()
} else {
frame.title.clone()
};
(
text_width_px.max(1),
text_height_px.saturating_add(non_text_height).max(1),
title,
cols,
text_lines,
)
};
{
let frame = frames
.get_mut(fid)
.ok_or_else(|| signal("error", vec![Value::string("Frame not found")]))?;
tracing::debug!(
"resize_live_gui_frame: fid={:?} pretend={} total={}x{} cols={} text_lines={}",
fid,
pretend,
total_width_px,
total_height_px,
cols,
text_lines
);
if pretend {
set_frame_text_size(frame, cols, text_lines);
} else {
frame.resize_pixelwise(total_width_px, total_height_px);
frame.parameters.insert(
FRAME_TEXT_LINES_PARAM.to_string(),
Value::fixnum(text_lines),
);
}
}
if !pretend && let Some(host) = display_host.as_mut() {
tracing::debug!(
"resize_live_gui_frame: notifying host fid={:?} size={}x{} title={}",
fid,
total_width_px,
total_height_px,
title
);
host.resize_gui_frame(super::eval::GuiFrameHostRequest {
frame_id: fid,
width: total_width_px,
height: total_height_px,
title,
})
.map_err(|message| signal("error", vec![Value::string(message)]))?;
}
Ok(())
}
fn recenter_missing_display_error() -> Flow {
signal(
"error",
vec![Value::string(
"‘recenter’ing a window that does not display current-buffer.",
)],
)
}
fn scroll_up_batch_error() -> Flow {
signal("end-of-buffer", vec![])
}
fn scroll_down_batch_error() -> Flow {
signal("beginning-of-buffer", vec![])
}
pub(crate) fn builtin_scroll_up_command(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_max_args("scroll-up-command", &args, 1)?;
builtin_scroll_up(eval, args)
}
pub(crate) fn builtin_scroll_down_command(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_max_args("scroll-down-command", &args, 1)?;
builtin_scroll_down(eval, args)
}
fn scroll_lines(eval: &mut super::eval::Context, arg: Option<&Value>, direction: i64) -> i64 {
scroll_lines_in_state(
&eval.obarray,
&mut eval.frames,
&mut eval.buffers,
arg,
direction,
)
}
fn scroll_lines_in_state(
obarray: &crate::emacs_core::symbol::Obarray,
frames: &mut FrameManager,
buffers: &mut BufferManager,
arg: Option<&Value>,
direction: i64,
) -> i64 {
if let Some(v) = arg {
if !v.is_nil() {
let n = match v.kind() {
ValueKind::Fixnum(n) => n,
_ => 1,
};
return n * direction;
}
}
let wh = window_body_height_impl(frames, buffers, vec![])
.ok()
.and_then(|v| v.as_fixnum())
.unwrap_or(24);
let ctx = obarray
.symbol_value("next-screen-context-lines")
.and_then(|v| v.as_fixnum())
.unwrap_or(2);
(wh - ctx).max(1) * direction
}
pub(crate) fn builtin_scroll_up(eval: &mut super::eval::Context, args: Vec<Value>) -> EvalResult {
expect_max_args("scroll-up", &args, 1)?;
let arg = args.first().cloned();
let lines = scroll_lines_in_state(
&eval.obarray,
&mut eval.frames,
&mut eval.buffers,
arg.as_ref(),
1,
);
let result = scroll_by_lines_in_state(&mut eval.frames, &mut eval.buffers, lines);
let _ = builtin_run_window_scroll_functions(eval, vec![]);
result
}
pub(crate) fn builtin_scroll_down(eval: &mut super::eval::Context, args: Vec<Value>) -> EvalResult {
expect_max_args("scroll-down", &args, 1)?;
let arg = args.first().cloned();
let lines = scroll_lines_in_state(
&eval.obarray,
&mut eval.frames,
&mut eval.buffers,
arg.as_ref(),
-1,
);
let result = scroll_by_lines_in_state(&mut eval.frames, &mut eval.buffers, lines);
let _ = builtin_run_window_scroll_functions(eval, vec![]);
result
}
fn scroll_by_lines(eval: &mut super::eval::Context, lines: i64) -> EvalResult {
scroll_by_lines_in_state(&mut eval.frames, &mut eval.buffers, lines)
}
fn scroll_by_lines_in_state(
frames: &mut FrameManager,
buffers: &mut BufferManager,
lines: i64,
) -> EvalResult {
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (fid, wid) = resolve_window_id_in_state(frames, buffers, None)?;
let (buffer_id, window_point) = match get_leaf(frames, fid, wid)? {
Window::Leaf {
buffer_id, point, ..
} => (*buffer_id, *point as i64),
_ => return Ok(Value::NIL),
};
let Some(buf) = buffers.get(buffer_id) else {
return Ok(Value::NIL);
};
let text = buf.text.to_string();
let pt = buf.lisp_pos_to_byte(window_point).clamp(buf.begv, buf.zv);
let bytes = text.as_bytes();
let begv = buf.begv;
let zv = buf.zv;
let mut pos = pt;
if lines > 0 {
if pt >= zv {
return Err(scroll_up_batch_error());
}
for _ in 0..lines {
while pos < zv && bytes[pos] != b'\n' {
pos += 1;
}
if pos < zv {
pos += 1; }
}
} else {
if pt <= begv {
return Err(scroll_down_batch_error());
}
let target = (-lines) as usize;
while pos > begv && bytes[pos - 1] != b'\n' {
pos -= 1;
}
for _ in 0..target {
if pos <= begv {
break;
}
pos -= 1; while pos > begv && bytes[pos - 1] != b'\n' {
pos -= 1;
}
}
}
let point_lisp = buf.text.byte_to_char(pos) + 1;
let _ = buffers.goto_buffer_byte(buffer_id, pos);
if let Some(Window::Leaf {
point,
window_start,
..
}) = frames
.get_mut(fid)
.and_then(|frame| frame.find_window_mut(wid))
{
*point = point_lisp;
*window_start = point_lisp;
}
Ok(Value::NIL)
}
pub(crate) fn builtin_recenter_top_bottom(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_max_args("recenter-top-bottom", &args, 1)?;
builtin_recenter(eval, args)
}
pub(crate) fn builtin_recenter(eval: &mut super::eval::Context, args: Vec<Value>) -> EvalResult {
expect_max_args("recenter", &args, 2)?;
{
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
let wh = window_body_height_impl(frames, buffers, vec![])
.ok()
.and_then(|v| v.as_fixnum())
.unwrap_or(24);
let target_line = match args.first().and_then(|v| v.as_fixnum()) {
Some(n) => {
if n >= 0 {
n
} else {
(wh + n).max(0)
}
}
None if args.first().is_some_and(|v| !v.is_nil()) => wh / 2, _ => wh / 2, };
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (fid, wid) = resolve_window_id_in_state(frames, buffers, None)?;
let (buffer_id, window_point) = match get_leaf(frames, fid, wid)? {
Window::Leaf {
buffer_id, point, ..
} => (*buffer_id, *point as i64),
_ => return Ok(Value::NIL),
};
let Some(buf) = buffers.get(buffer_id) else {
return Ok(Value::NIL);
};
let text = buf.text.to_string();
let pt = buf.lisp_pos_to_byte(window_point).clamp(buf.begv, buf.zv);
let bytes = text.as_bytes();
let begv = buf.begv;
let mut pos = pt;
while pos > begv && bytes[pos - 1] != b'\n' {
pos -= 1;
}
for _ in 0..target_line {
if pos <= begv {
break;
}
pos -= 1;
while pos > begv && bytes[pos - 1] != b'\n' {
pos -= 1;
}
}
let pos_lisp = buf.text.byte_to_char(pos) as i64 + 1;
if let Some(clamped) = clamped_window_position_in_state(frames, buffers, fid, wid, pos_lisp)
{
if let Some(Window::Leaf { window_start, .. }) = frames
.get_mut(fid)
.and_then(|frame| frame.find_window_mut(wid))
{
*window_start = clamped;
}
}
}
let _ = builtin_run_window_scroll_functions(eval, vec![]);
Ok(Value::NIL)
}
pub(crate) fn builtin_iconify_frame(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("iconify-frame", &args, 1)?;
let fid = resolve_frame_id_in_state(frames, buffers, args.first(), "frame-live-p")?;
let frame = frames
.get_mut(fid)
.ok_or_else(|| signal("error", vec![Value::string("Frame not found")]))?;
frame.visible = false;
Ok(Value::NIL)
}
pub(crate) fn builtin_make_frame_visible(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("make-frame-visible", &args, 1)?;
let fid = resolve_frame_id_in_state(frames, buffers, args.first(), "frame-live-p")?;
let frame = frames
.get_mut(fid)
.ok_or_else(|| signal("error", vec![Value::string("Frame not found")]))?;
frame.visible = true;
Ok(Value::make_frame(frame.id.0))
}
pub(crate) fn builtin_selected_frame(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
selected_frame_impl(&mut eval.frames, &mut eval.buffers, args)
}
pub(crate) fn selected_frame_impl(
frames: &mut FrameManager,
buffers: &mut BufferManager,
args: Vec<Value>,
) -> EvalResult {
expect_args("selected-frame", &args, 0)?;
let fid = ensure_selected_frame_id_in_state(frames, buffers);
Ok(Value::make_frame(fid.0))
}
pub(crate) fn builtin_select_frame(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_min_args("select-frame", &args, 1)?;
expect_max_args("select-frame", &args, 2)?;
let fid = match args[0].kind() {
ValueKind::Fixnum(n) => {
let fid = FrameId(n as u64);
if frames.get(fid).is_none() {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("frame-live-p"), Value::fixnum(n)],
));
}
fid
}
ValueKind::Veclike(VecLikeType::Frame) => {
let raw_id = args[0].as_frame_id().unwrap();
let fid = FrameId(raw_id);
if frames.get(fid).is_none() {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("frame-live-p"), Value::make_frame(raw_id)],
));
}
fid
}
_ => {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("frame-live-p"), args[0]],
));
}
};
if let Some(old_fid) = frames.selected_frame().map(|frame| frame.id) {
remember_selected_window_point_in_state(frames, buffers, old_fid);
}
if !frames.select_frame(fid) {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("frame-live-p"), args[0]],
));
}
if args.get(1).is_none_or(|v| v.is_nil()) {
if let Some(selected_wid) = frames.get(fid).map(|f| f.selected_window) {
let _ = frames.note_window_selected(selected_wid);
}
}
sync_selected_window_buffer_in_state(frames, buffers, fid);
eval.sync_keyboard_terminal_owner();
Ok(Value::make_frame(fid.0))
}
pub(crate) fn builtin_select_frame_set_input_focus(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_min_args("select-frame-set-input-focus", &args, 1)?;
expect_max_args("select-frame-set-input-focus", &args, 2)?;
let fid = match args[0].kind() {
ValueKind::Fixnum(n) => {
let fid = FrameId(n as u64);
if frames.get(fid).is_none() {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("frame-live-p"), Value::fixnum(n)],
));
}
fid
}
ValueKind::Veclike(VecLikeType::Frame) => {
let raw_id = args[0].as_frame_id().unwrap();
let fid = FrameId(raw_id);
if frames.get(fid).is_none() {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("frame-live-p"), Value::make_frame(raw_id)],
));
}
fid
}
_ => {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("frame-live-p"), args[0]],
));
}
};
if let Some(old_fid) = frames.selected_frame().map(|frame| frame.id) {
remember_selected_window_point_in_state(frames, buffers, old_fid);
}
if !frames.select_frame(fid) {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("frame-live-p"), args[0]],
));
}
if args.get(1).is_none_or(|v| v.is_nil()) {
if let Some(selected_wid) = frames.get(fid).map(|f| f.selected_window) {
let _ = frames.note_window_selected(selected_wid);
}
}
sync_selected_window_buffer_in_state(frames, buffers, fid);
eval.sync_keyboard_terminal_owner();
Ok(Value::NIL)
}
pub(crate) fn builtin_frame_list(eval: &mut super::eval::Context, args: Vec<Value>) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_args("frame-list", &args, 0)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let ids: Vec<Value> = frames
.frame_list()
.into_iter()
.map(|fid| Value::make_frame(fid.0))
.collect();
Ok(Value::list(ids))
}
pub(crate) fn builtin_visible_frame_list(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_args("visible-frame-list", &args, 0)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let mut frame_ids = frames.frame_list();
frame_ids.sort_by_key(|fid| fid.0);
let visible = frame_ids
.into_iter()
.filter(|fid| frames.get(*fid).is_some_and(|frame| frame.visible))
.map(|fid| Value::make_frame(fid.0))
.collect::<Vec<_>>();
Ok(Value::list(visible))
}
pub(crate) fn builtin_frame_char_height(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("frame-char-height", &args, 1)?;
let fid = resolve_frame_id_in_state(frames, buffers, args.first(), "framep")?;
let ch = frames.get(fid).map(|f| f.char_height as i64).unwrap_or(16);
Ok(Value::fixnum(ch))
}
pub(crate) fn builtin_frame_char_width(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("frame-char-width", &args, 1)?;
let fid = resolve_frame_id_in_state(frames, buffers, args.first(), "framep")?;
let cw = frames.get(fid).map(|f| f.char_width as i64).unwrap_or(8);
Ok(Value::fixnum(cw))
}
pub(crate) fn builtin_frame_native_height(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
eval.sync_pending_resize_events();
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("frame-native-height", &args, 1)?;
let fid = resolve_frame_id_in_state(frames, buffers, args.first(), "framep")?;
let frame = frames
.get(fid)
.ok_or_else(|| signal("error", vec![Value::string("Frame not found")]))?;
Ok(Value::fixnum(if frame_uses_window_system_pixels(frame) {
frame.height as i64
} else {
frame_total_lines(frame)
}))
}
pub(crate) fn builtin_frame_native_width(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
eval.sync_pending_resize_events();
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("frame-native-width", &args, 1)?;
let fid = resolve_frame_id_in_state(frames, buffers, args.first(), "framep")?;
let frame = frames
.get(fid)
.ok_or_else(|| signal("error", vec![Value::string("Frame not found")]))?;
let uses_window_system_pixels = frame_uses_window_system_pixels(frame);
if std::env::var("NEOMACS_TRACE_FRAME_GEOMETRY")
.ok()
.is_some_and(|value| value == "1")
{
tracing::debug!(
"frame-native-width: fid={:?} selected={:?} size={}x{} uses_pixels={} effective_ws={:?} param_ws={:?}",
fid,
frames.selected_frame().map(|selected| selected.id),
frame.width,
frame.height,
uses_window_system_pixels,
frame.effective_window_system(),
frame.parameters.get("window-system").copied()
);
}
Ok(Value::fixnum(if uses_window_system_pixels {
frame.width as i64
} else {
frame_total_cols(frame)
}))
}
pub(crate) fn builtin_frame_text_cols(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("frame-text-cols", &args, 1)?;
let fid = resolve_frame_id_in_state(frames, buffers, args.first(), "framep")?;
let frame = frames
.get(fid)
.ok_or_else(|| signal("error", vec![Value::string("Frame not found")]))?;
Ok(Value::fixnum(frame_total_cols(frame)))
}
pub(crate) fn builtin_frame_text_lines(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("frame-text-lines", &args, 1)?;
let fid = resolve_frame_id_in_state(frames, buffers, args.first(), "framep")?;
let frame = frames
.get(fid)
.ok_or_else(|| signal("error", vec![Value::string("Frame not found")]))?;
Ok(Value::fixnum(if frame_uses_window_system_pixels(frame) {
let char_height = frame.char_height.max(1.0);
((frame_text_height_pixels(frame) as f32) / char_height)
.floor()
.max(1.0) as i64
} else {
frame_text_lines(frame)
}))
}
pub(crate) fn builtin_frame_text_width(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("frame-text-width", &args, 1)?;
let fid = resolve_frame_id_in_state(frames, buffers, args.first(), "framep")?;
let frame = frames
.get(fid)
.ok_or_else(|| signal("error", vec![Value::string("Frame not found")]))?;
Ok(Value::fixnum(if frame_uses_window_system_pixels(frame) {
frame_text_width_pixels(frame) as i64
} else {
frame_text_cols(frame)
}))
}
pub(crate) fn builtin_frame_text_height(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("frame-text-height", &args, 1)?;
let fid = resolve_frame_id_in_state(frames, buffers, args.first(), "framep")?;
let frame = frames
.get(fid)
.ok_or_else(|| signal("error", vec![Value::string("Frame not found")]))?;
Ok(Value::fixnum(if frame_uses_window_system_pixels(frame) {
frame_text_height_pixels(frame) as i64
} else {
frame_text_lines(frame)
}))
}
pub(crate) fn builtin_frame_total_cols(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("frame-total-cols", &args, 1)?;
let fid = resolve_frame_id_in_state(frames, buffers, args.first(), "framep")?;
let frame = frames
.get(fid)
.ok_or_else(|| signal("error", vec![Value::string("Frame not found")]))?;
Ok(Value::fixnum(frame_total_cols(frame)))
}
pub(crate) fn builtin_frame_total_lines(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("frame-total-lines", &args, 1)?;
let fid = resolve_frame_id_in_state(frames, buffers, args.first(), "framep")?;
let frame = frames
.get(fid)
.ok_or_else(|| signal("error", vec![Value::string("Frame not found")]))?;
Ok(Value::fixnum(frame_total_lines(frame)))
}
pub(crate) fn builtin_frame_position(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("frame-position", &args, 1)?;
let _ = resolve_frame_id_in_state(frames, buffers, args.first(), "frame-live-p")?;
Ok(Value::cons(Value::fixnum(0), Value::fixnum(0)))
}
pub(crate) fn builtin_set_frame_height(
ctx: &mut crate::emacs_core::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_min_args("set-frame-height", &args, 2)?;
expect_max_args("set-frame-height", &args, 4)?;
let fid = resolve_frame_id_in_state(
&mut ctx.frames,
&mut ctx.buffers,
Some(&args[0]),
"frame-live-p",
)?;
let pretend = args.get(2).is_some_and(|v| v.is_truthy());
let pixelwise = args.get(3).is_some_and(|v| v.is_truthy());
let (current_text_width_px, char_height, uses_window_system_pixels) = {
let frame = &mut ctx
.frames
.get(fid)
.ok_or_else(|| signal("error", vec![Value::string("Frame not found")]))?;
(
frame_text_width_pixels(frame),
frame.char_height,
frame_uses_window_system_pixels(frame),
)
};
let text_height_px = check_frame_pixels(&args[1], pixelwise, char_height)?;
if uses_window_system_pixels {
resize_live_gui_frame(
&mut ctx.frames,
&mut ctx.display_host,
fid,
current_text_width_px,
text_height_px,
pretend,
)?;
} else {
let cols = {
let frame = &mut ctx
.frames
.get(fid)
.ok_or_else(|| signal("error", vec![Value::string("Frame not found")]))?;
frame_total_cols(frame)
};
let text_lines = ((text_height_px as f32) / char_height.max(1.0))
.floor()
.max(1.0) as i64;
let frame = &mut ctx
.frames
.get_mut(fid)
.ok_or_else(|| signal("error", vec![Value::string("Frame not found")]))?;
set_frame_text_size(frame, cols, text_lines);
}
Ok(Value::NIL)
}
pub(crate) fn builtin_set_frame_width(
ctx: &mut crate::emacs_core::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_min_args("set-frame-width", &args, 2)?;
expect_max_args("set-frame-width", &args, 4)?;
let fid = resolve_frame_id_in_state(
&mut ctx.frames,
&mut ctx.buffers,
Some(&args[0]),
"frame-live-p",
)?;
let pretend = args.get(2).is_some_and(|v| v.is_truthy());
let pixelwise = args.get(3).is_some_and(|v| v.is_truthy());
let (current_text_height_px, char_width, uses_window_system_pixels) = {
let frame = &mut ctx
.frames
.get(fid)
.ok_or_else(|| signal("error", vec![Value::string("Frame not found")]))?;
(
frame_text_height_pixels(frame),
frame.char_width,
frame_uses_window_system_pixels(frame),
)
};
let text_width_px = check_frame_pixels(&args[1], pixelwise, char_width)?;
if uses_window_system_pixels {
resize_live_gui_frame(
&mut ctx.frames,
&mut ctx.display_host,
fid,
text_width_px,
current_text_height_px,
pretend,
)?;
} else {
let text_lines = {
let frame = &mut ctx
.frames
.get(fid)
.ok_or_else(|| signal("error", vec![Value::string("Frame not found")]))?;
frame_text_lines(frame)
};
let cols = ((text_width_px as f32) / char_width.max(1.0))
.floor()
.max(1.0) as i64;
let frame = &mut ctx
.frames
.get_mut(fid)
.ok_or_else(|| signal("error", vec![Value::string("Frame not found")]))?;
set_frame_text_size(frame, cols, text_lines);
}
Ok(Value::NIL)
}
pub(crate) fn builtin_set_frame_size(
ctx: &mut crate::emacs_core::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_min_args("set-frame-size", &args, 3)?;
expect_max_args("set-frame-size", &args, 4)?;
let fid = resolve_frame_id_in_state(
&mut ctx.frames,
&mut ctx.buffers,
Some(&args[0]),
"frame-live-p",
)?;
let pixelwise = args.get(3).is_some_and(|v| v.is_truthy());
let (char_width, char_height, uses_window_system_pixels) = {
let frame = &mut ctx
.frames
.get(fid)
.ok_or_else(|| signal("error", vec![Value::string("Frame not found")]))?;
(
frame.char_width,
frame.char_height,
frame_uses_window_system_pixels(frame),
)
};
let text_width_px = check_frame_pixels(&args[1], pixelwise, char_width)?;
let text_height_px = check_frame_pixels(&args[2], pixelwise, char_height)?;
tracing::debug!(
"set-frame-size: fid={:?} pixelwise={} gui={} requested_text={}x{} char={}x{}",
fid,
pixelwise,
uses_window_system_pixels,
text_width_px,
text_height_px,
char_width,
char_height
);
if uses_window_system_pixels {
resize_live_gui_frame(
&mut ctx.frames,
&mut ctx.display_host,
fid,
text_width_px,
text_height_px,
false,
)?;
} else {
let cols = ((text_width_px as f32) / char_width.max(1.0))
.floor()
.max(1.0) as i64;
let text_lines = ((text_height_px as f32) / char_height.max(1.0))
.floor()
.max(1.0) as i64;
let frame = &mut ctx
.frames
.get_mut(fid)
.ok_or_else(|| signal("error", vec![Value::string("Frame not found")]))?;
tracing::debug!(
"set-frame-size: non-gui fallback fid={:?} cols={} text_lines={}",
fid,
cols,
text_lines
);
set_frame_text_size(frame, cols, text_lines);
}
Ok(Value::NIL)
}
pub(crate) fn builtin_set_frame_position(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_args("set-frame-position", &args, 3)?;
let _ = resolve_frame_id_in_state(frames, buffers, Some(&args[0]), "frame-live-p")?;
let _ = expect_int(&args[1])?;
let _ = expect_int(&args[2])?;
Ok(Value::T)
}
pub(crate) fn builtin_make_frame(eval: &mut super::eval::Context, args: Vec<Value>) -> EvalResult {
let backend = resolve_make_frame_backend_request(args.first(), eval.display_host.is_some());
tracing::debug!(
"builtin_make_frame: backend={backend:?} display_host_available={} args={:?}",
eval.display_host.is_some(),
args
);
if backend == MakeFrameBackend::Gui {
eval.sync_pending_resize_events();
}
let result = make_frame_with_state(
&mut eval.frames,
&mut eval.buffers,
&mut eval.display_host,
args,
);
eval.sync_keyboard_terminal_owner();
result
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum MakeFrameBackend {
Plain,
Gui,
}
fn resolve_make_frame_backend_request(
params: Option<&Value>,
display_host_available: bool,
) -> MakeFrameBackend {
let Some(params) = params else {
return if display_host_available {
MakeFrameBackend::Gui
} else {
MakeFrameBackend::Plain
};
};
let Some(items) = super::value::list_to_vec(params) else {
return if display_host_available {
MakeFrameBackend::Gui
} else {
MakeFrameBackend::Plain
};
};
let mut requested_window_system = None;
let mut requested_display = false;
let mut requested_terminal = false;
for item in items {
if !item.is_cons() {
continue;
};
let pair_car = item.cons_car();
let pair_cdr = item.cons_cdr();
let Some(key) = pair_car.as_symbol_name() else {
continue;
};
match key {
"window-system" => requested_window_system = Some(!pair_cdr.is_nil()),
"display" => requested_display = !pair_cdr.is_nil(),
"terminal" => requested_terminal = !pair_cdr.is_nil(),
_ => {}
}
}
if requested_terminal || matches!(requested_window_system, Some(false)) {
return MakeFrameBackend::Plain;
}
if requested_display || matches!(requested_window_system, Some(true)) {
return MakeFrameBackend::Gui;
}
if display_host_available {
MakeFrameBackend::Gui
} else {
MakeFrameBackend::Plain
}
}
pub(crate) fn make_frame_with_state(
frames: &mut FrameManager,
buffers: &mut BufferManager,
display_host: &mut Option<Box<dyn super::eval::DisplayHost>>,
args: Vec<Value>,
) -> EvalResult {
let backend = resolve_make_frame_backend_request(args.first(), display_host.is_some());
tracing::debug!(
"make_frame_with_state: backend={backend:?} display_host_available={} args={:?}",
display_host.is_some(),
args
);
if backend == MakeFrameBackend::Gui {
if display_host.is_none() {
return Err(signal(
"error",
vec![Value::string("GUI frame creation requires a display host")],
));
}
let gui_args = vec![args.first().copied().unwrap_or(Value::NIL)];
return x_create_frame_impl(frames, buffers, display_host, gui_args);
}
make_frame_plain(frames, buffers, args)
}
fn make_frame_plain(
frames: &mut FrameManager,
buffers: &mut BufferManager,
args: Vec<Value>,
) -> EvalResult {
expect_max_args("make-frame", &args, 1)?;
let mut width: u32 = 800;
let mut height: u32 = 600;
let mut name = String::from("F");
if let Some(params) = args.first() {
if let Some(items) = super::value::list_to_vec(params) {
for item in &items {
if item.is_cons() {
let pair_car = item.cons_car();
let pair_cdr = item.cons_cdr();
if let Some(key) = pair_car.as_symbol_id() {
match resolve_sym(key) {
"width" => {
if let Some(n) = pair_cdr.as_int() {
width = n as u32;
}
}
"height" => {
if let Some(n) = pair_cdr.as_int() {
height = n as u32;
}
}
"name" => {
if let Some(s) = pair_cdr.as_str() {
name = s.to_string();
}
}
_ => {}
}
}
}
}
}
}
let buf_id = buffers
.current_buffer()
.map(|b| b.id)
.unwrap_or(BufferId(0));
let fid = frames.create_frame(&name, width, height, buf_id);
tracing::debug!(
"make_frame_plain: created plain frame {:?} size={}x{} name={}",
fid,
width,
height,
name
);
Ok(Value::make_frame(fid.0))
}
#[derive(Default)]
struct ParsedGuiFrameParams {
name: Option<String>,
title: Option<String>,
width_columns: Option<u32>,
height_lines: Option<u32>,
visibility: Option<bool>,
all: std::collections::HashMap<String, Value>,
}
#[derive(Clone, Copy)]
struct GuiFrameMetrics {
width_px: u32,
height_px: u32,
char_width: f32,
char_height: f32,
font_pixel_size: f32,
minibuffer_height: f32,
}
fn stringish_value(value: &Value) -> Option<String> {
value
.as_str()
.map(ToOwned::to_owned)
.or_else(|| value.as_symbol_name().map(ToOwned::to_owned))
}
fn parse_gui_frame_params(value: Option<&Value>) -> ParsedGuiFrameParams {
let mut parsed = ParsedGuiFrameParams::default();
let Some(value) = value else {
return parsed;
};
let Some(items) = list_to_vec(value) else {
return parsed;
};
for item in items {
if !item.is_cons() {
continue;
};
let pair_car = item.cons_car();
let pair_cdr = item.cons_cdr();
let Some(key) = pair_car.as_symbol_name() else {
continue;
};
let key_name = key.to_string();
parsed.all.insert(key_name.clone(), pair_cdr);
match key {
"name" => parsed.name = stringish_value(&pair_cdr),
"title" => parsed.title = stringish_value(&pair_cdr),
"width" => {
if let Some(n) = pair_cdr.as_int() {
if n > 0 {
parsed.width_columns = Some(n as u32);
}
}
}
"height" => {
if let Some(n) = pair_cdr.as_int() {
if n > 0 {
parsed.height_lines = Some(n as u32);
}
}
}
"visibility" => parsed.visibility = Some(pair_cdr.is_truthy()),
_ => {}
}
}
parsed
}
fn current_gui_frame_metrics(eval: &super::eval::Context) -> GuiFrameMetrics {
current_gui_frame_metrics_in_state(&eval.frames)
}
fn current_gui_frame_metrics_in_state(frames: &FrameManager) -> GuiFrameMetrics {
if let Some(frame) = frames.selected_frame() {
let minibuffer_height = frame
.minibuffer_leaf
.as_ref()
.map(|leaf| leaf.bounds().height.max(frame.char_height).max(1.0))
.unwrap_or_else(|| (frame.char_height * 2.0).max(1.0));
return GuiFrameMetrics {
width_px: frame.width.max(1),
height_px: frame.height.max(minibuffer_height.ceil() as u32 + 1),
char_width: frame.char_width.max(1.0),
char_height: frame.char_height.max(1.0),
font_pixel_size: frame.font_pixel_size.max(1.0),
minibuffer_height,
};
}
GuiFrameMetrics {
width_px: 960,
height_px: 640,
char_width: 8.0,
char_height: 16.0,
font_pixel_size: 16.0,
minibuffer_height: 32.0,
}
}
fn current_primary_window_size(
display_host: &Option<Box<dyn super::eval::DisplayHost>>,
) -> Option<super::eval::GuiFrameHostSize> {
display_host
.as_ref()
.and_then(|host| host.current_primary_window_size())
.filter(|size| size.width > 0 && size.height > 0)
}
pub(crate) fn builtin_x_create_frame(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
tracing::debug!(
"builtin_x_create_frame: syncing pending resize events before frame realization"
);
eval.sync_pending_resize_events();
let result = x_create_frame_impl(
&mut eval.frames,
&mut eval.buffers,
&mut eval.display_host,
args,
);
eval.sync_keyboard_terminal_owner();
result
}
pub(crate) fn x_create_frame_impl(
frames: &mut FrameManager,
buffers: &mut BufferManager,
display_host: &mut Option<Box<dyn super::eval::DisplayHost>>,
args: Vec<Value>,
) -> EvalResult {
expect_args("x-create-frame", &args, 1)?;
let parsed = parse_gui_frame_params(args.first());
tracing::debug!(
"x_create_frame_impl: display_host_available={} params={:?}",
display_host.is_some(),
args.first()
);
let metrics = current_gui_frame_metrics_in_state(frames);
let host_size = current_primary_window_size(&*display_host);
let opening_frame_adoption = display_host
.as_ref()
.is_some_and(|host| host.opening_gui_frame_pending());
let width_px = parsed
.width_columns
.map(|cols| ((cols as f32 * metrics.char_width).round().max(1.0)) as u32)
.unwrap_or_else(|| host_size.map(|size| size.width).unwrap_or(metrics.width_px));
let text_height_px = parsed.height_lines.map(|lines| {
((lines as f32 * metrics.char_height)
.round()
.max(metrics.char_height)) as u32
});
let height_px = text_height_px
.map(|text| text.saturating_add(metrics.minibuffer_height.round() as u32))
.unwrap_or_else(|| {
host_size
.map(|size| size.height)
.unwrap_or(metrics.height_px)
});
tracing::debug!(
"x-create-frame: parsed width_cols={:?} height_lines={:?} host_size={:?} metrics={}x{} char={}x{} mini_h={} -> size={}x{}",
parsed.width_columns,
parsed.height_lines,
host_size,
metrics.width_px,
metrics.height_px,
metrics.char_width,
metrics.char_height,
metrics.minibuffer_height,
width_px,
height_px
);
let title = parsed
.title
.clone()
.or_else(|| parsed.name.clone())
.unwrap_or_else(|| "Neomacs".to_string());
let name = parsed.name.clone().unwrap_or_else(|| title.clone());
let current_buffer_id = buffers
.current_buffer()
.map(|buffer| buffer.id)
.unwrap_or_else(|| buffers.create_buffer("*scratch*"));
let minibuffer_buffer_id = buffers.find_buffer_by_name(" *Minibuf-0*");
let fid = frames.create_frame(&name, width_px, height_px, current_buffer_id);
{
let frame = frames
.get_mut(fid)
.ok_or_else(|| signal("error", vec![Value::string("Frame not found")]))?;
frame.name = name.clone();
frame.title = title.clone();
frame.width = width_px;
frame.height = height_px;
frame.visible = parsed.visibility.unwrap_or(frame.visible);
frame.char_width = metrics.char_width;
frame.char_height = metrics.char_height;
frame.font_pixel_size = metrics.font_pixel_size;
frame.set_window_system(Some(Value::symbol(
crate::emacs_core::display::gui_window_system_symbol(),
)));
frame
.parameters
.insert("display-type".to_string(), Value::symbol("color"));
frame
.parameters
.insert("background-mode".to_string(), Value::symbol("dark"));
for (key, value) in parsed.all {
frame.parameters.insert(key, value);
}
if let Window::Leaf { buffer_id, .. } = &mut frame.root_window {
*buffer_id = current_buffer_id;
}
if let Some(minibuffer_leaf) = frame.minibuffer_leaf.as_mut() {
if let Some(minibuffer_buffer_id) = minibuffer_buffer_id {
minibuffer_leaf.set_buffer(minibuffer_buffer_id);
}
minibuffer_leaf.set_bounds(Rect::new(
0.0,
0.0,
width_px as f32,
metrics.minibuffer_height.min(height_px as f32),
));
}
frame.sync_tab_bar_height_from_parameters();
}
if let Some(host) = display_host.as_mut() {
host.realize_gui_frame(super::eval::GuiFrameHostRequest {
frame_id: fid,
width: width_px,
height: height_px,
title,
})
.map_err(|message| signal("error", vec![Value::string(message)]))?;
}
if opening_frame_adoption {
frames.select_frame(fid);
if let Some(selected_wid) = frames.get(fid).map(|frame| frame.selected_window) {
let _ = frames.note_window_selected(selected_wid);
}
buffers.switch_current(current_buffer_id);
}
Ok(Value::make_frame(fid.0))
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum DeleteFrameMode {
Public { force_non_nil: bool },
Noelisp,
}
impl DeleteFrameMode {
fn runs_hooks_immediately(self) -> bool {
matches!(self, Self::Public { .. })
}
fn force_non_nil(self) -> bool {
match self {
Self::Public { force_non_nil } => force_non_nil,
Self::Noelisp => true,
}
}
fn bypasses_only_frame_check(self) -> bool {
matches!(self, Self::Noelisp)
}
fn allows_terminal_cascade(self) -> bool {
matches!(self, Self::Public { .. })
}
}
fn other_frames_in_state(
eval: &super::eval::Context,
deleting: crate::window::FrameId,
include_invisible: bool,
) -> bool {
eval.frames
.frame_list()
.into_iter()
.filter(|frame_id| *frame_id != deleting)
.any(|frame_id| {
eval.frames
.get(frame_id)
.is_some_and(|frame| include_invisible || frame.visible)
})
}
pub(crate) fn delete_frame_owned(
eval: &mut super::eval::Context,
fid: crate::window::FrameId,
mode: DeleteFrameMode,
) -> EvalResult {
if eval.frames.get(fid).is_none() {
return Ok(Value::NIL);
}
let terminal_id = eval
.frames
.get(fid)
.map(|frame| frame.terminal_id)
.unwrap_or(crate::emacs_core::terminal::pure::TERMINAL_ID);
let frame_value = Value::make_frame(fid.0);
if mode.runs_hooks_immediately() {
let delete_hook =
crate::emacs_core::hook_runtime::hook_symbol_by_name(eval, "delete-frame-functions");
let _ = crate::emacs_core::hook_runtime::safe_run_named_hook(
eval,
delete_hook,
&[frame_value],
)?;
} else {
eval.queue_pending_safe_hook("delete-frame-functions", &[frame_value]);
}
if eval.frames.get(fid).is_none() {
return Ok(Value::NIL);
}
let force_non_nil = mode.force_non_nil();
if !mode.bypasses_only_frame_check() && !other_frames_in_state(eval, fid, force_non_nil) {
return Err(signal(
"error",
vec![Value::string(if force_non_nil {
"Attempt to delete the only frame"
} else {
"Attempt to delete the sole visible or iconified frame"
})],
));
}
if !eval.frames.delete_frame(fid) {
return Err(signal("error", vec![Value::string("Cannot delete frame")]));
}
let terminal_is_empty = eval.frames.frame_list().into_iter().all(|frame_id| {
eval.frames
.get(frame_id)
.is_none_or(|frame| frame.terminal_id != terminal_id)
});
if mode.allows_terminal_cascade() && terminal_is_empty && !eval.frames.frame_list().is_empty() {
if let Some(terminal) =
crate::emacs_core::terminal::pure::terminal_handle_value_for_id(terminal_id)
{
let _ = crate::emacs_core::terminal::pure::delete_terminal_owned(
eval,
crate::emacs_core::terminal::pure::terminal_handle_id(&terminal)
.expect("live terminal handle id"),
crate::emacs_core::terminal::pure::DeleteTerminalMode::Public {
force_non_nil: true,
},
)?;
}
}
eval.sync_keyboard_terminal_owner();
if mode.runs_hooks_immediately() {
let after_delete_hook = crate::emacs_core::hook_runtime::hook_symbol_by_name(
eval,
"after-delete-frame-functions",
);
let _ = crate::emacs_core::hook_runtime::safe_run_named_hook(
eval,
after_delete_hook,
&[frame_value],
)?;
} else {
eval.queue_pending_safe_hook("after-delete-frame-functions", &[frame_value]);
}
Ok(Value::NIL)
}
pub(crate) fn builtin_delete_frame(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_max_args("delete-frame", &args, 2)?;
let fid = {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
resolve_frame_id_in_state(frames, buffers, args.first(), "framep")?
};
let force_non_nil = args.get(1).copied().unwrap_or(Value::NIL).is_truthy();
delete_frame_owned(eval, fid, DeleteFrameMode::Public { force_non_nil })
}
pub(crate) fn builtin_frame_window_state_change(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_max_args("frame-window-state-change", &args, 1)?;
let fid = resolve_frame_id(eval, args.first(), "frame-live-p")?;
Ok(Value::bool_val(
eval.frames
.get(fid)
.is_some_and(|frame| frame.window_state_change),
))
}
pub(crate) fn builtin_set_frame_window_state_change(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_max_args("set-frame-window-state-change", &args, 2)?;
let fid = resolve_frame_id(eval, args.first(), "frame-live-p")?;
let state = args.get(1).copied().unwrap_or(Value::NIL).is_truthy();
let frame = eval.frames.get_mut(fid).ok_or_else(|| {
signal(
"wrong-type-argument",
vec![
Value::symbol("frame-live-p"),
args.first().copied().unwrap_or(Value::NIL),
],
)
})?;
frame.window_state_change = state;
Ok(Value::bool_val(state))
}
pub(crate) fn builtin_frame_parameter(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_min_args("frame-parameter", &args, 2)?;
expect_max_args("frame-parameter", &args, 2)?;
let fid = resolve_frame_id(eval, Some(&args[0]), "framep")?;
let param_name = match args[1].kind() {
ValueKind::Symbol(id) => resolve_sym(id).to_owned(),
_ => return Ok(Value::NIL),
};
let frame = eval
.frames
.get(fid)
.ok_or_else(|| signal("error", vec![Value::string("Frame not found")]))?;
match param_name.as_str() {
"name" => return Ok(Value::string(frame.name.clone())),
"title" => return Ok(Value::string(frame.title.clone())),
"width" => {
return Ok(frame
.parameters
.get("width")
.cloned()
.unwrap_or(Value::fixnum(frame.columns() as i64)));
}
"height" => {
return Ok(frame
.parameters
.get("height")
.cloned()
.unwrap_or(Value::fixnum(frame.lines() as i64)));
}
"visibility" => {
return Ok(if frame.visible { Value::T } else { Value::NIL });
}
_ => {}
}
Ok(frame
.parameters
.get(¶m_name)
.cloned()
.unwrap_or(Value::NIL))
}
pub(crate) fn builtin_frame_parameters(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("frame-parameters", &args, 1)?;
let fid = resolve_frame_id_in_state(frames, buffers, args.first(), "framep")?;
let frame = frames
.get(fid)
.ok_or_else(|| signal("error", vec![Value::string("Frame not found")]))?;
let mut pairs: Vec<Value> = Vec::new();
pairs.push(Value::cons(
Value::symbol("name"),
Value::string(frame.name.clone()),
));
pairs.push(Value::cons(
Value::symbol("title"),
Value::string(frame.title.clone()),
));
let width = frame
.parameters
.get("width")
.cloned()
.unwrap_or(Value::fixnum(frame.columns() as i64));
let height = frame
.parameters
.get("height")
.cloned()
.unwrap_or(Value::fixnum(frame.lines() as i64));
pairs.push(Value::cons(Value::symbol("width"), width));
pairs.push(Value::cons(Value::symbol("height"), height));
pairs.push(Value::cons(
Value::symbol("visibility"),
Value::bool_val(frame.visible),
));
for (k, v) in &frame.parameters {
pairs.push(Value::cons(Value::symbol(k.clone()), *v));
}
Ok(Value::list(pairs))
}
pub(crate) fn builtin_modify_frame_parameters(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_min_args("modify-frame-parameters", &args, 2)?;
expect_max_args("modify-frame-parameters", &args, 2)?;
let fid = resolve_frame_id_in_state(
&mut eval.frames,
&mut eval.buffers,
Some(&args[0]),
"frame-live-p",
)?;
let items = super::value::list_to_vec(&args[1]).unwrap_or_default();
if eval.frames.get(fid).is_none() {
return Err(signal("error", vec![Value::string("Frame not found")]));
}
for item in items.into_iter().rev() {
if item.is_cons() {
let pair_car = item.cons_car();
let pair_cdr = item.cons_cdr();
if let Some(key) = pair_car.as_symbol_id() {
let key_name = resolve_sym(key).to_owned();
match key_name.as_str() {
"name" => {
if let Some(s) = pair_cdr.as_str() {
if let Some(frame) = eval.frames.get_mut(fid) {
frame.name = s.to_string();
}
}
}
"title" => {
if let Some(s) = pair_cdr.as_str() {
if let Some(frame) = eval.frames.get_mut(fid) {
frame.title = s.to_string();
}
}
}
"width" => {
if let Some(n) = pair_cdr.as_int() {
if let Some(frame) = eval.frames.get_mut(fid) {
frame
.parameters
.insert("width".to_string(), Value::fixnum(n));
}
}
}
"height" => {
if let Some(n) = pair_cdr.as_int() {
if let Some(frame) = eval.frames.get_mut(fid) {
frame
.parameters
.insert("height".to_string(), Value::fixnum(n));
}
}
}
"visibility" => {
if let Some(frame) = eval.frames.get_mut(fid) {
frame.visible = pair_cdr.is_truthy();
}
}
_ => {
if let Some(frame) = eval.frames.get_mut(fid) {
frame.parameters.insert(key_name.clone(), pair_cdr);
}
if matches!(key_name.as_str(), "foreground-color" | "background-color") {
super::font::update_face_from_frame_parameter(
eval, fid, &key_name, pair_cdr,
)?;
}
}
}
}
}
}
if let Some(frame) = eval.frames.get_mut(fid) {
frame.sync_tab_bar_height_from_parameters();
}
Ok(Value::NIL)
}
pub(crate) fn builtin_frame_visible_p(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let frames = &eval.frames;
expect_args("frame-visible-p", &args, 1)?;
let val = args.first().unwrap(); let fid = match val.kind() {
ValueKind::Fixnum(n) => FrameId(n as u64),
ValueKind::Veclike(VecLikeType::Frame) => FrameId(val.as_frame_id().unwrap()),
_ => {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("frame-live-p"), *val],
));
}
};
let frame = frames.get(fid).ok_or_else(|| {
signal(
"wrong-type-argument",
vec![Value::symbol("frame-live-p"), args[0]],
)
})?;
Ok(Value::bool_val(frame.visible))
}
pub(crate) fn builtin_framep(eval: &mut super::eval::Context, args: Vec<Value>) -> EvalResult {
expect_args("framep", &args, 1)?;
let id = match args[0].kind() {
ValueKind::Veclike(VecLikeType::Frame) => args[0].as_frame_id().unwrap(),
ValueKind::Fixnum(n) => n as u64,
_ => return Ok(Value::NIL),
};
let Some(frame) = eval.frames.get(FrameId(id)) else {
return Ok(Value::NIL);
};
Ok(frame
.parameters
.get("window-system")
.copied()
.unwrap_or(Value::T))
}
pub(crate) fn builtin_frame_live_p(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let frames = &eval.frames;
expect_args("frame-live-p", &args, 1)?;
let id = match args[0].kind() {
ValueKind::Veclike(VecLikeType::Frame) => args[0].as_frame_id().unwrap(),
ValueKind::Fixnum(n) => n as u64,
_ => return Ok(Value::NIL),
};
Ok(Value::bool_val(frames.get(FrameId(id)).is_some()))
}
pub fn register_bootstrap_vars(obarray: &mut crate::emacs_core::symbol::Obarray) {
use crate::emacs_core::value::Value;
obarray.set_symbol_value(
"window-persistent-parameters",
Value::list(vec![Value::cons(Value::symbol("clone-of"), Value::T)]),
);
obarray.set_symbol_value("recenter-redisplay", Value::symbol("tty"));
obarray.set_symbol_value("window-combination-resize", Value::NIL);
obarray.set_symbol_value("window-combination-limit", Value::NIL);
obarray.set_symbol_value("delete-frame-functions", Value::NIL);
obarray.set_symbol_value("after-delete-frame-functions", Value::NIL);
obarray.set_symbol_value("window-buffer-change-functions", Value::NIL);
obarray.set_symbol_value("window-size-change-functions", Value::NIL);
obarray.set_symbol_value("window-selection-change-functions", Value::NIL);
obarray.set_symbol_value("window-state-change-functions", Value::NIL);
obarray.set_symbol_value("window-state-change-hook", Value::NIL);
obarray.set_symbol_value("window-sides-vertical", Value::NIL);
obarray.set_symbol_value("window-sides-slots", Value::NIL);
obarray.set_symbol_value("window-resize-pixelwise", Value::NIL);
obarray.set_symbol_value("fit-window-to-buffer-horizontally", Value::NIL);
obarray.set_symbol_value("fit-frame-to-buffer", Value::NIL);
obarray.set_symbol_value(
"fit-frame-to-buffer-margins",
Value::list(vec![
Value::fixnum(0),
Value::fixnum(0),
Value::fixnum(0),
Value::fixnum(0),
]),
);
obarray.set_symbol_value("fit-frame-to-buffer-sizes", Value::NIL);
obarray.set_symbol_value("window-min-height", Value::fixnum(4));
obarray.set_symbol_value("window-min-width", Value::fixnum(10));
obarray.set_symbol_value("window-safe-min-height", Value::fixnum(1));
obarray.set_symbol_value("window-safe-min-width", Value::fixnum(2));
obarray.set_symbol_value("scroll-preserve-screen-position", Value::NIL);
obarray.set_symbol_value("window-point-insertion-type", Value::NIL);
obarray.set_symbol_value("next-screen-context-lines", Value::fixnum(2));
obarray.set_symbol_value("fast-but-imprecise-scrolling", Value::NIL);
obarray.set_symbol_value("scroll-error-top-bottom", Value::NIL);
obarray.set_symbol_value(
"temp-buffer-max-height",
Value::make_float(1.0 / 3.0), );
obarray.set_symbol_value("temp-buffer-max-width", Value::NIL);
obarray.set_symbol_value("even-window-sizes", Value::symbol("width-only"));
obarray.set_symbol_value("auto-window-vscroll", Value::T);
}
pub(crate) fn builtin_window_combination_limit(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_args("window-combination-limit", &args, 1)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-valid-p")?;
let w = get_window(frames, fid, wid)?;
match w.combination_limit() {
Some(true) => Ok(Value::T),
Some(false) => Ok(Value::NIL),
None => Err(signal(
"error",
vec![Value::string(
"Combination limit is meaningful for internal windows only",
)],
)),
}
}
pub(crate) fn builtin_set_window_combination_limit(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_args("set-window-combination-limit", &args, 2)?;
let _ = ensure_selected_frame_id_in_state(frames, buffers);
let (fid, wid) =
resolve_window_id_with_pred_in_state(frames, buffers, args.first(), "window-valid-p")?;
let limit = args[1].is_truthy();
let frame = frames
.get_mut(fid)
.ok_or_else(|| signal("error", vec![Value::string("Frame not found")]))?;
let w = frame
.find_window_mut(wid)
.ok_or_else(|| signal("error", vec![Value::string("Window not found")]))?;
if w.is_leaf() {
return Err(signal(
"error",
vec![Value::string(
"Combination limit is meaningful for internal windows only",
)],
));
}
w.set_combination_limit(limit);
Ok(args[1])
}
pub(crate) fn builtin_window_resize_apply(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("window-resize-apply", &args, 2)?;
let fid = resolve_frame_id_in_state(frames, buffers, args.first(), "frame-live-p")?;
let horflag = args.get(1).is_some_and(|v| v.is_truthy());
let new_pixel_map = super::builtins::snapshot_window_new_pixel();
let new_normal_map = super::builtins::snapshot_window_new_normal();
let frame = frames
.get_mut(fid)
.ok_or_else(|| signal("error", vec![Value::string("Frame not found")]))?;
let cw = frame.char_width;
let ch = frame.char_height;
if !crate::window::window_resize_check(&frame.root_window, horflag, &new_pixel_map) {
return Ok(Value::NIL);
}
let root_id = frame.root_window.id().0;
let root_new = new_pixel_map.get(&root_id).copied().unwrap_or_else(|| {
let b = frame.root_window.bounds();
if horflag {
b.width as i64
} else {
b.height as i64
}
});
let frame_dim = if horflag {
frame.root_window.bounds().width as i64
} else {
frame.root_window.bounds().height as i64
};
if root_new != frame_dim {
return Ok(Value::NIL);
}
crate::window::window_resize_apply(
&mut frame.root_window,
horflag,
&new_pixel_map,
&new_normal_map,
cw,
ch,
);
frame.recalculate_minibuffer_bounds();
Ok(Value::T)
}
pub(crate) fn builtin_window_resize_apply_total(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (frames, buffers) = (&mut eval.frames, &mut eval.buffers);
expect_max_args("window-resize-apply-total", &args, 2)?;
let fid = resolve_frame_id_in_state(frames, buffers, args.first(), "frame-live-p")?;
let horflag = args.get(1).is_some_and(|v| v.is_truthy());
let new_total_map = super::builtins::snapshot_window_new_total();
let frame = frames
.get_mut(fid)
.ok_or_else(|| signal("error", vec![Value::string("Frame not found")]))?;
let cw = frame.char_width;
let ch = frame.char_height;
crate::window::window_resize_apply_total(
&mut frame.root_window,
horflag,
&new_total_map,
cw,
ch,
);
if !horflag {
if let Some(mb_wid) = frame.minibuffer_window {
if let Some(mb) = frame.minibuffer_leaf.as_mut() {
if let Some(&new_total) = new_total_map.get(&mb_wid.0) {
let root_bounds = *frame.root_window.bounds();
let mb_top = root_bounds.y + root_bounds.height;
let mb_bounds = *mb.bounds();
let new_h = new_total.max(0) as f32 * ch;
mb.set_bounds(crate::window::Rect::new(
mb_bounds.x,
mb_top,
mb_bounds.width,
new_h,
));
}
}
}
}
frame.recalculate_minibuffer_bounds();
Ok(Value::T)
}
pub(crate) fn builtin_balance_windows(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_max_args("balance-windows", &args, 1)?;
let fid = ensure_selected_frame_id(eval);
let frame = eval
.frames
.get_mut(fid)
.ok_or_else(|| signal("error", vec![Value::string("No frame")]))?;
fn balance_subtree(window: &mut Window) {
if let Window::Internal {
direction,
children,
bounds,
..
} = window
{
let parent_bounds = *bounds;
let n = children.len() as f32;
if n < 1.0 {
return;
}
match direction {
SplitDirection::Horizontal => {
let w = parent_bounds.width / n;
for (i, child) in children.iter_mut().enumerate() {
child.set_bounds(Rect::new(
parent_bounds.x + i as f32 * w,
parent_bounds.y,
w,
parent_bounds.height,
));
}
}
SplitDirection::Vertical => {
let h = parent_bounds.height / n;
for (i, child) in children.iter_mut().enumerate() {
child.set_bounds(Rect::new(
parent_bounds.x,
parent_bounds.y + i as f32 * h,
parent_bounds.width,
h,
));
}
}
}
for child in children.iter_mut() {
balance_subtree(child);
}
}
}
balance_subtree(&mut frame.root_window);
frame.recalculate_minibuffer_bounds();
Ok(Value::NIL)
}
const MIN_WINDOW_PIXEL_SIZE: f32 = 1.0;
pub(crate) fn builtin_enlarge_window(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_min_args("enlarge-window", &args, 1)?;
expect_max_args("enlarge-window", &args, 2)?;
let delta = expect_int(&args[0])?;
let horizontal = args.get(1).is_some_and(|v| !v.is_nil());
resize_selected_window(eval, delta, horizontal, "enlarge-window")
}
pub(crate) fn builtin_shrink_window(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_min_args("shrink-window", &args, 1)?;
expect_max_args("shrink-window", &args, 2)?;
let delta = expect_int(&args[0])?;
let horizontal = args.get(1).is_some_and(|v| !v.is_nil());
resize_selected_window(eval, -delta, horizontal, "shrink-window")
}
fn resize_selected_window(
eval: &mut super::eval::Context,
delta: i64,
horizontal: bool,
name: &str,
) -> EvalResult {
if delta == 0 {
return Ok(Value::NIL);
}
let fid = ensure_selected_frame_id(eval);
let frame = eval
.frames
.get_mut(fid)
.ok_or_else(|| signal("error", vec![Value::string("No frame")]))?;
let sel_wid = frame.selected_window;
let char_size = if horizontal {
frame.char_width.max(1.0)
} else {
frame.char_height.max(1.0)
};
let delta_px = delta as f32 * char_size;
fn resize_in_tree(
node: &mut Window,
target: WindowId,
delta_px: f32,
horizontal: bool,
) -> bool {
let Window::Internal {
direction,
children,
..
} = node
else {
return false;
};
if let Some(target_idx) = children.iter().position(|c| c.id() == target) {
let dir_matches = (*direction == SplitDirection::Horizontal) == horizontal;
if !dir_matches || children.len() < 2 {
return false;
}
let sibling_count = (children.len() - 1) as f32;
let shrink_each = delta_px / sibling_count;
for (i, child) in children.iter().enumerate() {
if i == target_idx {
continue;
}
let dim = if horizontal {
child.bounds().width
} else {
child.bounds().height
};
if dim - shrink_each < MIN_WINDOW_PIXEL_SIZE {
}
let _ = dim; }
let sizes: Vec<f32> = children
.iter()
.enumerate()
.map(|(i, child)| {
let dim = if horizontal {
child.bounds().width
} else {
child.bounds().height
};
if i == target_idx {
(dim + delta_px).max(MIN_WINDOW_PIXEL_SIZE)
} else {
(dim - shrink_each).max(MIN_WINDOW_PIXEL_SIZE)
}
})
.collect();
let parent_bounds = *node.bounds();
if let Window::Internal { children, .. } = node {
let mut edge = if horizontal {
parent_bounds.x
} else {
parent_bounds.y
};
for (i, child) in children.iter_mut().enumerate() {
let b = *child.bounds();
if horizontal {
child.set_bounds(Rect::new(edge, b.y, sizes[i], b.height));
edge += sizes[i];
} else {
child.set_bounds(Rect::new(b.x, edge, b.width, sizes[i]));
edge += sizes[i];
}
}
}
return true;
}
for child in children.iter_mut() {
if resize_in_tree(child, target, delta_px, horizontal) {
return true;
}
}
false
}
if !resize_in_tree(&mut frame.root_window, sel_wid, delta_px, horizontal) {
return Err(signal(
"error",
vec![Value::string(format!(
"{name}: cannot resize the only window"
))],
));
}
frame.recalculate_minibuffer_bounds();
Ok(Value::NIL)
}
pub(crate) fn builtin_window_tree(eval: &mut super::eval::Context, args: Vec<Value>) -> EvalResult {
expect_max_args("window-tree", &args, 1)?;
let fid = resolve_frame_id(eval, args.first(), "frame-live-p")?;
let frame = eval
.frames
.get(fid)
.ok_or_else(|| signal("error", vec![Value::string("No frame")]))?;
fn window_tree_to_value(window: &Window) -> Value {
match window {
Window::Leaf { id, .. } => Value::make_window(id.0),
Window::Internal {
direction,
children,
bounds,
..
} => {
let horizontal_p = if *direction == SplitDirection::Horizontal {
Value::T
} else {
Value::NIL
};
let top = Value::fixnum(bounds.y as i64);
let left = Value::fixnum(bounds.x as i64);
let right = Value::fixnum(bounds.right() as i64);
let bottom = Value::fixnum(bounds.bottom() as i64);
let mut elts = vec![horizontal_p, top, left, right, bottom];
for child in children {
elts.push(window_tree_to_value(child));
}
Value::list(elts)
}
}
}
let tree = window_tree_to_value(&frame.root_window);
let mini = frame
.minibuffer_window
.map(|wid| Value::make_window(wid.0))
.unwrap_or(Value::NIL);
Ok(Value::cons(tree, mini))
}
pub(crate) fn builtin_fit_window_to_buffer(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_max_args("fit-window-to-buffer", &args, 6)?;
let (fid, wid) = resolve_window_id_or_error(eval, args.first())?;
let (buf_id, bounds) = {
let w = get_leaf(&eval.frames, fid, wid)?;
match w {
Window::Leaf {
buffer_id, bounds, ..
} => (*buffer_id, bounds.clone()),
_ => return Ok(Value::NIL),
}
};
let buf = match eval.buffers.get(buf_id) {
Some(b) => b,
None => return Ok(Value::NIL),
};
let text = buf.text.to_string();
let buf_lines = text.chars().filter(|&c| c == '\n').count() + 1;
let parse_opt_int = |idx: usize| -> Option<usize> {
args.get(idx)
.and_then(|v| v.as_fixnum())
.filter(|&n| n > 0)
.map(|n| n as usize)
};
let max_height = parse_opt_int(1);
let min_height = parse_opt_int(2);
let mut desired = buf_lines;
if let Some(max_h) = max_height {
desired = desired.min(max_h);
}
if let Some(min_h) = min_height {
desired = desired.max(min_h);
}
let ch = eval
.frames
.get(fid)
.map(|f| f.char_height.max(1.0))
.unwrap_or(16.0);
let new_pixel_height = (desired as f32 * ch) + ch; let current_height = bounds.height;
if (new_pixel_height - current_height).abs() > 0.5 {
if let Some(frame) = eval.frames.get_mut(fid) {
if let Some(Window::Leaf { bounds, .. }) = frame.find_window_mut(wid) {
bounds.height = new_pixel_height;
}
}
}
Ok(Value::NIL)
}
#[cfg(test)]
#[path = "../window_cmds_test.rs"]
mod tests;