use crate::buffer::BufferId;
use crate::emacs_core::value::{HashTableTest, Value};
use crate::face::Face as RuntimeFace;
use crate::gc_trace::GcTrace;
use std::collections::{HashMap, HashSet};
mod display;
mod history;
mod parameters;
pub use display::WindowBufferDisplayDefaults;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct WindowId(pub u64);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct FrameId(pub u64);
pub(crate) const FRAME_ID_BASE: u64 = 1 << 32;
pub(crate) const MINIBUFFER_WINDOW_ID_BASE: u64 = 1 << 48;
pub const DEFAULT_TOOL_BAR_LABEL_SIZE: f32 = 14.0;
pub const DEFAULT_TOOL_BAR_BUTTON_MARGIN: f32 = 4.0;
pub const DEFAULT_TOOL_BAR_BUTTON_RELIEF: f32 = 1.0;
pub const DEFAULT_TOOL_BAR_IMAGE_HEIGHT: f32 = 24.0;
pub fn default_gui_tool_bar_line_height(font_pixel_size: f32) -> u32 {
let scale = if font_pixel_size.is_finite() && font_pixel_size > 0.0 {
(font_pixel_size / DEFAULT_TOOL_BAR_LABEL_SIZE).max(1.0)
} else {
1.0
};
let image_height = (DEFAULT_TOOL_BAR_IMAGE_HEIGHT * scale).round().max(1.0);
let margin = (DEFAULT_TOOL_BAR_BUTTON_MARGIN * scale).round().max(0.0);
let relief = (DEFAULT_TOOL_BAR_BUTTON_RELIEF * scale).round().max(1.0);
(image_height + 2.0 * margin + 2.0 * relief)
.round()
.max(1.0) as u32
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Rect {
pub x: f32,
pub y: f32,
pub width: f32,
pub height: f32,
}
impl Rect {
pub fn new(x: f32, y: f32, width: f32, height: f32) -> Self {
Self {
x,
y,
width,
height,
}
}
pub fn right(&self) -> f32 {
self.x + self.width
}
pub fn bottom(&self) -> f32 {
self.y + self.height
}
pub fn contains(&self, px: f32, py: f32) -> bool {
px >= self.x && px < self.right() && py >= self.y && py < self.bottom()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SplitDirection {
Horizontal, Vertical, }
#[derive(Clone, Debug)]
pub struct WindowDisplayState {
pub display_table: Value,
pub cursor_type: Value,
pub cursor: Option<WindowCursorPos>,
pub phys_cursor: Option<WindowCursorSnapshot>,
pub output_cursor: Option<WindowCursorPos>,
pub phys_cursor_type: WindowCursorKind,
pub phys_cursor_on_p: bool,
pub cursor_off_p: bool,
pub last_cursor_off_p: bool,
pub last_cursor_vpos: i64,
pub left_fringe_width: i32,
pub right_fringe_width: i32,
pub fringes_outside_margins: bool,
pub fringes_persistent: bool,
pub scroll_bar_width: i32,
pub vertical_scroll_bar_type: Value,
pub scroll_bar_height: i32,
pub horizontal_scroll_bar_type: Value,
pub scroll_bars_persistent: bool,
}
impl Default for WindowDisplayState {
fn default() -> Self {
Self {
display_table: Value::NIL,
cursor_type: Value::T,
cursor: None,
phys_cursor: None,
output_cursor: None,
phys_cursor_type: WindowCursorKind::NoCursor,
phys_cursor_on_p: false,
cursor_off_p: false,
last_cursor_off_p: false,
last_cursor_vpos: 0,
left_fringe_width: -1,
right_fringe_width: -1,
fringes_outside_margins: false,
fringes_persistent: false,
scroll_bar_width: -1,
vertical_scroll_bar_type: Value::T,
scroll_bar_height: -1,
horizontal_scroll_bar_type: Value::T,
scroll_bars_persistent: false,
}
}
}
impl WindowDisplayState {
pub fn clear_cursor_state(&mut self) {
self.cursor = None;
self.clear_output_cursor_state();
self.clear_physical_cursor_state();
}
fn begin_output_pass(&mut self) {
self.cursor = None;
self.clear_physical_cursor_state();
}
fn begin_window_output_update(&mut self) {
self.begin_output_pass();
self.clear_output_cursor_state();
}
fn clear_output_cursor_state(&mut self) {
self.output_cursor = None;
}
fn clear_physical_cursor_state(&mut self) {
self.phys_cursor = None;
self.phys_cursor_type = WindowCursorKind::NoCursor;
self.phys_cursor_on_p = false;
}
fn install_logical_cursor(&mut self, cursor: Option<WindowCursorPos>) {
self.cursor = cursor;
}
fn output_cursor_to(&mut self, pos: WindowCursorPos) {
self.output_cursor = Some(pos);
}
fn apply_physical_cursor_snapshot(&mut self, cursor: Option<WindowCursorSnapshot>) {
self.phys_cursor = cursor.clone();
self.phys_cursor_type = cursor
.as_ref()
.map(|c| c.kind)
.unwrap_or(WindowCursorKind::NoCursor);
self.phys_cursor_on_p = cursor.is_some();
}
fn commit_completed_redisplay(&mut self) {
self.last_cursor_off_p = self.cursor_off_p;
if let Some(cursor) = self.phys_cursor.as_ref() {
self.last_cursor_vpos = cursor.row;
} else if let Some(cursor) = self.cursor.as_ref() {
self.last_cursor_vpos = cursor.row;
}
}
}
pub struct WindowOutputUpdate<'a> {
display: &'a mut WindowDisplayState,
}
impl<'a> WindowOutputUpdate<'a> {
fn new(display: &'a mut WindowDisplayState) -> Self {
Self { display }
}
pub fn begin_update(&mut self) {
self.display.begin_window_output_update();
}
pub fn output_cursor_to(&mut self, pos: WindowCursorPos) {
self.display.output_cursor_to(pos);
}
pub fn output_cursor_to_coords(&mut self, row: i64, col: i64, y: i64, x: i64) {
self.output_cursor_to(WindowCursorPos { x, y, row, col });
}
fn replay_output_rows(&mut self, rows: &[DisplayRowSnapshot]) {
if rows.is_empty() {
self.display.clear_output_cursor_state();
return;
}
for row in rows {
self.output_cursor_to_coords(row.row, row.start_col, row.y, row.start_x);
self.output_cursor_to_coords(row.row, row.end_col, row.y, row.end_x);
}
}
pub fn install_logical_cursor(&mut self, cursor: Option<WindowCursorPos>) {
self.display.install_logical_cursor(cursor);
}
pub fn apply_physical_cursor_snapshot(&mut self, cursor: Option<WindowCursorSnapshot>) {
self.display.apply_physical_cursor_snapshot(cursor);
}
fn fallback_output_cursor_from_snapshot(&mut self, snapshot: &WindowDisplaySnapshot) {
if self.display.output_cursor.is_none() {
self.replay_output_rows(&snapshot.rows);
}
}
pub fn finalize_live_update(
&mut self,
logical_cursor: Option<WindowCursorPos>,
phys_cursor: Option<WindowCursorSnapshot>,
) {
self.install_logical_cursor(logical_cursor);
self.apply_physical_cursor_snapshot(phys_cursor);
self.commit();
}
pub fn finalize_with_output_fallback(
&mut self,
logical_cursor: Option<WindowCursorPos>,
phys_cursor: Option<WindowCursorSnapshot>,
output_fallback: &WindowDisplaySnapshot,
) {
self.install_logical_cursor(logical_cursor);
self.apply_physical_cursor_snapshot(phys_cursor);
self.fallback_output_cursor_from_snapshot(output_fallback);
self.commit();
}
pub fn replay_snapshot(&mut self, snapshot: &WindowDisplaySnapshot) {
self.begin_update();
self.install_logical_cursor(snapshot.logical_cursor_pos());
self.replay_output_rows(&snapshot.rows);
self.apply_physical_cursor_snapshot(snapshot.phys_cursor.clone());
self.commit();
}
pub fn commit(&mut self) {
self.display.commit_completed_redisplay();
}
}
#[derive(Clone, Debug)]
pub struct WindowHistoryState {
pub prev_buffers: Value,
pub next_buffers: Value,
pub use_time: i64,
}
impl Default for WindowHistoryState {
fn default() -> Self {
Self {
prev_buffers: Value::NIL,
next_buffers: Value::NIL,
use_time: 0,
}
}
}
pub(crate) type WindowParameters = Vec<(Value, Value)>;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct WindowRedisplayState {
pub id: WindowId,
pub buffer_id: BufferId,
pub bounds: (u32, u32, u32, u32),
pub window_start: usize,
pub window_end_pos: usize,
pub window_end_bytepos: usize,
pub window_end_vpos: usize,
pub window_end_valid: bool,
pub point: usize,
pub old_point: usize,
pub hscroll: usize,
pub vscroll: i32,
pub preserve_vscroll_p: bool,
}
fn redisplay_f32_bits(value: f32) -> u32 {
if value == 0.0 {
0.0f32.to_bits()
} else {
value.to_bits()
}
}
#[derive(Clone, Debug)]
pub enum Window {
Leaf {
id: WindowId,
buffer_id: BufferId,
bounds: Rect,
window_start: usize,
window_end_pos: usize,
window_end_bytepos: usize,
window_end_vpos: usize,
window_end_valid: bool,
point: usize,
old_point: usize,
dedicated: bool,
parameters: WindowParameters,
history: WindowHistoryState,
fixed_height: usize,
fixed_width: usize,
hscroll: usize,
vscroll: i32,
preserve_vscroll_p: bool,
margins: (usize, usize),
display: WindowDisplayState,
new_pixel: Option<i64>,
new_total: Option<i64>,
new_normal: Value,
normal_lines: Value,
normal_cols: Value,
},
Internal {
id: WindowId,
direction: SplitDirection,
children: Vec<Window>,
bounds: Rect,
parameters: WindowParameters,
combination_limit: bool,
new_pixel: Option<i64>,
new_total: Option<i64>,
new_normal: Value,
normal_lines: Value,
normal_cols: Value,
},
}
impl Window {
pub fn new_leaf(id: WindowId, buffer_id: BufferId, bounds: Rect) -> Self {
Window::Leaf {
id,
buffer_id,
bounds,
window_start: 1,
window_end_pos: 0,
window_end_bytepos: 0,
window_end_vpos: 0,
window_end_valid: false,
point: 1,
old_point: 1,
dedicated: false,
parameters: Vec::new(),
history: WindowHistoryState::default(),
fixed_height: 0,
fixed_width: 0,
hscroll: 0,
vscroll: 0,
preserve_vscroll_p: false,
margins: (0, 0),
display: WindowDisplayState::default(),
new_pixel: None,
new_total: None,
new_normal: Value::NIL,
normal_lines: Value::make_float(1.0),
normal_cols: Value::make_float(1.0),
}
}
pub fn new_pixel(&self) -> Option<i64> {
match self {
Window::Leaf { new_pixel, .. } | Window::Internal { new_pixel, .. } => *new_pixel,
}
}
pub fn set_new_pixel(&mut self, value: Option<i64>) {
match self {
Window::Leaf { new_pixel, .. } | Window::Internal { new_pixel, .. } => {
*new_pixel = value;
}
}
}
pub fn new_total(&self) -> Option<i64> {
match self {
Window::Leaf { new_total, .. } | Window::Internal { new_total, .. } => *new_total,
}
}
pub fn set_new_total(&mut self, value: Option<i64>) {
match self {
Window::Leaf { new_total, .. } | Window::Internal { new_total, .. } => {
*new_total = value;
}
}
}
pub fn new_normal(&self) -> Value {
match self {
Window::Leaf { new_normal, .. } | Window::Internal { new_normal, .. } => *new_normal,
}
}
pub fn set_new_normal(&mut self, value: Value) {
match self {
Window::Leaf { new_normal, .. } | Window::Internal { new_normal, .. } => {
*new_normal = value;
}
}
}
pub fn normal_lines(&self) -> Value {
match self {
Window::Leaf { normal_lines, .. } | Window::Internal { normal_lines, .. } => {
*normal_lines
}
}
}
pub fn set_normal_lines(&mut self, value: Value) {
match self {
Window::Leaf { normal_lines, .. } | Window::Internal { normal_lines, .. } => {
*normal_lines = value;
}
}
}
pub fn normal_cols(&self) -> Value {
match self {
Window::Leaf { normal_cols, .. } | Window::Internal { normal_cols, .. } => *normal_cols,
}
}
pub fn set_normal_cols(&mut self, value: Value) {
match self {
Window::Leaf { normal_cols, .. } | Window::Internal { normal_cols, .. } => {
*normal_cols = value;
}
}
}
pub fn set_point(&mut self, pos: usize) {
if let Window::Leaf { point, .. } = self {
*point = pos;
}
}
pub fn redisplay_state(&self) -> Option<WindowRedisplayState> {
match self {
Window::Leaf {
id,
buffer_id,
bounds,
window_start,
window_end_pos,
window_end_bytepos,
window_end_vpos,
window_end_valid,
point,
old_point,
hscroll,
vscroll,
preserve_vscroll_p,
..
} => Some(WindowRedisplayState {
id: *id,
buffer_id: *buffer_id,
bounds: (
redisplay_f32_bits(bounds.x),
redisplay_f32_bits(bounds.y),
redisplay_f32_bits(bounds.width),
redisplay_f32_bits(bounds.height),
),
window_start: *window_start,
window_end_pos: *window_end_pos,
window_end_bytepos: *window_end_bytepos,
window_end_vpos: *window_end_vpos,
window_end_valid: *window_end_valid,
point: *point,
old_point: *old_point,
hscroll: *hscroll,
vscroll: *vscroll,
preserve_vscroll_p: *preserve_vscroll_p,
}),
Window::Internal { .. } => None,
}
}
pub fn id(&self) -> WindowId {
match self {
Window::Leaf { id, .. } | Window::Internal { id, .. } => *id,
}
}
pub fn bounds(&self) -> &Rect {
match self {
Window::Leaf { bounds, .. } | Window::Internal { bounds, .. } => bounds,
}
}
pub fn bounds_mut(&mut self) -> &mut Rect {
match self {
Window::Leaf { bounds, .. } | Window::Internal { bounds, .. } => bounds,
}
}
pub fn set_bounds(&mut self, new_bounds: Rect) {
match self {
Window::Leaf { bounds, .. } | Window::Internal { bounds, .. } => {
*bounds = new_bounds;
}
}
}
pub fn is_leaf(&self) -> bool {
matches!(self, Window::Leaf { .. })
}
pub fn display(&self) -> Option<&WindowDisplayState> {
match self {
Window::Leaf { display, .. } => Some(display),
Window::Internal { .. } => None,
}
}
pub fn display_mut(&mut self) -> Option<&mut WindowDisplayState> {
match self {
Window::Leaf { display, .. } => Some(display),
Window::Internal { .. } => None,
}
}
pub fn parameters(&self) -> &WindowParameters {
match self {
Window::Leaf { parameters, .. } | Window::Internal { parameters, .. } => parameters,
}
}
pub fn parameters_mut(&mut self) -> &mut WindowParameters {
match self {
Window::Leaf { parameters, .. } | Window::Internal { parameters, .. } => parameters,
}
}
pub fn history(&self) -> Option<&WindowHistoryState> {
match self {
Window::Leaf { history, .. } => Some(history),
Window::Internal { .. } => None,
}
}
pub fn history_mut(&mut self) -> Option<&mut WindowHistoryState> {
match self {
Window::Leaf { history, .. } => Some(history),
Window::Internal { .. } => None,
}
}
pub fn combination_limit(&self) -> Option<bool> {
match self {
Window::Internal {
combination_limit, ..
} => Some(*combination_limit),
Window::Leaf { .. } => None,
}
}
pub fn set_combination_limit(&mut self, limit: bool) {
if let Window::Internal {
combination_limit, ..
} = self
{
*combination_limit = limit;
}
}
pub fn buffer_id(&self) -> Option<BufferId> {
match self {
Window::Leaf { buffer_id, .. } => Some(*buffer_id),
Window::Internal { .. } => None,
}
}
pub fn set_buffer(&mut self, new_id: BufferId) {
if let Window::Leaf {
buffer_id,
window_start,
window_end_pos,
window_end_bytepos,
window_end_vpos,
window_end_valid,
point,
..
} = self
{
*buffer_id = new_id;
*window_start = 1;
*window_end_pos = 0;
*window_end_bytepos = 0;
*window_end_vpos = 0;
*window_end_valid = false;
*point = 1;
}
}
pub fn window_end_charpos(&self, buffer_z: usize) -> Option<usize> {
match self {
Window::Leaf { window_end_pos, .. } => Some(buffer_z.saturating_sub(*window_end_pos)),
Window::Internal { .. } => None,
}
}
pub fn window_end_bytepos(&self, buffer_z_byte: usize) -> Option<usize> {
match self {
Window::Leaf {
window_end_bytepos, ..
} => Some(buffer_z_byte.saturating_sub(*window_end_bytepos)),
Window::Internal { .. } => None,
}
}
pub fn window_end_valid(&self) -> Option<bool> {
match self {
Window::Leaf {
window_end_valid, ..
} => Some(*window_end_valid),
Window::Internal { .. } => None,
}
}
pub fn set_window_end_from_positions(
&mut self,
buffer_z_char: usize,
buffer_z_byte: usize,
end_charpos: usize,
end_bytepos: usize,
vpos: usize,
) {
if let Window::Leaf {
window_end_pos,
window_end_bytepos,
window_end_vpos,
window_end_valid,
..
} = self
{
*window_end_pos = buffer_z_char.saturating_sub(end_charpos.min(buffer_z_char));
*window_end_bytepos = buffer_z_byte.saturating_sub(end_bytepos.min(buffer_z_byte));
*window_end_vpos = vpos;
*window_end_valid = true;
}
}
pub fn replace_buffer_id(&mut self, old_id: BufferId, new_id: BufferId) {
match self {
Window::Leaf { buffer_id, .. } => {
if *buffer_id == old_id {
self.set_buffer(new_id);
}
}
Window::Internal { children, .. } => {
for child in children {
child.replace_buffer_id(old_id, new_id);
}
}
}
}
pub fn find(&self, target: WindowId) -> Option<&Window> {
if self.id() == target {
return Some(self);
}
if let Window::Internal { children, .. } = self {
for child in children {
if let Some(w) = child.find(target) {
return Some(w);
}
}
}
None
}
pub fn find_mut(&mut self, target: WindowId) -> Option<&mut Window> {
if self.id() == target {
return Some(self);
}
if let Window::Internal { children, .. } = self {
for child in children {
if let Some(w) = child.find_mut(target) {
return Some(w);
}
}
}
None
}
pub fn leaf_ids(&self) -> Vec<WindowId> {
let mut result = Vec::new();
self.collect_leaves(&mut result);
result
}
fn collect_leaves(&self, out: &mut Vec<WindowId>) {
match self {
Window::Leaf { id, .. } => out.push(*id),
Window::Internal { children, .. } => {
for child in children {
child.collect_leaves(out);
}
}
}
}
pub fn window_at(&self, px: f32, py: f32) -> Option<WindowId> {
match self {
Window::Leaf { id, bounds, .. } => {
if bounds.contains(px, py) {
Some(*id)
} else {
None
}
}
Window::Internal {
children, bounds, ..
} => {
if !bounds.contains(px, py) {
return None;
}
for child in children {
if let Some(id) = child.window_at(px, py) {
return Some(id);
}
}
None
}
}
}
pub fn leaf_count(&self) -> usize {
match self {
Window::Leaf { .. } => 1,
Window::Internal { children, .. } => children.iter().map(|c| c.leaf_count()).sum(),
}
}
pub fn invalidate_display_state(&mut self) {
match self {
Window::Leaf {
window_end_valid,
display,
..
} => {
*window_end_valid = false;
display.clear_physical_cursor_state();
}
Window::Internal { children, .. } => {
for child in children {
child.invalidate_display_state();
}
}
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DisplayPointSnapshot {
pub buffer_pos: usize,
pub x: i64,
pub y: i64,
pub width: i64,
pub height: i64,
pub row: i64,
pub col: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DisplayRowSnapshot {
pub row: i64,
pub y: i64,
pub height: i64,
pub start_x: i64,
pub start_col: i64,
pub end_x: i64,
pub end_col: i64,
pub start_buffer_pos: Option<usize>,
pub end_buffer_pos: Option<usize>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum WindowCursorKind {
NoCursor,
FilledBox,
HollowBox,
Bar,
Hbar,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct WindowCursorPos {
pub x: i64,
pub y: i64,
pub row: i64,
pub col: i64,
}
impl WindowCursorPos {
pub fn from_snapshot(snapshot: &WindowCursorSnapshot) -> Self {
Self {
x: snapshot.x,
y: snapshot.y,
row: snapshot.row,
col: snapshot.col,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct WindowCursorSnapshot {
pub kind: WindowCursorKind,
pub x: i64,
pub y: i64,
pub width: i64,
pub height: i64,
pub ascent: i64,
pub row: i64,
pub col: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct WindowDisplaySnapshot {
pub window_id: WindowId,
pub text_area_left_offset: i64,
pub mode_line_height: i64,
pub header_line_height: i64,
pub tab_line_height: i64,
pub logical_cursor: Option<WindowCursorPos>,
pub phys_cursor: Option<WindowCursorSnapshot>,
pub points: Vec<DisplayPointSnapshot>,
pub rows: Vec<DisplayRowSnapshot>,
}
impl WindowDisplaySnapshot {
pub fn logical_cursor_pos(&self) -> Option<WindowCursorPos> {
self.logical_cursor.or_else(|| {
self.phys_cursor
.as_ref()
.map(WindowCursorPos::from_snapshot)
})
}
pub fn visible_buffer_span(&self) -> Option<(usize, usize)> {
let start = self
.rows
.iter()
.find_map(|row| row.start_buffer_pos)
.or_else(|| self.points.first().map(|point| point.buffer_pos))?;
let end = self
.rows
.iter()
.rev()
.find_map(|row| row.end_buffer_pos)
.or_else(|| self.points.last().map(|point| point.buffer_pos))?;
Some((start, end))
}
fn row_for_buffer_pos(&self, pos: usize) -> Option<&DisplayRowSnapshot> {
self.rows.iter().find(|row| {
let Some(start) = row.start_buffer_pos else {
return false;
};
let Some(end) = row.end_buffer_pos else {
return false;
};
start <= pos && pos <= end
})
}
pub fn point_for_buffer_pos(&self, pos: usize) -> Option<&DisplayPointSnapshot> {
if self.points.is_empty() {
return None;
}
let (visible_start, visible_end) = self.visible_buffer_span()?;
if pos < visible_start || pos > visible_end {
return None;
}
match self
.points
.binary_search_by_key(&pos, |point| point.buffer_pos)
{
Ok(idx) => self.points.get(idx),
Err(_) => {
let row = self.row_for_buffer_pos(pos)?;
let next_on_row = self
.points
.iter()
.find(|point| point.row == row.row && point.buffer_pos > pos);
let prev_on_row = self
.points
.iter()
.rev()
.find(|point| point.row == row.row && point.buffer_pos < pos);
match (prev_on_row, next_on_row) {
(Some(_), Some(next)) => Some(next),
_ => None,
}
}
}
}
pub fn point_at_coords(&self, x: i64, y: i64) -> Option<&DisplayPointSnapshot> {
let row = self
.rows
.iter()
.find(|row| y >= row.y && y < row.y.saturating_add(row.height.max(1)))?;
let mut row_points = self.points.iter().filter(|point| point.row == row.row);
let mut last = row_points.next()?;
if x <= last.x {
return Some(last);
}
for point in row_points {
let right = last.x.saturating_add(last.width.max(1));
if x < right {
return Some(last);
}
if x < point.x {
return Some(last);
}
last = point;
}
Some(last)
}
pub fn row_metrics(&self, row: i64) -> Option<&DisplayRowSnapshot> {
self.rows.iter().find(|metrics| metrics.row == row)
}
}
impl Default for WindowDisplaySnapshot {
fn default() -> Self {
Self {
window_id: WindowId(0),
text_area_left_offset: 0,
mode_line_height: 0,
header_line_height: 0,
tab_line_height: 0,
logical_cursor: None,
phys_cursor: None,
points: Vec::new(),
rows: Vec::new(),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct WindowHookSnapshot {
pub buffer_id: BufferId,
pub bounds: Rect,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct FrameWindowHookRecord {
pub windows: HashMap<WindowId, WindowHookSnapshot>,
pub selected_window: Option<WindowId>,
pub was_selected_frame: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PendingGuiResize {
pub width_cols: i64,
pub total_lines: i64,
pub host_request_sent: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct GuiFrameGeometryHints {
pub base_width: u32,
pub base_height: u32,
pub min_width: u32,
pub min_height: u32,
pub width_inc: u32,
pub height_inc: u32,
}
pub struct Frame {
pub id: FrameId,
pub name: Value,
pub explicit_name: bool,
pub icon_name: Value,
pub focus_frame: Value,
pub parent_frame: Value,
pub terminal_id: u64,
pub initial: bool,
pub root_window: Window,
pub selected_window: WindowId,
pub old_selected_window: Option<WindowId>,
pub minibuffer_window: Option<WindowId>,
pub minibuffer_leaf: Option<Window>,
pub width: u32,
pub height: u32,
pub left_pos: i64,
pub top_pos: i64,
pub window_system: Option<Value>,
pub parameters: HashMap<Value, Value>,
pub visible: bool,
pub title: Value,
pub menu_bar_height: u32,
pub tool_bar_height: u32,
pub tab_bar_height: u32,
pub font_pixel_size: f32,
pub char_width: f32,
pub char_height: f32,
pub defer_next_gui_parameter_resize: bool,
pub pending_gui_resize: Option<PendingGuiResize>,
pub display_snapshots: HashMap<WindowId, WindowDisplaySnapshot>,
pub(crate) window_hook_record: FrameWindowHookRecord,
pub(crate) window_state_change: bool,
pub face_hash_table: Value,
pub realized_faces: HashMap<Value, RuntimeFace>,
pub z_order: i32,
pub undecorated: bool,
pub no_accept_focus: bool,
pub no_split: bool,
}
impl Frame {
pub fn new(
id: FrameId,
name: Value,
terminal_id: u64,
width: u32,
height: u32,
root_window: Window,
) -> Self {
let minibuffer_window = WindowId(MINIBUFFER_WINDOW_ID_BASE + id.0);
let minibuffer_buffer_id = root_window.buffer_id().unwrap_or(BufferId(0));
let mut minibuffer_leaf = Window::new_leaf(
minibuffer_window,
minibuffer_buffer_id,
Rect::new(0.0, height as f32, width as f32, 16.0),
);
if let Window::Leaf {
window_start,
point,
..
} = &mut minibuffer_leaf
{
*window_start = 1;
*point = 1;
}
let selected = root_window
.leaf_ids()
.first()
.copied()
.unwrap_or(WindowId(0));
Self {
id,
name,
explicit_name: false,
icon_name: Value::NIL,
focus_frame: Value::NIL,
parent_frame: Value::NIL,
terminal_id,
initial: false,
root_window,
selected_window: selected,
old_selected_window: None,
minibuffer_window: Some(minibuffer_window),
minibuffer_leaf: Some(minibuffer_leaf),
width,
height,
left_pos: 0,
top_pos: 0,
window_system: None,
parameters: {
let mut params = HashMap::new();
params.insert(
Value::symbol("foreground-color"),
Value::string("unspecified-fg"),
);
params.insert(
Value::symbol("background-color"),
Value::string("unspecified-bg"),
);
params.insert(Value::symbol("cursor-color"), Value::string("black"));
params.insert(Value::symbol("tab-bar-lines"), Value::fixnum(0));
params.insert(Value::symbol("minibuffer"), Value::T);
params
},
visible: true,
title: Value::NIL,
menu_bar_height: 0,
tool_bar_height: 0,
tab_bar_height: 0,
font_pixel_size: 16.0,
char_width: 8.0,
char_height: 16.0,
defer_next_gui_parameter_resize: false,
pending_gui_resize: None,
display_snapshots: HashMap::new(),
window_hook_record: FrameWindowHookRecord::default(),
window_state_change: false,
face_hash_table: Value::hash_table(HashTableTest::Eq),
realized_faces: HashMap::new(),
z_order: 0,
undecorated: false,
no_accept_focus: false,
no_split: false,
}
}
pub fn name_value(&self) -> Value {
self.name
}
pub fn title_value(&self) -> Value {
self.title
}
pub fn explicit_name_value(&self) -> Value {
Value::bool_val(self.explicit_name)
}
pub fn icon_name_value(&self) -> Value {
self.icon_name
}
pub fn focus_frame_value(&self) -> Value {
self.focus_frame
}
pub fn name_runtime_string_owned(&self) -> String {
self.name.as_runtime_string_owned().unwrap_or_default()
}
pub fn title_runtime_string_owned(&self) -> Option<String> {
self.title.as_runtime_string_owned()
}
pub fn host_title_runtime_string_owned(&self) -> String {
self.title_runtime_string_owned()
.filter(|title| !title.is_empty())
.or_else(|| {
let name = self.name_runtime_string_owned();
(!name.is_empty()).then_some(name)
})
.unwrap_or_else(|| "Neomacs".to_string())
}
pub fn host_title_lisp_string(&self) -> crate::heap_types::LispString {
self.title
.as_lisp_string()
.filter(|ls| !ls.as_bytes().is_empty())
.or_else(|| {
self.name
.as_lisp_string()
.filter(|ls| !ls.as_bytes().is_empty())
})
.cloned()
.unwrap_or_else(|| crate::heap_types::LispString::from_utf8("Neomacs"))
}
pub fn generated_name_runtime_string(&self) -> String {
let ordinal = if self.id.0 >= FRAME_ID_BASE {
self.id.0 - FRAME_ID_BASE + 1
} else {
self.id.0
};
format!("F{ordinal}")
}
pub fn generated_name_value(&self) -> Value {
Value::string(self.generated_name_runtime_string())
}
pub fn set_name_value(&mut self, name: Value) {
self.explicit_name = true;
self.name = name;
}
pub fn set_generated_name_value(&mut self, name: Value) {
self.explicit_name = false;
self.name = name;
}
pub fn set_name_parameter_value(&mut self, name: Value) {
if name.is_nil() {
self.set_generated_name_value(self.generated_name_value());
} else {
self.set_name_value(name);
}
}
pub fn set_title_value(&mut self, title: Value) {
self.title = title;
}
pub fn clear_title(&mut self) {
self.title = Value::NIL;
}
pub fn recalculate_minibuffer_bounds(&mut self) {
self.reposition_minibuffer_below_root();
}
pub fn selected_window(&self) -> Option<&Window> {
self.root_window.find(self.selected_window)
}
pub fn selected_window_mut(&mut self) -> Option<&mut Window> {
self.root_window.find_mut(self.selected_window)
}
pub fn replace_buffer_bindings(&mut self, old_id: BufferId, new_id: BufferId) {
self.root_window.replace_buffer_id(old_id, new_id);
if let Some(minibuffer_leaf) = self.minibuffer_leaf.as_mut() {
minibuffer_leaf.replace_buffer_id(old_id, new_id);
}
}
pub fn effective_window_system(&self) -> Option<Value> {
self.window_system
.or_else(|| self.parameter("window-system"))
}
pub fn set_window_system(&mut self, window_system: Option<Value>) {
self.window_system = window_system;
match window_system {
Some(value) => {
self.set_parameter(Value::symbol("window-system"), value);
}
None => {
self.remove_parameter(Value::symbol("window-system"));
}
}
}
pub fn frame_parameter_int(&self, key: &str) -> Option<i64> {
self.parameter(key).and_then(|v| v.as_int())
}
pub fn parameter(&self, key: &str) -> Option<Value> {
self.parameters.get(&Value::symbol(key)).copied()
}
pub fn set_parameter(&mut self, key: Value, value: Value) -> Option<Value> {
self.parameters.insert(key, value)
}
pub fn remove_parameter(&mut self, key: Value) -> Option<Value> {
self.parameters.remove(&key)
}
pub fn realized_face(&self, name: &str) -> Option<&RuntimeFace> {
self.realized_faces.get(&Value::symbol(name))
}
pub fn face_hash_table(&self) -> Value {
self.face_hash_table
}
pub fn set_realized_face(&mut self, name: Value, face: RuntimeFace) {
self.realized_faces.insert(name, face);
}
pub fn clear_realized_faces(&mut self) {
self.realized_faces.clear();
if self.face_hash_table.is_hash_table() {
let _ = self.face_hash_table.with_hash_table_mut(|table| {
table.data.clear();
table.key_snapshots.clear();
table.insertion_order.clear();
});
}
}
pub fn defer_next_gui_parameter_resize(&mut self) {
self.defer_next_gui_parameter_resize = true;
}
pub fn should_defer_gui_parameter_resize(&self) -> bool {
self.defer_next_gui_parameter_resize || self.pending_gui_resize.is_some()
}
pub fn queue_pending_gui_resize(
&mut self,
width_cols: i64,
total_lines: i64,
host_request_sent: bool,
) {
self.defer_next_gui_parameter_resize = false;
self.pending_gui_resize = Some(PendingGuiResize {
width_cols,
total_lines,
host_request_sent,
});
}
pub fn take_pending_gui_resize(&mut self) -> Option<PendingGuiResize> {
self.defer_next_gui_parameter_resize = false;
self.pending_gui_resize.take()
}
pub fn clear_pending_gui_resize(&mut self) {
self.defer_next_gui_parameter_resize = false;
self.pending_gui_resize = None;
}
pub fn gui_geometry_hints(&self) -> GuiFrameGeometryHints {
let width_inc = self.char_width.max(1.0).round() as u32;
let height_inc = self.char_height.max(1.0).round() as u32;
let base_width = width_inc.saturating_add(self.horizontal_non_text_width().max(0) as u32);
let base_height = height_inc.saturating_add(
self.menu_bar_height
.saturating_add(self.tool_bar_height)
.saturating_add(self.tab_bar_height),
);
GuiFrameGeometryHints {
base_width,
base_height,
min_width: base_width,
min_height: base_height,
width_inc,
height_inc,
}
}
fn chrome_top_height(&self) -> f32 {
self.menu_bar_height
.saturating_add(self.tool_bar_height)
.saturating_add(self.tab_bar_height) as f32
}
fn default_left_fringe_width(&self) -> i64 {
self.parameter("left-fringe")
.and_then(|v| v.as_int())
.unwrap_or(8)
.max(0)
}
fn default_right_fringe_width(&self) -> i64 {
self.parameter("right-fringe")
.and_then(|v| v.as_int())
.unwrap_or(8)
.max(0)
}
fn default_vertical_scroll_bar_side(&self) -> Option<&'static str> {
let raw = self.parameter("vertical-scroll-bars").unwrap_or_else(|| {
if self.effective_window_system().is_some() {
Value::symbol("right")
} else {
Value::NIL
}
});
match raw.as_symbol_name() {
Some("left") => Some("left"),
Some("right") => Some("right"),
_ if raw.is_nil() => None,
_ if raw.is_truthy() => Some("right"),
_ => None,
}
}
fn default_vertical_scroll_bar_width(&self) -> i64 {
self.parameter("scroll-bar-width")
.and_then(|v| v.as_int())
.filter(|value| *value > 0)
.unwrap_or_else(|| self.char_width.max(1.0).round() as i64)
}
pub(crate) fn horizontal_non_text_width(&self) -> i64 {
if self.effective_window_system().is_none() {
return 0;
}
let left_fringe = self.default_left_fringe_width();
let right_fringe = self.default_right_fringe_width();
let scroll_bar_width = if self.default_vertical_scroll_bar_side().is_some() {
self.default_vertical_scroll_bar_width()
} else {
0
};
left_fringe
.saturating_add(right_fringe)
.saturating_add(scroll_bar_width)
}
fn window_text_area_bounds(&self) -> Rect {
let frame_w = self.width as f32;
let frame_h = self.height as f32;
let chrome_top = self.chrome_top_height().min(frame_h);
let minibuffer_height = self
.minibuffer_leaf
.as_ref()
.map(|mini| mini.bounds().height.max(0.0))
.unwrap_or(0.0)
.min((frame_h - chrome_top).max(0.0));
let root_height = (frame_h - chrome_top - minibuffer_height).max(0.0);
Rect::new(0.0, chrome_top, frame_w, root_height)
}
pub fn sync_window_area_bounds(&mut self) {
let root_bounds = self.window_text_area_bounds();
resize_window_subtree(&mut self.root_window, root_bounds);
self.reposition_minibuffer_below_root();
}
pub fn reposition_minibuffer_below_root(&mut self) {
let root_bounds = *self.root_window.bounds();
if let Some(mini) = self.minibuffer_leaf.as_mut() {
let mini_h = mini
.bounds()
.height
.max(0.0)
.min((self.height as f32 - (root_bounds.y + root_bounds.height)).max(0.0));
mini.set_bounds(Rect::new(
root_bounds.x,
root_bounds.y + root_bounds.height,
root_bounds.width,
mini_h,
));
mini.invalidate_display_state();
}
self.root_window.invalidate_display_state();
self.display_snapshots.clear();
}
pub fn sync_tab_bar_height_from_parameters(&mut self) {
let lines = self
.frame_parameter_int("tab-bar-lines")
.unwrap_or(0)
.max(0) as u32;
let char_height = self.char_height.max(1.0).round() as u32;
self.tab_bar_height = lines.saturating_mul(char_height);
self.sync_window_area_bounds();
}
pub fn sync_menu_bar_height_from_parameters(&mut self) {
let lines = self
.frame_parameter_int("menu-bar-lines")
.unwrap_or(0)
.max(0) as u32;
let char_height = self.char_height.max(1.0).round() as u32;
self.menu_bar_height = lines.saturating_mul(char_height);
self.sync_window_area_bounds();
}
pub fn sync_tool_bar_height_from_parameters(&mut self) {
let lines = self
.frame_parameter_int("tool-bar-lines")
.unwrap_or(0)
.max(0) as u32;
let line_height = if self.effective_window_system().is_some() {
default_gui_tool_bar_line_height(self.font_pixel_size)
} else {
self.char_height.max(1.0).round() as u32
};
self.tool_bar_height = lines.saturating_mul(line_height);
self.sync_window_area_bounds();
}
pub fn select_window(&mut self, id: WindowId) -> bool {
if self.find_window(id).is_some() {
self.selected_window = id;
true
} else {
false
}
}
pub fn find_window(&self, id: WindowId) -> Option<&Window> {
if let Some(window) = self.root_window.find(id) {
return Some(window);
}
self.minibuffer_leaf.as_ref().and_then(|window| {
if window.id() == id {
Some(window)
} else {
None
}
})
}
pub fn find_window_mut(&mut self, id: WindowId) -> Option<&mut Window> {
if let Some(window) = self.root_window.find_mut(id) {
return Some(window);
}
self.minibuffer_leaf.as_mut().and_then(|window| {
if window.id() == id {
Some(window)
} else {
None
}
})
}
pub fn window_list(&self) -> Vec<WindowId> {
self.root_window.leaf_ids()
}
fn live_window_ids_with_minibuffer(&self) -> Vec<WindowId> {
let mut ids = self.window_list();
if let Some(minibuffer_leaf) = self.minibuffer_leaf.as_ref() {
ids.push(minibuffer_leaf.id());
}
ids
}
pub fn window_count(&self) -> usize {
self.root_window.leaf_count()
}
pub fn window_at(&self, px: f32, py: f32) -> Option<WindowId> {
self.root_window.window_at(px, py)
}
pub fn columns(&self) -> u32 {
(self.width as f32 / self.char_width) as u32
}
pub fn lines(&self) -> u32 {
(self.height as f32 / self.char_height) as u32
}
pub fn replace_display_snapshots(&mut self, snapshots: Vec<WindowDisplaySnapshot>) {
self.begin_display_output_pass();
for snapshot in &snapshots {
self.replay_window_output_snapshot(snapshot);
}
self.set_display_snapshots(snapshots);
}
pub fn begin_display_output_pass(&mut self) {
let live_window_ids = self.live_window_ids_with_minibuffer();
for wid in &live_window_ids {
if let Some(window) = self.find_window_mut(*wid)
&& let Some(display) = window.display_mut()
{
display.begin_output_pass();
}
}
}
pub fn window_output_update(&mut self, window_id: WindowId) -> Option<WindowOutputUpdate<'_>> {
let display = self.find_window_mut(window_id)?.display_mut()?;
Some(WindowOutputUpdate::new(display))
}
pub fn replay_window_output_snapshot(&mut self, snapshot: &WindowDisplaySnapshot) {
if let Some(mut update) = self.window_output_update(snapshot.window_id) {
update.replay_snapshot(snapshot);
}
}
pub fn set_display_snapshots(&mut self, snapshots: Vec<WindowDisplaySnapshot>) {
self.display_snapshots.clear();
for snapshot in snapshots {
if self.find_window(snapshot.window_id).is_some() {
self.display_snapshots.insert(snapshot.window_id, snapshot);
}
}
}
pub fn window_display_snapshot(&self, id: WindowId) -> Option<&WindowDisplaySnapshot> {
self.display_snapshots.get(&id)
}
pub fn resize_pixelwise(&mut self, width: u32, height: u32) {
self.clear_pending_gui_resize();
self.width = width;
self.height = height;
self.sync_window_area_bounds();
let char_width = self.char_width.max(1.0).round();
let char_height = self.char_height.max(1.0).round();
let text_width = (i64::from(width) - self.horizontal_non_text_width()).max(1) as f32;
let root_height = self.root_window.bounds().height;
let cols = (text_width / char_width).floor().max(1.0) as i64;
let text_lines = (root_height / char_height).floor().max(1.0) as i64;
let total_lines = text_lines.saturating_add(1);
self.set_parameter(Value::symbol("width"), Value::fixnum(cols));
self.set_parameter(Value::symbol("height"), Value::fixnum(total_lines));
self.set_parameter(
Value::symbol("neovm--frame-text-lines"),
Value::fixnum(text_lines),
);
}
pub fn grow_mini_window(&mut self, delta_rows: i32) {
self.grow_mini_window_with_max_lines(delta_rows, 0.25);
}
pub fn grow_mini_window_with_max_lines(&mut self, delta_rows: i32, max_lines: f32) {
let char_h = self.char_height.max(1.0);
let unit = char_h;
let frame_inner_h = (self.height as f32) - self.chrome_top_height();
let requested_max_h = if max_lines <= 1.0 {
frame_inner_h * max_lines.max(0.0)
} else {
unit * max_lines
};
let max_h = requested_max_h.min(frame_inner_h).max(unit);
let Some(mini) = self.minibuffer_leaf.as_mut() else {
return;
};
let current_h = mini.bounds().height;
let new_h = (current_h + delta_rows as f32 * unit).clamp(unit, max_h);
if (new_h - current_h).abs() < 0.5 {
return;
}
let mut bounds = *mini.bounds();
bounds.height = new_h;
mini.set_bounds(bounds);
self.sync_window_area_bounds();
}
pub fn shrink_mini_window(&mut self) {
let Some(mini) = self.minibuffer_leaf.as_mut() else {
return;
};
let unit = self.char_height.max(1.0);
let mut bounds = *mini.bounds();
if (bounds.height - unit).abs() < 0.5 {
return;
}
bounds.height = unit;
mini.set_bounds(bounds);
self.sync_window_area_bounds();
}
}
pub struct FrameManager {
frames: HashMap<FrameId, Frame>,
selected: Option<FrameId>,
next_frame_id: u64,
next_window_id: u64,
old_selected_window: Option<WindowId>,
deleted_windows: HashSet<WindowId>,
deleted_window_parameters: HashMap<WindowId, WindowParameters>,
window_select_count: i64,
}
impl FrameManager {
pub fn new() -> Self {
Self {
frames: HashMap::new(),
selected: None,
next_frame_id: FRAME_ID_BASE,
next_window_id: 1,
old_selected_window: None,
deleted_windows: HashSet::new(),
deleted_window_parameters: HashMap::new(),
window_select_count: 0,
}
}
pub fn next_window_id(&mut self) -> WindowId {
let id = WindowId(self.next_window_id);
self.next_window_id += 1;
self.deleted_windows.remove(&id);
self.deleted_window_parameters.remove(&id);
id
}
pub fn create_frame(
&mut self,
name: &str,
width: u32,
height: u32,
buffer_id: BufferId,
) -> FrameId {
self.create_frame_value(Value::string(name), width, height, buffer_id)
}
pub fn create_frame_value(
&mut self,
name: Value,
width: u32,
height: u32,
buffer_id: BufferId,
) -> FrameId {
self.create_frame_value_on_terminal(name, 0, width, height, buffer_id)
}
pub fn create_frame_on_terminal(
&mut self,
name: &str,
terminal_id: u64,
width: u32,
height: u32,
buffer_id: BufferId,
) -> FrameId {
self.create_frame_value_on_terminal(
Value::string(name),
terminal_id,
width,
height,
buffer_id,
)
}
pub fn create_frame_value_on_terminal(
&mut self,
name: Value,
terminal_id: u64,
width: u32,
height: u32,
buffer_id: BufferId,
) -> FrameId {
let frame_id = FrameId(self.next_frame_id);
self.next_frame_id += 1;
let window_id = self.next_window_id();
let bounds = Rect::new(0.0, 0.0, width as f32, height as f32);
let root = Window::new_leaf(window_id, buffer_id, bounds);
let frame = Frame::new(frame_id, name, terminal_id, width, height, root);
let selected_wid = frame.selected_window;
self.frames.insert(frame_id, frame);
self.note_window_selected(selected_wid);
if self.selected.is_none() {
self.selected = Some(frame_id);
self.old_selected_window = Some(selected_wid);
}
frame_id
}
pub fn get(&self, id: FrameId) -> Option<&Frame> {
self.frames.get(&id)
}
pub fn get_mut(&mut self, id: FrameId) -> Option<&mut Frame> {
self.frames.get_mut(&id)
}
pub fn selected_frame(&self) -> Option<&Frame> {
self.selected.and_then(|id| self.frames.get(&id))
}
pub fn selected_frame_mut(&mut self) -> Option<&mut Frame> {
self.selected.and_then(|id| self.frames.get_mut(&id))
}
pub fn select_frame(&mut self, id: FrameId) -> bool {
if self.frames.contains_key(&id) {
let previous = self.selected;
self.selected = Some(id);
if let Some(previous) = previous {
let previous_value = Value::make_frame(previous.0);
let redirected_value = Value::make_frame(id.0);
for frame in self.frames.values_mut() {
if frame.focus_frame == previous_value {
frame.focus_frame = redirected_value;
}
}
}
true
} else {
false
}
}
pub fn delete_frame(&mut self, id: FrameId) -> bool {
if let Some(frame) = self.frames.remove(&id) {
for wid in frame.window_list() {
self.deleted_windows.insert(wid);
if let Some(window) = frame.find_window(wid) {
self.deleted_window_parameters
.insert(wid, window.parameters().clone());
}
}
if let Some(minibuffer_leaf) = frame.minibuffer_leaf.as_ref() {
let minibuffer_wid = minibuffer_leaf.id();
self.deleted_windows.insert(minibuffer_wid);
self.deleted_window_parameters
.insert(minibuffer_wid, minibuffer_leaf.parameters().clone());
}
if self.selected == Some(id) {
self.selected = self.frames.keys().next().copied();
}
true
} else {
false
}
}
pub fn frame_list(&self) -> Vec<FrameId> {
self.frames.keys().copied().collect()
}
pub fn frame_parent_id(&self, id: FrameId) -> Option<FrameId> {
self.frames
.get(&id)
.and_then(|frame| frame.parent_frame.as_frame_id())
.map(FrameId)
.filter(|parent| self.frames.contains_key(parent))
}
pub fn root_frame_id(&self, id: FrameId) -> Option<FrameId> {
if !self.frames.contains_key(&id) {
return None;
}
let mut current = id;
let mut seen = HashSet::new();
while seen.insert(current) {
let Some(parent) = self.frame_parent_id(current) else {
return Some(current);
};
current = parent;
}
Some(current)
}
pub fn frame_ancestor_p(&self, ancestor: FrameId, descendant: FrameId) -> bool {
let mut current = descendant;
let mut seen = HashSet::new();
while seen.insert(current) {
let Some(parent) = self.frame_parent_id(current) else {
return false;
};
if parent == ancestor {
return true;
}
current = parent;
}
false
}
pub fn max_child_z_order(&self, parent: FrameId) -> i32 {
self.frames
.values()
.filter(|frame| frame.parent_frame.as_frame_id() == Some(parent.0))
.map(|frame| frame.z_order)
.max()
.unwrap_or(0)
}
pub fn set_child_z_order_above_siblings(&mut self, child: FrameId, parent: FrameId) {
let z_order = 1 + self.max_child_z_order(parent);
if let Some(frame) = self.frames.get_mut(&child) {
frame.z_order = z_order;
}
}
pub fn raise_or_lower_child_frame(&mut self, id: FrameId, raise: bool) {
let Some(parent) = self.frame_parent_id(id) else {
return;
};
let mut siblings: Vec<FrameId> = self
.frames
.iter()
.filter_map(|(frame_id, frame)| {
(frame.parent_frame.as_frame_id() == Some(parent.0)).then_some(*frame_id)
})
.collect();
siblings.sort_by(|a, b| self.frame_z_order_cmp(*a, *b));
let mut next_z = 0;
for sibling in siblings {
if sibling == id {
continue;
}
if let Some(frame) = self.frames.get_mut(&sibling) {
frame.z_order = next_z;
}
next_z += 1;
}
if let Some(frame) = self.frames.get_mut(&id) {
frame.z_order = if raise { next_z } else { 0 };
}
}
fn frame_ancestors_visible_p(&self, id: FrameId) -> bool {
let mut current = Some(id);
let mut seen = HashSet::new();
while let Some(frame_id) = current {
if !seen.insert(frame_id) {
return false;
}
let Some(frame) = self.frames.get(&frame_id) else {
return false;
};
if !frame.visible {
return false;
}
current = self.frame_parent_id(frame_id);
}
true
}
fn frame_z_order_cmp(&self, a: FrameId, b: FrameId) -> std::cmp::Ordering {
if a == b {
return std::cmp::Ordering::Equal;
}
if self.frame_ancestor_p(a, b) {
return std::cmp::Ordering::Less;
}
if self.frame_ancestor_p(b, a) {
return std::cmp::Ordering::Greater;
}
let a_z = self.frames.get(&a).map(|frame| frame.z_order).unwrap_or(0);
let b_z = self.frames.get(&b).map(|frame| frame.z_order).unwrap_or(0);
a_z.cmp(&b_z).then_with(|| a.0.cmp(&b.0))
}
pub fn frames_in_reverse_z_order(&self, frame: FrameId, visible_only: bool) -> Vec<FrameId> {
let Some(root) = self.root_frame_id(frame) else {
return Vec::new();
};
let mut frames: Vec<FrameId> = self
.frames
.keys()
.copied()
.filter(|frame_id| self.root_frame_id(*frame_id) == Some(root))
.filter(|frame_id| !visible_only || self.frame_ancestors_visible_p(*frame_id))
.collect();
frames.sort_by(|a, b| self.frame_z_order_cmp(*a, *b));
frames
}
pub fn split_window(
&mut self,
frame_id: FrameId,
window_id: WindowId,
direction: SplitDirection,
new_buffer_id: BufferId,
size: Option<i64>,
) -> Option<WindowId> {
let internal_id = self.alloc_window_id();
let new_id = self.alloc_window_id();
let frame = self.frames.get_mut(&frame_id)?;
split_window_in_tree(
&mut frame.root_window,
window_id,
direction,
internal_id,
new_id,
new_buffer_id,
size,
)?;
frame.recalculate_minibuffer_bounds();
Some(new_id)
}
pub fn delete_window(&mut self, frame_id: FrameId, window_id: WindowId) -> bool {
let Some(frame) = self.frames.get_mut(&frame_id) else {
return false;
};
if frame.root_window.leaf_count() <= 1 {
return false; }
let deleted_parameters = frame
.find_window(window_id)
.map(|window| window.parameters().clone());
let removed = delete_window_in_tree(&mut frame.root_window, window_id);
if removed {
self.deleted_windows.insert(window_id);
self.deleted_window_parameters
.insert(window_id, deleted_parameters.unwrap_or_default());
frame.recalculate_minibuffer_bounds();
}
if removed && frame.selected_window == window_id {
if let Some(first) = frame.root_window.leaf_ids().first() {
frame.selected_window = *first;
}
}
removed
}
fn alloc_window_id(&mut self) -> WindowId {
let id = WindowId(self.next_window_id);
self.next_window_id += 1;
self.deleted_windows.remove(&id);
self.deleted_window_parameters.remove(&id);
id
}
pub fn replace_buffer_in_windows(&mut self, old_id: BufferId, new_id: BufferId) {
for frame in self.frames.values_mut() {
frame.replace_buffer_bindings(old_id, new_id);
}
}
pub fn find_window_frame_id(&self, window_id: WindowId) -> Option<FrameId> {
self.frames.iter().find_map(|(frame_id, frame)| {
frame.find_window(window_id)?.is_leaf().then_some(*frame_id)
})
}
pub fn find_valid_window_frame_id(&self, window_id: WindowId) -> Option<FrameId> {
self.frames
.iter()
.find_map(|(frame_id, frame)| frame.find_window(window_id).map(|_| *frame_id))
}
pub fn is_live_window_id(&self, window_id: WindowId) -> bool {
self.find_window_frame_id(window_id).is_some()
}
pub fn is_valid_window_id(&self, window_id: WindowId) -> bool {
self.find_valid_window_frame_id(window_id).is_some()
}
pub fn is_window_object_id(&self, window_id: WindowId) -> bool {
self.is_valid_window_id(window_id) || self.deleted_windows.contains(&window_id)
}
pub fn lookup_window(&self, window_id: WindowId) -> Option<&Window> {
for frame in self.frames.values() {
if let Some(w) = frame.find_window(window_id) {
return Some(w);
}
}
None
}
pub fn lookup_window_mut(&mut self, window_id: WindowId) -> Option<&mut Window> {
for frame in self.frames.values_mut() {
if let Some(w) = frame.find_window_mut(window_id) {
return Some(w);
}
}
None
}
pub fn window_new_pixel(&self, window_id: WindowId) -> Option<i64> {
self.lookup_window(window_id).and_then(Window::new_pixel)
}
pub fn window_new_total(&self, window_id: WindowId) -> Option<i64> {
self.lookup_window(window_id).and_then(Window::new_total)
}
pub fn window_new_normal(&self, window_id: WindowId) -> Value {
self.lookup_window(window_id)
.map(Window::new_normal)
.unwrap_or(Value::NIL)
}
pub fn set_window_new_pixel(&mut self, window_id: WindowId, size: i64, add: bool) -> i64 {
if let Some(window) = self.lookup_window_mut(window_id) {
let stored = if add {
window.new_pixel().unwrap_or(0) + size
} else {
size
};
window.set_new_pixel(Some(stored));
stored
} else {
size
}
}
pub fn set_window_new_total(&mut self, window_id: WindowId, size: i64, add: bool) -> i64 {
if let Some(window) = self.lookup_window_mut(window_id) {
let stored = if add {
window.new_total().unwrap_or(0) + size
} else {
size
};
window.set_new_total(Some(stored));
stored
} else {
size
}
}
pub fn set_window_new_normal(&mut self, window_id: WindowId, value: Value) {
if let Some(window) = self.lookup_window_mut(window_id) {
window.set_new_normal(value);
}
}
}
impl Default for FrameManager {
fn default() -> Self {
Self::new()
}
}
fn split_window_in_tree(
tree: &mut Window,
target: WindowId,
direction: SplitDirection,
internal_id: WindowId,
new_id: WindowId,
new_buffer_id: BufferId,
size: Option<i64>,
) -> Option<()> {
fn split_sizes(total: f32, requested_new_size: Option<i64>) -> (f32, f32) {
let total_px = total.round().max(0.0) as i64;
let new_size_px = match requested_new_size {
Some(n) if n > 0 => n.clamp(1, total_px.saturating_sub(1)),
Some(n) if n < 0 => (total_px - (-n)).clamp(1, total_px.saturating_sub(1)),
_ => total_px / 2,
};
let old_size_px = total_px - new_size_px;
(old_size_px as f32, new_size_px as f32)
}
if tree.id() == target {
let old_id = tree.id();
let old_bounds = *tree.bounds();
let old_window = tree.clone();
if let Window::Leaf {
buffer_id: buf_id, ..
} = old_window
{
let (left_bounds, right_bounds) = match direction {
SplitDirection::Horizontal => {
let (old_size, new_size) = split_sizes(old_bounds.width, size);
(
Rect::new(old_bounds.x, old_bounds.y, old_size, old_bounds.height),
Rect::new(
old_bounds.x + old_size,
old_bounds.y,
new_size,
old_bounds.height,
),
)
}
SplitDirection::Vertical => {
let (old_size, new_size) = split_sizes(old_bounds.height, size);
(
Rect::new(old_bounds.x, old_bounds.y, old_bounds.width, old_size),
Rect::new(
old_bounds.x,
old_bounds.y + old_size,
old_bounds.width,
new_size,
),
)
}
};
let mut old_leaf = old_window;
old_leaf.set_bounds(left_bounds);
let mut new_leaf = old_leaf.clone();
if let Window::Leaf {
id,
buffer_id,
bounds,
parameters,
history,
window_start,
window_end_pos,
window_end_bytepos,
window_end_vpos,
window_end_valid,
point,
old_point,
vscroll,
preserve_vscroll_p,
..
} = &mut new_leaf
{
*id = new_id;
*buffer_id = new_buffer_id;
*bounds = right_bounds;
parameters.clear();
*history = WindowHistoryState::default();
*window_start = 1;
*window_end_pos = 0;
*window_end_bytepos = 0;
*window_end_vpos = 0;
*window_end_valid = false;
*point = 1;
*old_point = 1;
*vscroll = 0;
*preserve_vscroll_p = false;
}
let inherited_normal_lines = old_leaf.normal_lines();
let inherited_normal_cols = old_leaf.normal_cols();
let parent_size = match direction {
SplitDirection::Horizontal => old_bounds.width,
SplitDirection::Vertical => old_bounds.height,
};
let (old_fraction, new_fraction) = if parent_size > 0.0 {
let old_frac = match direction {
SplitDirection::Horizontal => left_bounds.width / parent_size,
SplitDirection::Vertical => left_bounds.height / parent_size,
};
let new_frac = match direction {
SplitDirection::Horizontal => right_bounds.width / parent_size,
SplitDirection::Vertical => right_bounds.height / parent_size,
};
(old_frac as f64, new_frac as f64)
} else {
(0.5, 0.5)
};
match direction {
SplitDirection::Horizontal => {
old_leaf.set_normal_cols(Value::make_float(old_fraction));
old_leaf.set_normal_lines(Value::make_float(1.0));
new_leaf.set_normal_cols(Value::make_float(new_fraction));
new_leaf.set_normal_lines(Value::make_float(1.0));
}
SplitDirection::Vertical => {
old_leaf.set_normal_lines(Value::make_float(old_fraction));
old_leaf.set_normal_cols(Value::make_float(1.0));
new_leaf.set_normal_lines(Value::make_float(new_fraction));
new_leaf.set_normal_cols(Value::make_float(1.0));
}
}
*tree = Window::Internal {
id: internal_id,
direction,
children: vec![old_leaf, new_leaf],
bounds: old_bounds,
parameters: Vec::new(),
combination_limit: false,
new_pixel: None,
new_total: None,
new_normal: Value::NIL,
normal_lines: inherited_normal_lines,
normal_cols: inherited_normal_cols,
};
return Some(());
}
}
if let Window::Internal { children, .. } = tree {
for child in children {
if split_window_in_tree(
child,
target,
direction,
internal_id,
new_id,
new_buffer_id,
size,
)
.is_some()
{
return Some(());
}
}
}
None
}
fn delete_window_in_tree(tree: &mut Window, target: WindowId) -> bool {
if let Window::Internal {
children, bounds, ..
} = tree
{
if let Some(idx) = children.iter().position(|c| c.id() == target) {
children.remove(idx);
if children.len() == 1 {
let mut remaining = children.pop().unwrap();
remaining.set_bounds(*bounds);
*tree = remaining;
} else {
redistribute_bounds(children, *bounds);
}
return true;
}
for child in children {
if delete_window_in_tree(child, target) {
return true;
}
}
}
false
}
fn find_parent_in_tree(node: &Window, target: WindowId) -> Option<WindowId> {
let Window::Internal { children, .. } = node else {
return None;
};
for child in children {
if child.id() == target {
return Some(node.id());
}
if let Some(parent) = find_parent_in_tree(child, target) {
return Some(parent);
}
}
None
}
fn find_sibling_in_tree(node: &Window, target: WindowId, next: bool) -> Option<WindowId> {
let Window::Internal { children, .. } = node else {
return None;
};
if let Some(index) = children.iter().position(|child| child.id() == target) {
let sibling = if next {
children.get(index + 1)
} else {
index.checked_sub(1).and_then(|idx| children.get(idx))
};
return sibling.map(Window::id);
}
children
.iter()
.find_map(|child| find_sibling_in_tree(child, target, next))
}
fn find_first_child_in_tree(
node: &Window,
target: WindowId,
direction: SplitDirection,
) -> Option<WindowId> {
match node {
Window::Leaf { .. } => None,
Window::Internal {
id,
direction: node_direction,
children,
..
} => {
if *id == target {
return (*node_direction == direction)
.then(|| children.first().map(Window::id))
.flatten();
}
children
.iter()
.find_map(|child| find_first_child_in_tree(child, target, direction))
}
}
}
pub fn window_parent_id(frame: &Frame, window_id: WindowId) -> Option<WindowId> {
if frame.minibuffer_window == Some(window_id) {
return None;
}
find_parent_in_tree(&frame.root_window, window_id)
}
pub fn window_first_child_id(
frame: &Frame,
window_id: WindowId,
direction: SplitDirection,
) -> Option<WindowId> {
if frame.minibuffer_window == Some(window_id) {
return None;
}
find_first_child_in_tree(&frame.root_window, window_id, direction)
}
pub fn window_next_sibling_id(frame: &Frame, window_id: WindowId) -> Option<WindowId> {
if frame.minibuffer_window == Some(window_id) {
return None;
}
find_sibling_in_tree(&frame.root_window, window_id, true)
}
pub fn window_prev_sibling_id(frame: &Frame, window_id: WindowId) -> Option<WindowId> {
if frame.minibuffer_window == Some(window_id) {
return None;
}
find_sibling_in_tree(&frame.root_window, window_id, false)
}
pub fn window_resize_apply(
window: &mut Window,
horflag: bool,
_char_width: f32,
_char_height: f32,
) {
let new_px = window.new_pixel();
let bounds = *window.bounds();
if let Some(px) = new_px {
let px = px.max(0) as f32;
if horflag {
window.set_bounds(Rect::new(bounds.x, bounds.y, px, bounds.height));
} else {
window.set_bounds(Rect::new(bounds.x, bounds.y, bounds.width, px));
}
window.set_new_pixel(None);
}
let pending_normal = window.new_normal();
if !pending_normal.is_nil() {
if horflag {
window.set_normal_cols(pending_normal);
} else {
window.set_normal_lines(pending_normal);
}
window.set_new_normal(Value::NIL);
}
let bounds = *window.bounds();
let edge = if horflag { bounds.x } else { bounds.y };
if let Window::Internal {
direction,
children,
..
} = window
{
let mut edge = edge;
let dir = *direction;
for child in children.iter_mut() {
let cb = *child.bounds();
if horflag {
child.set_bounds(Rect::new(edge, cb.y, cb.width, cb.height));
} else {
child.set_bounds(Rect::new(cb.x, edge, cb.width, cb.height));
}
window_resize_apply(child, horflag, _char_width, _char_height);
let child_bounds = *child.bounds();
match (dir, horflag) {
(SplitDirection::Horizontal, true) => edge += child_bounds.width,
(SplitDirection::Vertical, false) => edge += child_bounds.height,
_ => {}
}
}
}
}
pub fn window_resize_check(window: &Window, horflag: bool) -> bool {
let my_new = window.new_pixel().unwrap_or_else(|| {
let b = window.bounds();
if horflag {
b.width as i64
} else {
b.height as i64
}
});
match window {
Window::Leaf { .. } => true,
Window::Internal {
direction,
children,
..
} => {
let combines = (*direction == SplitDirection::Horizontal) == horflag;
if combines {
let child_sum: i64 = children
.iter()
.map(|c| {
c.new_pixel().unwrap_or_else(|| {
let b = c.bounds();
if horflag {
b.width as i64
} else {
b.height as i64
}
})
})
.sum();
if child_sum != my_new {
return false;
}
}
children.iter().all(|c| window_resize_check(c, horflag))
}
}
}
pub fn window_resize_apply_total(
window: &mut Window,
horflag: bool,
char_width: f32,
char_height: f32,
) {
let new_total = window.new_total();
let bounds = *window.bounds();
if let Some(total) = new_total {
let total = total.max(0) as f32;
if horflag {
let px = total * char_width;
window.set_bounds(Rect::new(bounds.x, bounds.y, px, bounds.height));
} else {
let px = total * char_height;
window.set_bounds(Rect::new(bounds.x, bounds.y, bounds.width, px));
}
window.set_new_total(None);
}
let bounds = *window.bounds();
let edge = if horflag { bounds.x } else { bounds.y };
if let Window::Internal {
direction,
children,
..
} = window
{
let mut edge = edge;
let dir = *direction;
for child in children.iter_mut() {
let cb = *child.bounds();
if horflag {
child.set_bounds(Rect::new(edge, cb.y, cb.width, cb.height));
} else {
child.set_bounds(Rect::new(cb.x, edge, cb.width, cb.height));
}
window_resize_apply_total(child, horflag, char_width, char_height);
let child_bounds = *child.bounds();
match (dir, horflag) {
(SplitDirection::Horizontal, true) => edge += child_bounds.width,
(SplitDirection::Vertical, false) => edge += child_bounds.height,
_ => {}
}
}
}
}
fn redistribute_bounds(children: &mut [Window], parent: Rect) {
if children.is_empty() {
return;
}
fn distributed_sizes(total: f32, n: usize) -> Vec<f32> {
let total_px = total.round().max(0.0) as i64;
let n = n as i64;
let base = total_px / n;
let remainder = total_px % n;
(0..n)
.map(|idx| (base + if idx < remainder { 1 } else { 0 }) as f32)
.collect()
}
if children.len() >= 2 {
let first = children[0].bounds();
let second = children[1].bounds();
if (first.x - second.x).abs() > 0.1 {
let widths = distributed_sizes(parent.width, children.len());
let mut edge = parent.x.round();
for (child, width) in children.iter_mut().zip(widths.into_iter()) {
child.set_bounds(Rect::new(
edge,
parent.y.round(),
width,
parent.height.round(),
));
edge += width;
}
} else {
let heights = distributed_sizes(parent.height, children.len());
let mut edge = parent.y.round();
for (child, height) in children.iter_mut().zip(heights.into_iter()) {
child.set_bounds(Rect::new(
parent.x.round(),
edge,
parent.width.round(),
height,
));
edge += height;
}
}
} else {
children[0].set_bounds(parent);
}
}
fn resize_window_subtree(window: &mut Window, bounds: Rect) {
window.set_bounds(bounds);
if let Window::Internal { children, .. } = window {
redistribute_bounds(children, bounds);
for child in children {
let child_bounds = *child.bounds();
resize_window_subtree(child, child_bounds);
}
}
}
impl GcTrace for FrameManager {
fn trace_roots(&self, roots: &mut Vec<Value>) {
for params in self.deleted_window_parameters.values() {
for (k, v) in params {
roots.push(*k);
roots.push(*v);
}
}
for frame in self.frames.values() {
roots.push(frame.name);
roots.push(frame.icon_name);
roots.push(frame.focus_frame);
roots.push(frame.title);
roots.extend(frame.parameters.keys().copied());
for v in frame.parameters.values() {
roots.push(*v);
}
roots.push(frame.face_hash_table);
roots.extend(frame.realized_faces.keys().copied());
trace_window(&frame.root_window, roots);
if let Some(mb) = &frame.minibuffer_leaf {
trace_window(mb, roots);
}
}
}
}
fn trace_window(window: &Window, roots: &mut Vec<Value>) {
match window {
Window::Leaf { display, .. } => {
for (key, value) in window.parameters() {
roots.push(*key);
roots.push(*value);
}
if let Some(history) = window.history() {
roots.push(history.prev_buffers);
roots.push(history.next_buffers);
}
roots.push(display.display_table);
roots.push(display.cursor_type);
roots.push(display.vertical_scroll_bar_type);
roots.push(display.horizontal_scroll_bar_type);
}
Window::Internal { children, .. } => {
for (key, value) in window.parameters() {
roots.push(*key);
roots.push(*value);
}
for child in children {
trace_window(child, roots);
}
}
}
}
#[cfg(test)]
#[path = "window_test.rs"]
mod tests;