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;
#[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 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,
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,
}
}
}
#[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, 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,
},
Internal {
id: WindowId,
direction: SplitDirection,
children: Vec<Window>,
bounds: Rect,
parameters: WindowParameters,
combination_limit: bool,
},
}
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(),
}
}
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, ..
} => {
*window_end_valid = false;
}
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_buffer_pos: Option<usize>,
pub end_buffer_pos: Option<usize>,
}
#[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 points: Vec<DisplayPointSnapshot>,
pub rows: Vec<DisplayRowSnapshot>,
}
impl WindowDisplaySnapshot {
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,
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,
}
pub struct Frame {
pub id: FrameId,
pub name: String,
pub terminal_id: u64,
pub root_window: Window,
pub selected_window: WindowId,
pub minibuffer_window: Option<WindowId>,
pub minibuffer_leaf: Option<Window>,
pub width: u32,
pub height: u32,
pub window_system: Option<Value>,
pub parameters: HashMap<String, Value>,
pub visible: bool,
pub title: String,
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 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<String, RuntimeFace>,
}
impl Frame {
pub fn new(
id: FrameId,
name: String,
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,
terminal_id,
root_window,
selected_window: selected,
minibuffer_window: Some(minibuffer_window),
minibuffer_leaf: Some(minibuffer_leaf),
width,
height,
window_system: None,
parameters: HashMap::new(),
visible: true,
title: String::new(),
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,
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(),
}
}
pub fn recalculate_minibuffer_bounds(&mut self) {
self.sync_window_area_bounds();
}
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.parameters.get("window-system").copied())
}
pub fn set_window_system(&mut self, window_system: Option<Value>) {
self.window_system = window_system;
match window_system {
Some(value) => {
self.parameters.insert("window-system".to_string(), value);
}
None => {
self.parameters.remove("window-system");
}
}
}
pub fn frame_parameter_int(&self, key: &str) -> Option<i64> {
self.parameters.get(key).and_then(|v| v.as_int())
}
pub fn realized_face(&self, name: &str) -> Option<&RuntimeFace> {
self.realized_faces.get(name)
}
pub fn face_hash_table(&self) -> Value {
self.face_hash_table
}
pub fn set_realized_face(&mut self, name: String, 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();
});
}
}
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 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);
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 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()
}
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.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.width = width;
self.height = height;
self.sync_window_area_bounds();
let char_width = self.char_width.max(1.0);
let char_height = self.char_height.max(1.0);
let root_height = self.root_window.bounds().height;
let cols = ((width as f32) / 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.parameters
.insert("width".to_string(), Value::fixnum(cols));
self.parameters
.insert("height".to_string(), Value::fixnum(total_lines));
}
}
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_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 {
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.to_string(), 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) {
self.selected = Some(id);
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_wid) = frame.minibuffer_window {
self.deleted_windows.insert(minibuffer_wid);
if let Some(window) = frame.find_window(minibuffer_wid) {
self.deleted_window_parameters
.insert(minibuffer_wid, window.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 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)| {
if frame.minibuffer_window == Some(window_id) {
return Some(*frame_id);
}
frame.find_window(window_id).and_then(|window| {
if window.is_leaf() {
Some(*frame_id)
} else {
None
}
})
})
}
pub fn find_valid_window_frame_id(&self, window_id: WindowId) -> Option<FrameId> {
self.frames.iter().find_map(|(frame_id, frame)| {
if frame.minibuffer_window == Some(window_id) {
return Some(*frame_id);
}
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)
}
}
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<()> {
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 total = old_bounds.width;
let new_size = match size {
Some(n) if n > 0 => (n as f32).min(total - 1.0).max(1.0),
Some(n) if n < 0 => (total - (-n) as f32).max(1.0).min(total - 1.0),
_ => total / 2.0,
};
let old_size = total - new_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 total = old_bounds.height;
let new_size = match size {
Some(n) if n > 0 => (n as f32).min(total - 1.0).max(1.0),
Some(n) if n < 0 => (total - (-n) as f32).max(1.0).min(total - 1.0),
_ => total / 2.0,
};
let old_size = total - new_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;
}
*tree = Window::Internal {
id: internal_id,
direction,
children: vec![old_leaf, new_leaf],
bounds: old_bounds,
parameters: Vec::new(),
combination_limit: false,
};
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,
new_pixel_map: &HashMap<u64, i64>,
new_normal_map: &HashMap<u64, f64>,
char_width: f32,
char_height: f32,
) {
let wid = window.id().0;
let new_px = new_pixel_map.get(&wid).copied();
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));
}
}
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,
new_pixel_map,
new_normal_map,
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,
new_pixel_map: &HashMap<u64, i64>,
) -> bool {
let wid = window.id().0;
let my_new = new_pixel_map.get(&wid).copied().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| {
let cid = c.id().0;
new_pixel_map.get(&cid).copied().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, new_pixel_map))
}
}
}
pub fn window_resize_apply_total(
window: &mut Window,
horflag: bool,
new_total_map: &HashMap<u64, i64>,
char_width: f32,
char_height: f32,
) {
let wid = window.id().0;
let new_total = new_total_map.get(&wid).copied();
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));
}
}
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, new_total_map, 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;
}
let n = children.len() as f32;
if children.len() >= 2 {
let first = children[0].bounds();
let second = children[1].bounds();
if (first.x - second.x).abs() > 0.1 {
let w = parent.width / n;
for (i, child) in children.iter_mut().enumerate() {
child.set_bounds(Rect::new(
parent.x + i as f32 * w,
parent.y,
w,
parent.height,
));
}
} else {
let h = parent.height / n;
for (i, child) in children.iter_mut().enumerate() {
child.set_bounds(Rect::new(
parent.x,
parent.y + i as f32 * h,
parent.width,
h,
));
}
}
} 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() {
for v in frame.parameters.values() {
roots.push(*v);
}
roots.push(frame.face_hash_table);
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)]
mod tests {
use super::*;
#[test]
fn create_frame_and_window() {
crate::test_utils::init_test_tracing();
let mut mgr = FrameManager::new();
let fid = mgr.create_frame("F1", 800, 600, BufferId(1));
let frame = mgr.get(fid).unwrap();
assert_eq!(frame.window_count(), 1);
assert!(frame.selected_window().is_some());
assert!(frame.selected_window().unwrap().is_leaf());
}
#[test]
fn split_window_horizontal() {
crate::test_utils::init_test_tracing();
let mut mgr = FrameManager::new();
let fid = mgr.create_frame("F1", 800, 600, BufferId(1));
let wid = mgr.get(fid).unwrap().window_list()[0];
let new_wid = mgr.split_window(fid, wid, SplitDirection::Horizontal, BufferId(2), None);
assert!(new_wid.is_some());
let frame = mgr.get(fid).unwrap();
assert_eq!(frame.window_count(), 2);
}
#[test]
fn split_window_vertical() {
crate::test_utils::init_test_tracing();
let mut mgr = FrameManager::new();
let fid = mgr.create_frame("F1", 800, 600, BufferId(1));
let wid = mgr.get(fid).unwrap().window_list()[0];
let new_wid = mgr.split_window(fid, wid, SplitDirection::Vertical, BufferId(2), None);
assert!(new_wid.is_some());
let frame = mgr.get(fid).unwrap();
assert_eq!(frame.window_count(), 2);
}
#[test]
fn split_window_copies_window_display_state() {
crate::test_utils::init_test_tracing();
let mut mgr = FrameManager::new();
let fid = mgr.create_frame("F1", 800, 600, BufferId(1));
{
let frame = mgr.get_mut(fid).unwrap();
frame.set_window_system(Some(Value::symbol("neo")));
let wid = frame.window_list()[0];
let display = frame
.find_window_mut(wid)
.and_then(Window::display_mut)
.expect("leaf display");
display.display_table = Value::fixnum(17);
display.cursor_type = Value::NIL;
display.left_fringe_width = 3;
display.right_fringe_width = 5;
display.fringes_outside_margins = true;
display.fringes_persistent = true;
display.scroll_bar_width = 11;
display.vertical_scroll_bar_type = Value::T;
display.scroll_bar_height = 7;
display.horizontal_scroll_bar_type = Value::NIL;
display.scroll_bars_persistent = true;
}
let original_wid = mgr.get(fid).unwrap().window_list()[0];
let new_wid = mgr
.split_window(
fid,
original_wid,
SplitDirection::Horizontal,
BufferId(2),
None,
)
.expect("split");
let frame = mgr.get(fid).unwrap();
let original_display = frame
.find_window(original_wid)
.and_then(Window::display)
.expect("original display");
let new_display = frame
.find_window(new_wid)
.and_then(Window::display)
.expect("new display");
assert_eq!(original_display.display_table, Value::fixnum(17));
assert_eq!(new_display.display_table, Value::fixnum(17));
assert_eq!(original_display.cursor_type, Value::NIL);
assert_eq!(new_display.cursor_type, Value::NIL);
assert_eq!(original_display.left_fringe_width, 3);
assert_eq!(new_display.left_fringe_width, 3);
assert_eq!(original_display.right_fringe_width, 5);
assert_eq!(new_display.right_fringe_width, 5);
assert!(original_display.fringes_outside_margins);
assert!(new_display.fringes_outside_margins);
assert!(original_display.fringes_persistent);
assert!(new_display.fringes_persistent);
assert_eq!(original_display.scroll_bar_width, 11);
assert_eq!(new_display.scroll_bar_width, 11);
assert_eq!(original_display.vertical_scroll_bar_type, Value::T);
assert_eq!(new_display.vertical_scroll_bar_type, Value::T);
assert_eq!(original_display.scroll_bar_height, 7);
assert_eq!(new_display.scroll_bar_height, 7);
assert_eq!(original_display.horizontal_scroll_bar_type, Value::NIL);
assert_eq!(new_display.horizontal_scroll_bar_type, Value::NIL);
assert!(original_display.scroll_bars_persistent);
assert!(new_display.scroll_bars_persistent);
}
#[test]
fn split_window_resets_new_leaf_vscroll_state() {
crate::test_utils::init_test_tracing();
let mut mgr = FrameManager::new();
let fid = mgr.create_frame("F1", 800, 600, BufferId(1));
let original_wid = mgr.get(fid).unwrap().window_list()[0];
if let Some(Window::Leaf {
vscroll,
preserve_vscroll_p,
..
}) = mgr
.get_mut(fid)
.and_then(|frame| frame.find_window_mut(original_wid))
{
*vscroll = -19;
*preserve_vscroll_p = true;
}
let new_wid = mgr
.split_window(
fid,
original_wid,
SplitDirection::Horizontal,
BufferId(2),
None,
)
.expect("split");
let frame = mgr.get(fid).unwrap();
let Window::Leaf {
vscroll: original_vscroll,
preserve_vscroll_p: original_preserve,
..
} = frame.find_window(original_wid).unwrap()
else {
panic!("expected original leaf");
};
let Window::Leaf {
vscroll: new_vscroll,
preserve_vscroll_p: new_preserve,
..
} = frame.find_window(new_wid).unwrap()
else {
panic!("expected new leaf");
};
assert_eq!(*original_vscroll, -19);
assert!(*original_preserve);
assert_eq!(*new_vscroll, 0);
assert!(!*new_preserve);
}
#[test]
fn delete_window() {
crate::test_utils::init_test_tracing();
let mut mgr = FrameManager::new();
let fid = mgr.create_frame("F1", 800, 600, BufferId(1));
let wid = mgr.get(fid).unwrap().window_list()[0];
let new_wid = mgr
.split_window(fid, wid, SplitDirection::Horizontal, BufferId(2), None)
.unwrap();
assert!(mgr.delete_window(fid, new_wid));
assert_eq!(mgr.get(fid).unwrap().window_count(), 1);
}
#[test]
fn cannot_delete_last_window() {
crate::test_utils::init_test_tracing();
let mut mgr = FrameManager::new();
let fid = mgr.create_frame("F1", 800, 600, BufferId(1));
let wid = mgr.get(fid).unwrap().window_list()[0];
assert!(!mgr.delete_window(fid, wid));
}
#[test]
fn select_window() {
crate::test_utils::init_test_tracing();
let mut mgr = FrameManager::new();
let fid = mgr.create_frame("F1", 800, 600, BufferId(1));
let wid = mgr.get(fid).unwrap().window_list()[0];
let new_wid = mgr
.split_window(fid, wid, SplitDirection::Horizontal, BufferId(2), None)
.unwrap();
assert!(mgr.get_mut(fid).unwrap().select_window(new_wid));
assert_eq!(mgr.get(fid).unwrap().selected_window.0, new_wid.0,);
}
#[test]
fn window_at_coordinates() {
crate::test_utils::init_test_tracing();
let mut mgr = FrameManager::new();
let fid = mgr.create_frame("F1", 800, 600, BufferId(1));
let wid = mgr.get(fid).unwrap().window_list()[0];
mgr.split_window(fid, wid, SplitDirection::Horizontal, BufferId(2), None);
let frame = mgr.get(fid).unwrap();
let left = frame.window_at(100.0, 300.0);
assert!(left.is_some());
let right = frame.window_at(600.0, 300.0);
assert!(right.is_some());
assert_ne!(left, right);
}
#[test]
fn frame_columns_and_lines() {
crate::test_utils::init_test_tracing();
let mut mgr = FrameManager::new();
let fid = mgr.create_frame("F1", 800, 600, BufferId(1));
let frame = mgr.get(fid).unwrap();
assert_eq!(frame.columns(), 100); assert_eq!(frame.lines(), 37); }
#[test]
fn delete_frame() {
crate::test_utils::init_test_tracing();
let mut mgr = FrameManager::new();
let fid = mgr.create_frame("F1", 800, 600, BufferId(1));
assert!(mgr.delete_frame(fid));
assert!(mgr.get(fid).is_none());
}
#[test]
fn multiple_frames() {
crate::test_utils::init_test_tracing();
let mut mgr = FrameManager::new();
let f1 = mgr.create_frame("F1", 800, 600, BufferId(1));
let f2 = mgr.create_frame("F2", 1024, 768, BufferId(2));
assert_eq!(mgr.frame_list().len(), 2);
assert!(mgr.select_frame(f2));
assert_eq!(mgr.selected_frame().unwrap().id, f2);
mgr.delete_frame(f1);
assert_eq!(mgr.frame_list().len(), 1);
}
#[test]
fn rect_contains() {
crate::test_utils::init_test_tracing();
let r = Rect::new(10.0, 20.0, 100.0, 50.0);
assert!(r.contains(10.0, 20.0));
assert!(r.contains(50.0, 40.0));
assert!(!r.contains(9.0, 20.0));
assert!(!r.contains(110.0, 70.0));
}
#[test]
fn find_window_frame_id() {
crate::test_utils::init_test_tracing();
let mut mgr = FrameManager::new();
let fid = mgr.create_frame("F1", 800, 600, BufferId(1));
let wid = mgr.get(fid).unwrap().window_list()[0];
assert_eq!(mgr.find_window_frame_id(wid), Some(fid));
assert_eq!(mgr.find_window_frame_id(WindowId(99999)), None);
}
#[test]
fn is_live_window_id() {
crate::test_utils::init_test_tracing();
let mut mgr = FrameManager::new();
let fid = mgr.create_frame("F1", 800, 600, BufferId(1));
let wid = mgr.get(fid).unwrap().window_list()[0];
assert!(mgr.is_live_window_id(wid));
assert!(!mgr.is_live_window_id(WindowId(99999)));
}
#[test]
fn window_parameters() {
crate::test_utils::init_test_tracing();
let mut mgr = FrameManager::new();
let fid = mgr.create_frame("F1", 800, 600, BufferId(1));
let wid = mgr.get(fid).unwrap().window_list()[0];
let key = Value::symbol("my-param");
let val = Value::fixnum(42);
assert!(mgr.window_parameter(wid, &key).is_none());
mgr.set_window_parameter(wid, key, val);
assert_eq!(mgr.window_parameter(wid, &key), Some(Value::fixnum(42)));
}
#[test]
fn split_window_does_not_copy_window_parameters() {
crate::test_utils::init_test_tracing();
let mut mgr = FrameManager::new();
let fid = mgr.create_frame("F1", 800, 600, BufferId(1));
let wid = mgr.get(fid).unwrap().window_list()[0];
let key = Value::symbol("my-param");
mgr.set_window_parameter(wid, key, Value::fixnum(42));
let new_wid = mgr
.split_window(fid, wid, SplitDirection::Horizontal, BufferId(2), None)
.expect("split");
assert_eq!(mgr.window_parameter(wid, &key), Some(Value::fixnum(42)));
assert_eq!(mgr.window_parameter(new_wid, &key), None);
}
#[test]
fn deleted_window_retains_window_parameters() {
crate::test_utils::init_test_tracing();
let mut mgr = FrameManager::new();
let fid = mgr.create_frame("F1", 800, 600, BufferId(1));
let wid = mgr.get(fid).unwrap().window_list()[0];
let other = mgr
.split_window(fid, wid, SplitDirection::Horizontal, BufferId(2), None)
.expect("split");
let key = Value::symbol("deleted-param");
mgr.set_window_parameter(other, key, Value::fixnum(7));
assert!(mgr.delete_window(fid, other));
assert_eq!(mgr.window_parameter(other, &key), Some(Value::fixnum(7)));
}
#[test]
fn replace_buffer_in_windows() {
crate::test_utils::init_test_tracing();
let mut mgr = FrameManager::new();
let fid = mgr.create_frame("F1", 800, 600, BufferId(1));
let wid = mgr.get(fid).unwrap().window_list()[0];
let frame = mgr.get(fid).unwrap();
assert_eq!(
frame.find_window(wid).unwrap().buffer_id(),
Some(BufferId(1))
);
mgr.replace_buffer_in_windows(BufferId(1), BufferId(2));
let frame = mgr.get(fid).unwrap();
assert_eq!(
frame.find_window(wid).unwrap().buffer_id(),
Some(BufferId(2))
);
}
#[test]
fn deep_split_and_delete() {
crate::test_utils::init_test_tracing();
let mut mgr = FrameManager::new();
let fid = mgr.create_frame("F1", 800, 600, BufferId(1));
let w1 = mgr.get(fid).unwrap().window_list()[0];
let w2 = mgr
.split_window(fid, w1, SplitDirection::Horizontal, BufferId(2), None)
.unwrap();
let w3 = mgr
.split_window(fid, w2, SplitDirection::Vertical, BufferId(3), None)
.unwrap();
assert_eq!(mgr.get(fid).unwrap().window_count(), 3);
assert!(mgr.delete_window(fid, w3));
assert_eq!(mgr.get(fid).unwrap().window_count(), 2);
assert!(mgr.delete_window(fid, w2));
assert_eq!(mgr.get(fid).unwrap().window_count(), 1);
assert!(!mgr.delete_window(fid, w1));
}
#[test]
fn note_window_selected_updates_use_time() {
crate::test_utils::init_test_tracing();
let mut mgr = FrameManager::new();
let fid = mgr.create_frame("F1", 800, 600, BufferId(1));
let w1 = mgr.get(fid).unwrap().window_list()[0];
let w2 = mgr
.split_window(fid, w1, SplitDirection::Horizontal, BufferId(2), None)
.unwrap();
let t1 = mgr.note_window_selected(w1);
let t2 = mgr.note_window_selected(w2);
assert!(t2 > t1);
}
#[test]
fn window_set_buffer_resets_position() {
crate::test_utils::init_test_tracing();
let mut mgr = FrameManager::new();
let fid = mgr.create_frame("F1", 800, 600, BufferId(1));
let wid = mgr.get(fid).unwrap().window_list()[0];
let frame = mgr.get_mut(fid).unwrap();
if let Some(w) = frame.find_window_mut(wid) {
if let Window::Leaf { point, .. } = w {
*point = 100;
}
}
let frame = mgr.get_mut(fid).unwrap();
if let Some(w) = frame.find_window_mut(wid) {
w.set_buffer(BufferId(2));
}
let frame = mgr.get(fid).unwrap();
let w = frame.find_window(wid).unwrap();
if let Window::Leaf {
point, buffer_id, ..
} = w
{
assert_eq!(*buffer_id, BufferId(2));
assert_eq!(*point, 1);
}
}
#[test]
fn frame_resize_pixelwise_updates_window_tree_and_invalidates_display_state() {
crate::test_utils::init_test_tracing();
let mut mgr = FrameManager::new();
let fid = mgr.create_frame("F1", 800, 600, BufferId(1));
let w1 = mgr.get(fid).unwrap().window_list()[0];
let w2 = mgr
.split_window(fid, w1, SplitDirection::Horizontal, BufferId(2), None)
.unwrap();
let frame = mgr.get_mut(fid).unwrap();
frame.char_width = 10.0;
frame.char_height = 20.0;
frame.replace_display_snapshots(vec![WindowDisplaySnapshot {
window_id: w1,
..WindowDisplaySnapshot::default()
}]);
frame
.find_window_mut(w1)
.unwrap()
.set_window_end_from_positions(200, 200, 50, 50, 3);
frame
.find_window_mut(w2)
.unwrap()
.set_window_end_from_positions(200, 200, 60, 60, 3);
frame.resize_pixelwise(400, 260);
assert_eq!(frame.width, 400);
assert_eq!(frame.height, 260);
assert!(frame.display_snapshots.is_empty());
assert_eq!(frame.parameters.get("width"), Some(&Value::fixnum(40)));
assert_eq!(frame.parameters.get("height"), Some(&Value::fixnum(13)));
let root_bounds = *frame.root_window.bounds();
assert_eq!(root_bounds, Rect::new(0.0, 0.0, 400.0, 244.0));
let mini_bounds = *frame.minibuffer_leaf.as_ref().unwrap().bounds();
assert_eq!(mini_bounds, Rect::new(0.0, 244.0, 400.0, 16.0));
assert_eq!(
frame.find_window(w1).unwrap().bounds(),
&Rect::new(0.0, 0.0, 200.0, 244.0)
);
assert_eq!(
frame.find_window(w2).unwrap().bounds(),
&Rect::new(200.0, 0.0, 200.0, 244.0)
);
assert_eq!(
frame.find_window(w1).unwrap().window_end_valid(),
Some(false)
);
assert_eq!(
frame.find_window(w2).unwrap().window_end_valid(),
Some(false)
);
assert_eq!(
frame.minibuffer_leaf.as_ref().unwrap().window_end_valid(),
Some(false)
);
}
#[test]
fn frame_resize_pixelwise_reserves_tab_bar_height_above_root_window_tree() {
crate::test_utils::init_test_tracing();
let mut mgr = FrameManager::new();
let fid = mgr.create_frame("F1", 800, 600, BufferId(1));
let frame = mgr.get_mut(fid).unwrap();
frame.char_width = 10.0;
frame.char_height = 20.0;
frame
.parameters
.insert("tab-bar-lines".to_string(), Value::fixnum(1));
frame.sync_tab_bar_height_from_parameters();
frame.resize_pixelwise(400, 260);
assert_eq!(frame.tab_bar_height, 20);
assert_eq!(
*frame.root_window.bounds(),
Rect::new(0.0, 20.0, 400.0, 224.0)
);
assert_eq!(
*frame.minibuffer_leaf.as_ref().unwrap().bounds(),
Rect::new(0.0, 244.0, 400.0, 16.0)
);
assert_eq!(frame.parameters.get("height"), Some(&Value::fixnum(12)));
}
}