#[path = "insdel.rs"]
mod insdel;
use std::collections::{HashMap, HashSet};
use std::sync::OnceLock;
use super::buffer_text::BufferText;
use super::overlay::OverlayList;
use super::shared::SharedUndoState;
use super::text_props::TextPropertyTable;
use super::undo;
use crate::emacs_core::intern::{SymId, intern};
use crate::emacs_core::value::{RuntimeBindingValue, Value, ValueKind};
use crate::gc_trace::GcTrace;
use crate::tagged::gc::with_tagged_heap;
use crate::window::WindowId;
use rustc_hash::FxHashMap;
pub const BUFFER_SLOT_COUNT: usize = 64;
pub const BUFFER_SLOT_FILE_NAME: usize = 0;
pub const BUFFER_SLOT_AUTO_SAVE_FILE_NAME: usize = 1;
pub const BUFFER_SLOT_READ_ONLY: usize = 2;
pub const BUFFER_SLOT_ENABLE_MULTIBYTE_CHARACTERS: usize = 3;
pub const BUFFER_SLOT_FILE_TRUENAME: usize = 4;
pub const BUFFER_SLOT_DEFAULT_DIRECTORY: usize = 5;
pub const BUFFER_SLOT_SAVED_SIZE: usize = 6;
pub const BUFFER_SLOT_BACKED_UP: usize = 7;
pub const BUFFER_SLOT_FILE_FORMAT: usize = 8;
pub const BUFFER_SLOT_AUTO_SAVE_FILE_FORMAT: usize = 9;
pub const BUFFER_SLOT_MAJOR_MODE: usize = 10;
pub const BUFFER_SLOT_LOCAL_MINOR_MODES: usize = 11;
pub const BUFFER_SLOT_MODE_NAME: usize = 12;
pub const BUFFER_SLOT_MARK_ACTIVE: usize = 13;
pub const BUFFER_SLOT_POINT_BEFORE_SCROLL: usize = 14;
pub const BUFFER_SLOT_DISPLAY_COUNT: usize = 15;
pub const BUFFER_SLOT_DISPLAY_TIME: usize = 16;
pub const BUFFER_SLOT_INVISIBILITY_SPEC: usize = 17;
pub const BUFFER_SLOT_FILL_COLUMN: usize = 18;
pub const BUFFER_SLOT_TAB_WIDTH: usize = 19;
pub const BUFFER_SLOT_LEFT_MARGIN: usize = 20;
pub const BUFFER_SLOT_ABBREV_MODE: usize = 21;
pub const BUFFER_SLOT_OVERWRITE_MODE: usize = 22;
pub const BUFFER_SLOT_SELECTIVE_DISPLAY: usize = 23;
pub const BUFFER_SLOT_SELECTIVE_DISPLAY_ELLIPSES: usize = 24;
pub const BUFFER_SLOT_TRUNCATE_LINES: usize = 25;
pub const BUFFER_SLOT_WORD_WRAP: usize = 26;
pub const BUFFER_SLOT_CTL_ARROW: usize = 27;
pub const BUFFER_SLOT_AUTO_FILL_FUNCTION: usize = 28;
pub const BUFFER_SLOT_MODE_LINE_FORMAT: usize = 29;
pub const BUFFER_SLOT_HEADER_LINE_FORMAT: usize = 30;
pub const BUFFER_SLOT_TAB_LINE_FORMAT: usize = 31;
pub const BUFFER_SLOT_BIDI_DISPLAY_REORDERING: usize = 32;
pub const BUFFER_SLOT_BIDI_PARAGRAPH_DIRECTION: usize = 33;
pub const BUFFER_SLOT_BIDI_PARAGRAPH_START_RE: usize = 34;
pub const BUFFER_SLOT_BIDI_PARAGRAPH_SEPARATE_RE: usize = 35;
pub const BUFFER_SLOT_CURSOR_TYPE: usize = 36;
pub const BUFFER_SLOT_LINE_SPACING: usize = 37;
pub const BUFFER_SLOT_TEXT_CONVERSION_STYLE: usize = 38;
pub const BUFFER_SLOT_CURSOR_IN_NON_SELECTED_WINDOWS: usize = 39;
pub const BUFFER_SLOT_LEFT_MARGIN_WIDTH: usize = 40;
pub const BUFFER_SLOT_RIGHT_MARGIN_WIDTH: usize = 41;
pub const BUFFER_SLOT_LEFT_FRINGE_WIDTH: usize = 42;
pub const BUFFER_SLOT_RIGHT_FRINGE_WIDTH: usize = 43;
pub const BUFFER_SLOT_FRINGES_OUTSIDE_MARGINS: usize = 44;
pub const BUFFER_SLOT_SCROLL_BAR_WIDTH: usize = 45;
pub const BUFFER_SLOT_SCROLL_BAR_HEIGHT: usize = 46;
pub const BUFFER_SLOT_VERTICAL_SCROLL_BAR: usize = 47;
pub const BUFFER_SLOT_HORIZONTAL_SCROLL_BAR: usize = 48;
pub const BUFFER_SLOT_INDICATE_EMPTY_LINES: usize = 49;
pub const BUFFER_SLOT_INDICATE_BUFFER_BOUNDARIES: usize = 50;
pub const BUFFER_SLOT_FRINGE_INDICATOR_ALIST: usize = 51;
pub const BUFFER_SLOT_FRINGE_CURSOR_ALIST: usize = 52;
pub const BUFFER_SLOT_SCROLL_UP_AGGRESSIVELY: usize = 53;
pub const BUFFER_SLOT_SCROLL_DOWN_AGGRESSIVELY: usize = 54;
pub const BUFFER_SLOT_CACHE_LONG_SCANS: usize = 55;
pub const BUFFER_SLOT_LOCAL_ABBREV_TABLE: usize = 56;
pub const BUFFER_SLOT_BUFFER_DISPLAY_TABLE: usize = 57;
pub const BUFFER_SLOT_BUFFER_FILE_CODING_SYSTEM: usize = 58;
pub const BUFFER_SLOT_SYNTAX_TABLE: usize = 59;
pub const BUFFER_SLOT_CATEGORY_TABLE: usize = 60;
pub const BUFFER_SLOT_CASE_TABLE: usize = 61;
#[derive(Copy, Clone, Debug)]
pub enum SlotDefault {
Const(crate::emacs_core::value::Value),
LazyFixnum(i64),
LazyString(&'static str),
LazyUnibyte(&'static str),
LazySymbol(&'static str),
LazyCwd,
}
impl SlotDefault {
pub fn to_value(self) -> crate::emacs_core::value::Value {
use crate::emacs_core::value::Value;
match self {
SlotDefault::Const(v) => v,
SlotDefault::LazyFixnum(n) => Value::fixnum(n),
SlotDefault::LazyString(s) => Value::string(s),
SlotDefault::LazyUnibyte(s) => Value::unibyte_string(s),
SlotDefault::LazySymbol(s) => Value::symbol(s),
SlotDefault::LazyCwd => {
let mut s = std::env::current_dir()
.map(|p| p.to_string_lossy().into_owned())
.unwrap_or_else(|_| "/".to_string());
if !s.ends_with('/') {
s.push('/');
}
Value::unibyte_string(s)
}
}
}
}
#[derive(Copy, Clone, Debug)]
pub struct BufferSlotInfo {
pub name: &'static str,
pub offset: usize,
pub default: SlotDefault,
pub predicate: &'static str,
pub reset_on_kill: bool,
pub permanent_local: bool,
pub local_flags_idx: i16,
pub install_as_forwarder: bool,
}
pub const BUFFER_SLOT_INFO: &[BufferSlotInfo] = &[
BufferSlotInfo {
name: "buffer-file-name",
offset: BUFFER_SLOT_FILE_NAME,
default: SlotDefault::Const(crate::emacs_core::value::Value::NIL),
predicate: "stringp",
reset_on_kill: false,
local_flags_idx: -1,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "buffer-auto-save-file-name",
offset: BUFFER_SLOT_AUTO_SAVE_FILE_NAME,
default: SlotDefault::Const(crate::emacs_core::value::Value::NIL),
predicate: "stringp",
reset_on_kill: false,
local_flags_idx: -1,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "buffer-read-only",
offset: BUFFER_SLOT_READ_ONLY,
default: SlotDefault::Const(crate::emacs_core::value::Value::NIL),
predicate: "booleanp",
reset_on_kill: false,
local_flags_idx: -1,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "enable-multibyte-characters",
offset: BUFFER_SLOT_ENABLE_MULTIBYTE_CHARACTERS,
default: SlotDefault::Const(crate::emacs_core::value::Value::T),
predicate: "booleanp",
reset_on_kill: false,
local_flags_idx: -1,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "buffer-file-truename",
offset: BUFFER_SLOT_FILE_TRUENAME,
default: SlotDefault::Const(crate::emacs_core::value::Value::NIL),
predicate: "stringp",
reset_on_kill: false,
local_flags_idx: -1,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "default-directory",
offset: BUFFER_SLOT_DEFAULT_DIRECTORY,
default: SlotDefault::LazyCwd,
predicate: "stringp",
reset_on_kill: false,
local_flags_idx: -1,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "buffer-saved-size",
offset: BUFFER_SLOT_SAVED_SIZE,
default: SlotDefault::LazyFixnum(0),
predicate: "integerp",
reset_on_kill: false,
local_flags_idx: -1,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "buffer-backed-up",
offset: BUFFER_SLOT_BACKED_UP,
default: SlotDefault::Const(crate::emacs_core::value::Value::NIL),
predicate: "booleanp",
reset_on_kill: false,
local_flags_idx: -1,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "buffer-file-format",
offset: BUFFER_SLOT_FILE_FORMAT,
default: SlotDefault::Const(crate::emacs_core::value::Value::NIL),
predicate: "listp",
reset_on_kill: false,
local_flags_idx: -1,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "buffer-auto-save-file-format",
offset: BUFFER_SLOT_AUTO_SAVE_FILE_FORMAT,
default: SlotDefault::Const(crate::emacs_core::value::Value::T),
predicate: "listp",
reset_on_kill: false,
local_flags_idx: -1,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "major-mode",
offset: BUFFER_SLOT_MAJOR_MODE,
default: SlotDefault::LazySymbol("fundamental-mode"),
predicate: "symbolp",
reset_on_kill: true,
local_flags_idx: -1,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "local-minor-modes",
offset: BUFFER_SLOT_LOCAL_MINOR_MODES,
default: SlotDefault::Const(crate::emacs_core::value::Value::NIL),
predicate: "listp",
reset_on_kill: false,
local_flags_idx: -1,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "mode-name",
offset: BUFFER_SLOT_MODE_NAME,
default: SlotDefault::LazyString("Fundamental"),
predicate: "",
reset_on_kill: true,
local_flags_idx: -1,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "mark-active",
offset: BUFFER_SLOT_MARK_ACTIVE,
default: SlotDefault::Const(crate::emacs_core::value::Value::NIL),
predicate: "",
reset_on_kill: false,
local_flags_idx: -1,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "point-before-scroll",
offset: BUFFER_SLOT_POINT_BEFORE_SCROLL,
default: SlotDefault::Const(crate::emacs_core::value::Value::NIL),
predicate: "",
reset_on_kill: false,
local_flags_idx: -1,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "buffer-display-count",
offset: BUFFER_SLOT_DISPLAY_COUNT,
default: SlotDefault::LazyFixnum(0),
predicate: "integerp",
reset_on_kill: false,
local_flags_idx: -1,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "buffer-display-time",
offset: BUFFER_SLOT_DISPLAY_TIME,
default: SlotDefault::Const(crate::emacs_core::value::Value::NIL),
predicate: "",
reset_on_kill: false,
local_flags_idx: -1,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "buffer-invisibility-spec",
offset: BUFFER_SLOT_INVISIBILITY_SPEC,
default: SlotDefault::Const(crate::emacs_core::value::Value::T),
predicate: "",
reset_on_kill: true,
local_flags_idx: -1,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "fill-column",
offset: BUFFER_SLOT_FILL_COLUMN,
default: SlotDefault::LazyFixnum(70),
predicate: "integerp",
reset_on_kill: false,
local_flags_idx: BUFFER_SLOT_FILL_COLUMN as i16,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "tab-width",
offset: BUFFER_SLOT_TAB_WIDTH,
default: SlotDefault::LazyFixnum(8),
predicate: "integerp",
reset_on_kill: false,
local_flags_idx: BUFFER_SLOT_TAB_WIDTH as i16,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "left-margin",
offset: BUFFER_SLOT_LEFT_MARGIN,
default: SlotDefault::LazyFixnum(0),
predicate: "integerp",
reset_on_kill: false,
local_flags_idx: BUFFER_SLOT_LEFT_MARGIN as i16,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "abbrev-mode",
offset: BUFFER_SLOT_ABBREV_MODE,
default: SlotDefault::Const(crate::emacs_core::value::Value::NIL),
predicate: "",
reset_on_kill: false,
local_flags_idx: BUFFER_SLOT_ABBREV_MODE as i16,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "overwrite-mode",
offset: BUFFER_SLOT_OVERWRITE_MODE,
default: SlotDefault::Const(crate::emacs_core::value::Value::NIL),
predicate: "",
reset_on_kill: false,
local_flags_idx: BUFFER_SLOT_OVERWRITE_MODE as i16,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "selective-display",
offset: BUFFER_SLOT_SELECTIVE_DISPLAY,
default: SlotDefault::Const(crate::emacs_core::value::Value::NIL),
predicate: "",
reset_on_kill: false,
local_flags_idx: BUFFER_SLOT_SELECTIVE_DISPLAY as i16,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "selective-display-ellipses",
offset: BUFFER_SLOT_SELECTIVE_DISPLAY_ELLIPSES,
default: SlotDefault::Const(crate::emacs_core::value::Value::T),
predicate: "",
reset_on_kill: false,
local_flags_idx: BUFFER_SLOT_SELECTIVE_DISPLAY_ELLIPSES as i16,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "truncate-lines",
offset: BUFFER_SLOT_TRUNCATE_LINES,
default: SlotDefault::Const(crate::emacs_core::value::Value::NIL),
predicate: "",
reset_on_kill: false,
local_flags_idx: BUFFER_SLOT_TRUNCATE_LINES as i16,
install_as_forwarder: true,
permanent_local: true,
},
BufferSlotInfo {
name: "word-wrap",
offset: BUFFER_SLOT_WORD_WRAP,
default: SlotDefault::Const(crate::emacs_core::value::Value::NIL),
predicate: "",
reset_on_kill: false,
local_flags_idx: BUFFER_SLOT_WORD_WRAP as i16,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "ctl-arrow",
offset: BUFFER_SLOT_CTL_ARROW,
default: SlotDefault::Const(crate::emacs_core::value::Value::T),
predicate: "",
reset_on_kill: false,
local_flags_idx: BUFFER_SLOT_CTL_ARROW as i16,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "auto-fill-function",
offset: BUFFER_SLOT_AUTO_FILL_FUNCTION,
default: SlotDefault::Const(crate::emacs_core::value::Value::NIL),
predicate: "",
reset_on_kill: false,
local_flags_idx: BUFFER_SLOT_AUTO_FILL_FUNCTION as i16,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "mode-line-format",
offset: BUFFER_SLOT_MODE_LINE_FORMAT,
default: SlotDefault::LazyString("%-"),
predicate: "",
reset_on_kill: false,
local_flags_idx: BUFFER_SLOT_MODE_LINE_FORMAT as i16,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "header-line-format",
offset: BUFFER_SLOT_HEADER_LINE_FORMAT,
default: SlotDefault::Const(crate::emacs_core::value::Value::NIL),
predicate: "",
reset_on_kill: false,
local_flags_idx: BUFFER_SLOT_HEADER_LINE_FORMAT as i16,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "tab-line-format",
offset: BUFFER_SLOT_TAB_LINE_FORMAT,
default: SlotDefault::Const(crate::emacs_core::value::Value::NIL),
predicate: "",
reset_on_kill: false,
local_flags_idx: BUFFER_SLOT_TAB_LINE_FORMAT as i16,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "bidi-display-reordering",
offset: BUFFER_SLOT_BIDI_DISPLAY_REORDERING,
default: SlotDefault::Const(crate::emacs_core::value::Value::T),
predicate: "",
reset_on_kill: false,
local_flags_idx: BUFFER_SLOT_BIDI_DISPLAY_REORDERING as i16,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "bidi-paragraph-direction",
offset: BUFFER_SLOT_BIDI_PARAGRAPH_DIRECTION,
default: SlotDefault::Const(crate::emacs_core::value::Value::NIL),
predicate: "",
reset_on_kill: false,
local_flags_idx: BUFFER_SLOT_BIDI_PARAGRAPH_DIRECTION as i16,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "bidi-paragraph-start-re",
offset: BUFFER_SLOT_BIDI_PARAGRAPH_START_RE,
default: SlotDefault::Const(crate::emacs_core::value::Value::NIL),
predicate: "",
reset_on_kill: false,
local_flags_idx: BUFFER_SLOT_BIDI_PARAGRAPH_START_RE as i16,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "bidi-paragraph-separate-re",
offset: BUFFER_SLOT_BIDI_PARAGRAPH_SEPARATE_RE,
default: SlotDefault::Const(crate::emacs_core::value::Value::NIL),
predicate: "",
reset_on_kill: false,
local_flags_idx: BUFFER_SLOT_BIDI_PARAGRAPH_SEPARATE_RE as i16,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "cursor-type",
offset: BUFFER_SLOT_CURSOR_TYPE,
default: SlotDefault::Const(crate::emacs_core::value::Value::T),
predicate: "",
reset_on_kill: false,
local_flags_idx: BUFFER_SLOT_CURSOR_TYPE as i16,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "line-spacing",
offset: BUFFER_SLOT_LINE_SPACING,
default: SlotDefault::Const(crate::emacs_core::value::Value::NIL),
predicate: "",
reset_on_kill: false,
local_flags_idx: BUFFER_SLOT_LINE_SPACING as i16,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "text-conversion-style",
offset: BUFFER_SLOT_TEXT_CONVERSION_STYLE,
default: SlotDefault::Const(crate::emacs_core::value::Value::NIL),
predicate: "",
reset_on_kill: false,
local_flags_idx: BUFFER_SLOT_TEXT_CONVERSION_STYLE as i16,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "cursor-in-non-selected-windows",
offset: BUFFER_SLOT_CURSOR_IN_NON_SELECTED_WINDOWS,
default: SlotDefault::Const(crate::emacs_core::value::Value::T),
predicate: "",
reset_on_kill: false,
local_flags_idx: BUFFER_SLOT_CURSOR_IN_NON_SELECTED_WINDOWS as i16,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "left-margin-width",
offset: BUFFER_SLOT_LEFT_MARGIN_WIDTH,
default: SlotDefault::LazyFixnum(0),
predicate: "",
reset_on_kill: false,
local_flags_idx: BUFFER_SLOT_LEFT_MARGIN_WIDTH as i16,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "right-margin-width",
offset: BUFFER_SLOT_RIGHT_MARGIN_WIDTH,
default: SlotDefault::LazyFixnum(0),
predicate: "",
reset_on_kill: false,
local_flags_idx: BUFFER_SLOT_RIGHT_MARGIN_WIDTH as i16,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "left-fringe-width",
offset: BUFFER_SLOT_LEFT_FRINGE_WIDTH,
default: SlotDefault::Const(crate::emacs_core::value::Value::NIL),
predicate: "",
reset_on_kill: false,
local_flags_idx: BUFFER_SLOT_LEFT_FRINGE_WIDTH as i16,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "right-fringe-width",
offset: BUFFER_SLOT_RIGHT_FRINGE_WIDTH,
default: SlotDefault::Const(crate::emacs_core::value::Value::NIL),
predicate: "",
reset_on_kill: false,
local_flags_idx: BUFFER_SLOT_RIGHT_FRINGE_WIDTH as i16,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "fringes-outside-margins",
offset: BUFFER_SLOT_FRINGES_OUTSIDE_MARGINS,
default: SlotDefault::Const(crate::emacs_core::value::Value::NIL),
predicate: "",
reset_on_kill: false,
local_flags_idx: BUFFER_SLOT_FRINGES_OUTSIDE_MARGINS as i16,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "scroll-bar-width",
offset: BUFFER_SLOT_SCROLL_BAR_WIDTH,
default: SlotDefault::Const(crate::emacs_core::value::Value::NIL),
predicate: "",
reset_on_kill: false,
local_flags_idx: BUFFER_SLOT_SCROLL_BAR_WIDTH as i16,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "scroll-bar-height",
offset: BUFFER_SLOT_SCROLL_BAR_HEIGHT,
default: SlotDefault::Const(crate::emacs_core::value::Value::NIL),
predicate: "",
reset_on_kill: false,
local_flags_idx: BUFFER_SLOT_SCROLL_BAR_HEIGHT as i16,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "vertical-scroll-bar",
offset: BUFFER_SLOT_VERTICAL_SCROLL_BAR,
default: SlotDefault::Const(crate::emacs_core::value::Value::T),
predicate: "",
reset_on_kill: false,
local_flags_idx: BUFFER_SLOT_VERTICAL_SCROLL_BAR as i16,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "horizontal-scroll-bar",
offset: BUFFER_SLOT_HORIZONTAL_SCROLL_BAR,
default: SlotDefault::Const(crate::emacs_core::value::Value::T),
predicate: "",
reset_on_kill: false,
local_flags_idx: BUFFER_SLOT_HORIZONTAL_SCROLL_BAR as i16,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "indicate-empty-lines",
offset: BUFFER_SLOT_INDICATE_EMPTY_LINES,
default: SlotDefault::Const(crate::emacs_core::value::Value::NIL),
predicate: "",
reset_on_kill: false,
local_flags_idx: BUFFER_SLOT_INDICATE_EMPTY_LINES as i16,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "indicate-buffer-boundaries",
offset: BUFFER_SLOT_INDICATE_BUFFER_BOUNDARIES,
default: SlotDefault::Const(crate::emacs_core::value::Value::NIL),
predicate: "",
reset_on_kill: false,
local_flags_idx: BUFFER_SLOT_INDICATE_BUFFER_BOUNDARIES as i16,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "fringe-indicator-alist",
offset: BUFFER_SLOT_FRINGE_INDICATOR_ALIST,
default: SlotDefault::Const(crate::emacs_core::value::Value::NIL),
predicate: "",
reset_on_kill: false,
local_flags_idx: BUFFER_SLOT_FRINGE_INDICATOR_ALIST as i16,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "fringe-cursor-alist",
offset: BUFFER_SLOT_FRINGE_CURSOR_ALIST,
default: SlotDefault::Const(crate::emacs_core::value::Value::NIL),
predicate: "",
reset_on_kill: false,
local_flags_idx: BUFFER_SLOT_FRINGE_CURSOR_ALIST as i16,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "scroll-up-aggressively",
offset: BUFFER_SLOT_SCROLL_UP_AGGRESSIVELY,
default: SlotDefault::Const(crate::emacs_core::value::Value::NIL),
predicate: "",
reset_on_kill: false,
local_flags_idx: BUFFER_SLOT_SCROLL_UP_AGGRESSIVELY as i16,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "scroll-down-aggressively",
offset: BUFFER_SLOT_SCROLL_DOWN_AGGRESSIVELY,
default: SlotDefault::Const(crate::emacs_core::value::Value::NIL),
predicate: "",
reset_on_kill: false,
local_flags_idx: BUFFER_SLOT_SCROLL_DOWN_AGGRESSIVELY as i16,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "cache-long-scans",
offset: BUFFER_SLOT_CACHE_LONG_SCANS,
default: SlotDefault::Const(crate::emacs_core::value::Value::T),
predicate: "",
reset_on_kill: false,
local_flags_idx: BUFFER_SLOT_CACHE_LONG_SCANS as i16,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "local-abbrev-table",
offset: BUFFER_SLOT_LOCAL_ABBREV_TABLE,
default: SlotDefault::Const(crate::emacs_core::value::Value::NIL),
predicate: "",
reset_on_kill: false,
local_flags_idx: BUFFER_SLOT_LOCAL_ABBREV_TABLE as i16,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "buffer-display-table",
offset: BUFFER_SLOT_BUFFER_DISPLAY_TABLE,
default: SlotDefault::Const(crate::emacs_core::value::Value::NIL),
predicate: "",
reset_on_kill: false,
local_flags_idx: BUFFER_SLOT_BUFFER_DISPLAY_TABLE as i16,
install_as_forwarder: true,
permanent_local: false,
},
BufferSlotInfo {
name: "buffer-file-coding-system",
offset: BUFFER_SLOT_BUFFER_FILE_CODING_SYSTEM,
default: SlotDefault::Const(crate::emacs_core::value::Value::NIL),
predicate: "",
reset_on_kill: false,
local_flags_idx: BUFFER_SLOT_BUFFER_FILE_CODING_SYSTEM as i16,
install_as_forwarder: true,
permanent_local: true,
},
BufferSlotInfo {
name: "syntax-table",
offset: BUFFER_SLOT_SYNTAX_TABLE,
default: SlotDefault::Const(crate::emacs_core::value::Value::NIL),
predicate: "",
reset_on_kill: false,
local_flags_idx: BUFFER_SLOT_SYNTAX_TABLE as i16,
install_as_forwarder: false,
permanent_local: false,
},
BufferSlotInfo {
name: "category-table",
offset: BUFFER_SLOT_CATEGORY_TABLE,
default: SlotDefault::Const(crate::emacs_core::value::Value::NIL),
predicate: "",
reset_on_kill: false,
local_flags_idx: BUFFER_SLOT_CATEGORY_TABLE as i16,
install_as_forwarder: false,
permanent_local: false,
},
BufferSlotInfo {
name: "case-table",
offset: BUFFER_SLOT_CASE_TABLE,
default: SlotDefault::Const(crate::emacs_core::value::Value::NIL),
predicate: "",
reset_on_kill: false,
local_flags_idx: -1,
install_as_forwarder: false,
permanent_local: false,
},
];
pub fn lookup_buffer_slot(name: &str) -> Option<&'static BufferSlotInfo> {
static BUFFER_SLOT_NAME_MAP: OnceLock<FxHashMap<&'static str, &'static BufferSlotInfo>> =
OnceLock::new();
BUFFER_SLOT_NAME_MAP
.get_or_init(|| {
let mut map = FxHashMap::default();
for info in BUFFER_SLOT_INFO {
if info.install_as_forwarder {
map.insert(info.name, info);
}
}
map
})
.get(name)
.copied()
}
fn buffer_slot_sym_map() -> &'static [Option<&'static BufferSlotInfo>] {
static BUFFER_SLOT_SYM_MAP: OnceLock<Box<[Option<&'static BufferSlotInfo>]>> = OnceLock::new();
BUFFER_SLOT_SYM_MAP
.get_or_init(|| {
let mut entries: Vec<Option<&'static BufferSlotInfo>> = Vec::new();
for info in BUFFER_SLOT_INFO {
if !info.install_as_forwarder {
continue;
}
let sym_id = intern(info.name);
let index = sym_id.0 as usize;
if entries.len() <= index {
entries.resize(index + 1, None);
}
entries[index] = Some(info);
}
entries.into_boxed_slice()
})
.as_ref()
}
pub fn lookup_buffer_slot_by_sym_id(sym_id: SymId) -> Option<&'static BufferSlotInfo> {
buffer_slot_sym_map()
.get(sym_id.0 as usize)
.and_then(|slot| *slot)
}
fn buffer_undo_list_sym() -> SymId {
static SYM: OnceLock<SymId> = OnceLock::new();
*SYM.get_or_init(|| intern("buffer-undo-list"))
}
pub(crate) fn find_local_var_alist_entry(
alist: crate::emacs_core::value::Value,
key: crate::emacs_core::value::Value,
) -> Option<crate::emacs_core::value::Value> {
let mut cursor = alist;
while cursor.is_cons() {
let entry = cursor.cons_car();
cursor = cursor.cons_cdr();
if entry.is_cons() && crate::emacs_core::value::eq_value(&entry.cons_car(), &key) {
return Some(entry.cons_cdr());
}
}
None
}
pub(crate) fn set_local_var_alist_entry(
alist: &mut crate::emacs_core::value::Value,
key: crate::emacs_core::value::Value,
value: crate::emacs_core::value::Value,
) {
use crate::emacs_core::value::{Value, eq_value};
let mut cursor = *alist;
while cursor.is_cons() {
let entry = cursor.cons_car();
cursor = cursor.cons_cdr();
if entry.is_cons() && eq_value(&entry.cons_car(), &key) {
entry.set_cdr(value);
return;
}
}
let cell = Value::cons(key, value);
*alist = Value::cons(cell, *alist);
}
pub(crate) fn remove_local_var_alist_entry(
alist: &mut crate::emacs_core::value::Value,
key: crate::emacs_core::value::Value,
) {
use crate::emacs_core::value::{Value, eq_value};
let mut head = *alist;
let mut prev: Option<Value> = None;
let mut cursor = head;
while cursor.is_cons() {
let entry = cursor.cons_car();
let next = cursor.cons_cdr();
if entry.is_cons() && eq_value(&entry.cons_car(), &key) {
match prev {
Some(p) => p.set_cdr(next),
None => head = next,
}
} else {
prev = Some(cursor);
}
cursor = next;
}
*alist = head;
}
pub(crate) fn preserve_partial_permanent_local_hook_value(
obarray: &crate::emacs_core::symbol::Obarray,
value: crate::emacs_core::value::Value,
) -> crate::emacs_core::value::Value {
use crate::emacs_core::value::Value;
if !value.is_cons() {
return value;
}
let mut preserved = Vec::new();
let mut cursor = value;
while cursor.is_cons() {
let elt = cursor.cons_car();
cursor = cursor.cons_cdr();
if elt.is_symbol_named("t")
|| elt.as_symbol_name().is_some_and(|name| {
obarray
.get_property(name, "permanent-local-hook")
.is_some_and(|prop| !prop.is_nil())
})
{
preserved.push(elt);
}
}
Value::list(preserved)
}
pub(crate) fn coerce_to_slot(
info: &BufferSlotInfo,
value: crate::emacs_core::value::Value,
current: crate::emacs_core::value::Value,
) -> crate::emacs_core::value::Value {
use crate::emacs_core::value::{Value, ValueKind};
match info.predicate {
"stringp" => match value.kind() {
ValueKind::String => value,
ValueKind::Nil => Value::NIL,
_ => current,
},
"booleanp" => {
if value.is_truthy() {
Value::T
} else {
Value::NIL
}
}
_ => value,
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct BufferId(pub u64);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum InsertionType {
Before,
After,
}
#[derive(Clone, Copy, Debug)]
pub struct BufferStateMarkers {
pub pt_marker: u64,
pub begv_marker: u64,
pub zv_marker: u64,
pub pt_marker_ptr: *mut crate::tagged::header::MarkerObj,
pub begv_marker_ptr: *mut crate::tagged::header::MarkerObj,
pub zv_marker_ptr: *mut crate::tagged::header::MarkerObj,
}
impl PartialEq for BufferStateMarkers {
fn eq(&self, other: &Self) -> bool {
self.pt_marker == other.pt_marker
&& self.begv_marker == other.begv_marker
&& self.zv_marker == other.zv_marker
}
}
impl Eq for BufferStateMarkers {}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum LabeledRestrictionLabel {
Outermost,
User(Value),
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct LabeledRestriction {
pub label: LabeledRestrictionLabel,
pub beg_marker: u64,
pub end_marker: u64,
}
#[derive(Clone, Debug, PartialEq)]
pub enum SavedRestrictionKind {
None,
Markers { beg_marker: u64, end_marker: u64 },
}
#[derive(Clone, Debug, PartialEq)]
pub struct SavedRestrictionState {
pub buffer_id: BufferId,
pub restriction: SavedRestrictionKind,
pub labeled_restrictions: Option<Vec<LabeledRestriction>>,
}
impl SavedRestrictionState {
pub fn trace_roots(&self, roots: &mut Vec<Value>) {
if let Some(restrictions) = &self.labeled_restrictions {
for restriction in restrictions {
if let LabeledRestrictionLabel::User(label) = restriction.label {
roots.push(label);
}
}
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct OutermostRestrictionResetState {
pub affected_buffers: Vec<BufferId>,
}
#[derive(Clone)]
pub struct Buffer {
pub id: BufferId,
pub name: Value,
pub base_buffer: Option<BufferId>,
pub text: BufferText,
pub pt: usize,
pub pt_byte: usize,
pub mark: Option<usize>,
pub mark_byte: Option<usize>,
pub begv: usize,
pub begv_byte: usize,
pub zv: usize,
pub zv_byte: usize,
pub autosave_modified_tick: i64,
pub last_window_start: usize,
pub last_selected_window: Option<WindowId>,
pub inhibit_buffer_hooks: bool,
pub state_markers: Option<BufferStateMarkers>,
pub local_var_alist: crate::emacs_core::value::Value,
pub keymap: crate::emacs_core::value::Value,
pub slots: [crate::emacs_core::value::Value; BUFFER_SLOT_COUNT],
pub local_flags: u64,
pub overlays: OverlayList,
pub undo_state: SharedUndoState,
}
impl Buffer {
pub fn syntax_chartable(&self) -> Value {
self.slots[BUFFER_SLOT_SYNTAX_TABLE]
}
}
impl Buffer {
pub fn new(id: BufferId, name: Value) -> Self {
assert!(name.is_string(), "buffer name must be a Lisp string");
Self {
id,
name,
base_buffer: None,
text: BufferText::new(),
pt: 0,
pt_byte: 0,
mark: None,
mark_byte: None,
begv: 0,
begv_byte: 0,
zv: 0,
zv_byte: 0,
autosave_modified_tick: 1,
last_window_start: 1,
last_selected_window: None,
inhibit_buffer_hooks: false,
state_markers: None,
local_var_alist: crate::emacs_core::value::Value::NIL,
keymap: crate::emacs_core::value::Value::NIL,
slots: {
let mut s = [crate::emacs_core::value::Value::NIL; BUFFER_SLOT_COUNT];
for info in BUFFER_SLOT_INFO {
s[info.offset] = info.default.to_value();
}
s
},
local_flags: 0,
overlays: OverlayList::new(),
undo_state: SharedUndoState::new(),
}
}
pub fn name_value(&self) -> Value {
self.name
}
pub fn name_runtime_string_owned(&self) -> String {
self.name
.as_runtime_string_owned()
.expect("buffer name must be a Lisp string")
}
pub fn has_name(&self, name: &str) -> bool {
self.name_runtime_string_owned() == name
}
pub fn name_starts_with_space(&self) -> bool {
self.name_runtime_string_owned().starts_with(' ')
}
pub fn set_name_value(&mut self, name: Value) {
assert!(name.is_string(), "buffer name must be a Lisp string");
self.name = name;
}
pub fn set_name_runtime_string(&mut self, name: impl Into<String>) {
self.name = Value::string(name.into());
}
#[inline]
pub fn slot_local_flag(&self, offset: usize) -> bool {
debug_assert!(offset < BUFFER_SLOT_COUNT);
(self.local_flags >> (offset as u32)) & 1 != 0
}
#[inline]
pub fn set_slot_local_flag(&mut self, offset: usize, on: bool) {
debug_assert!(offset < BUFFER_SLOT_COUNT);
let bit = 1u64 << (offset as u32);
if on {
self.local_flags |= bit;
} else {
self.local_flags &= !bit;
}
}
pub fn file_name_value(&self) -> Value {
self.slots[BUFFER_SLOT_FILE_NAME]
}
pub fn file_name_runtime_string_owned(&self) -> Option<String> {
self.slots[BUFFER_SLOT_FILE_NAME].as_runtime_string_owned()
}
pub fn file_name_lisp_string(&self) -> Option<&'static crate::heap_types::LispString> {
self.file_name_value().as_lisp_string()
}
pub fn set_file_name_value(&mut self, v: Value) {
assert!(
v.is_nil() || v.is_string(),
"buffer-file-name must be nil or a Lisp string"
);
self.slots[BUFFER_SLOT_FILE_NAME] = v;
}
pub fn auto_save_file_name_value(&self) -> Value {
self.slots[BUFFER_SLOT_AUTO_SAVE_FILE_NAME]
}
pub fn auto_save_file_name_runtime_string_owned(&self) -> Option<String> {
self.slots[BUFFER_SLOT_AUTO_SAVE_FILE_NAME].as_runtime_string_owned()
}
pub fn auto_save_file_name_lisp_string(
&self,
) -> Option<&'static crate::heap_types::LispString> {
self.auto_save_file_name_value().as_lisp_string()
}
pub fn set_auto_save_file_name_value(&mut self, v: Value) {
assert!(
v.is_nil() || v.is_string(),
"buffer-auto-save-file-name must be nil or a Lisp string"
);
self.slots[BUFFER_SLOT_AUTO_SAVE_FILE_NAME] = v;
}
pub fn get_read_only(&self) -> bool {
self.slots[BUFFER_SLOT_READ_ONLY].is_truthy()
}
pub fn set_read_only_value(&mut self, v: bool) {
self.slots[BUFFER_SLOT_READ_ONLY] = if v { Value::T } else { Value::NIL };
}
pub fn get_multibyte(&self) -> bool {
self.slots[BUFFER_SLOT_ENABLE_MULTIBYTE_CHARACTERS].is_truthy()
}
pub fn set_multibyte_value(&mut self, v: bool) {
self.text.set_multibyte(v);
self.slots[BUFFER_SLOT_ENABLE_MULTIBYTE_CHARACTERS] = if v { Value::T } else { Value::NIL };
}
pub fn point_byte(&self) -> usize {
self.pt_byte
}
pub fn point(&self) -> usize {
self.point_byte()
}
pub fn point_char(&self) -> usize {
self.pt
}
pub fn point_min_byte(&self) -> usize {
self.begv_byte
}
pub fn point_min_char(&self) -> usize {
self.begv
}
pub fn point_min(&self) -> usize {
self.point_min_byte()
}
pub fn point_max_byte(&self) -> usize {
self.zv_byte
}
pub fn point_max_char(&self) -> usize {
self.zv
}
pub fn total_chars(&self) -> usize {
self.text.char_count()
}
pub fn total_bytes(&self) -> usize {
self.text.emacs_byte_len()
}
pub fn char_to_byte_clamped(&self, char_pos: usize) -> usize {
self.text
.char_to_emacs_byte(char_pos.min(self.total_chars()))
}
pub fn lisp_pos_to_byte(&self, lisp_pos: i64) -> usize {
let char_pos = if lisp_pos > 0 {
lisp_pos as usize - 1
} else {
0
};
self.char_to_byte_clamped(char_pos)
}
pub fn lisp_pos_to_accessible_byte(&self, lisp_pos: i64) -> usize {
let char_pos = if lisp_pos > 0 {
lisp_pos as usize - 1
} else {
0
};
let clamped_char = char_pos.clamp(self.point_min_char(), self.point_max_char());
self.text.char_to_emacs_byte(clamped_char)
}
pub fn lisp_pos_to_full_buffer_byte(&self, lisp_pos: i64) -> usize {
let char_pos = if lisp_pos > 0 {
lisp_pos as usize - 1
} else {
0
};
let clamped_char = char_pos.min(self.total_chars());
self.text.char_to_emacs_byte(clamped_char)
}
pub fn point_max(&self) -> usize {
self.point_max_byte()
}
pub fn goto_byte(&mut self, pos: usize) {
self.pt_byte = pos.clamp(self.begv_byte, self.zv_byte);
self.pt = if self.pt_byte == self.begv_byte {
self.begv
} else if self.pt_byte == self.zv_byte {
self.zv
} else {
self.text.emacs_byte_to_char(self.pt_byte)
};
}
pub fn goto_char(&mut self, pos: usize) {
self.goto_byte(pos);
}
pub fn get_undo_list(&self) -> Value {
self.undo_state.list()
}
pub fn set_undo_list(&mut self, value: Value) {
self.undo_state.set_list(value);
}
pub fn emacs_byte_to_storage_byte(&self, pos: usize) -> usize {
self.text
.emacs_byte_to_storage_byte(pos.min(self.total_bytes()))
}
pub fn storage_byte_to_emacs_byte(&self, pos: usize) -> usize {
self.text
.storage_byte_to_emacs_byte(pos.min(self.text.len()))
}
pub fn copy_emacs_bytes_to(&self, start: usize, end: usize, out: &mut Vec<u8>) {
let total = self.total_bytes();
let s = start.min(total);
let e = end.max(s).min(total);
self.text.copy_emacs_bytes_to(s, e, out);
}
pub fn buffer_substring_bytes(&self, start: usize, end: usize) -> Vec<u8> {
let mut out = Vec::new();
self.copy_emacs_bytes_to(start, end, &mut out);
out
}
pub fn buffer_substring_lisp_string(
&self,
start: usize,
end: usize,
) -> crate::heap_types::LispString {
let bytes = self.buffer_substring_bytes(start, end);
if self.get_multibyte() {
crate::heap_types::LispString::from_emacs_bytes(bytes)
} else {
crate::heap_types::LispString::from_unibyte(bytes)
}
}
pub fn buffer_substring_value(&self, start: usize, end: usize) -> Value {
Value::heap_string(self.buffer_substring_lisp_string(start, end))
}
pub fn buffer_substring(&self, start: usize, end: usize) -> String {
let bytes = self.buffer_substring_bytes(start, end);
crate::emacs_core::string_escape::emacs_bytes_to_storage_string(
&bytes,
self.get_multibyte(),
)
}
pub fn buffer_string(&self) -> String {
self.buffer_substring(self.begv_byte, self.zv_byte)
}
pub fn buffer_size(&self) -> usize {
self.zv_byte - self.begv_byte
}
pub fn char_after(&self, pos: usize) -> Option<char> {
self.char_code_after(pos).and_then(char::from_u32)
}
pub fn char_code_after(&self, pos: usize) -> Option<u32> {
if pos >= self.total_bytes() {
return None;
}
let storage_pos = self.text.emacs_byte_to_storage_byte(pos);
self.text.char_code_at(storage_pos)
}
pub fn char_before(&self, pos: usize) -> Option<char> {
self.char_code_before(pos).and_then(char::from_u32)
}
pub fn char_code_before(&self, pos: usize) -> Option<u32> {
if pos == 0 || pos > self.total_bytes() {
return None;
}
let prior_char = self.text.emacs_byte_to_char(pos);
if prior_char == 0 {
return None;
}
let prior_byte = self.text.char_to_emacs_byte(prior_char - 1);
let storage_pos = self.text.emacs_byte_to_storage_byte(prior_byte);
self.text.char_code_at(storage_pos)
}
pub fn char_after_storage_len(&self, pos: usize) -> Option<usize> {
if pos >= self.total_bytes() {
return None;
}
let storage_pos = self.text.emacs_byte_to_storage_byte(pos);
let char_idx = self.text.emacs_byte_to_char(pos);
Some(self.text.char_to_byte(char_idx + 1) - storage_pos)
}
pub fn char_before_storage_len(&self, pos: usize) -> Option<usize> {
if pos == 0 || pos > self.total_bytes() {
return None;
}
let prior_char = self.text.emacs_byte_to_char(pos);
if prior_char == 0 {
return None;
}
let prior_byte = self.text.char_to_byte(prior_char - 1);
let storage_pos = self.text.emacs_byte_to_storage_byte(pos);
Some(storage_pos - prior_byte)
}
pub fn char_after_emacs_len(&self, pos: usize) -> Option<usize> {
if pos >= self.total_bytes() {
return None;
}
let char_idx = self.text.emacs_byte_to_char(pos);
Some(self.text.char_to_emacs_byte(char_idx + 1) - pos)
}
pub fn char_before_emacs_len(&self, pos: usize) -> Option<usize> {
if pos == 0 || pos > self.total_bytes() {
return None;
}
let prior_char = self.text.emacs_byte_to_char(pos);
if prior_char == 0 {
return None;
}
let prior_byte = self.text.char_to_emacs_byte(prior_char - 1);
Some(pos - prior_byte)
}
pub fn narrow_to_byte_region(&mut self, start: usize, end: usize) {
let total = self.total_bytes();
let s = start.min(total);
let e = end.clamp(s, total);
let total_chars = self.text.char_count();
self.begv_byte = s;
self.begv = self.text.emacs_byte_to_char(s);
self.zv_byte = e;
self.zv = if e == total {
total_chars
} else {
self.text.emacs_byte_to_char(e)
};
self.goto_byte(self.pt_byte);
}
pub fn narrow_to_region(&mut self, start: usize, end: usize) {
self.narrow_to_byte_region(start, end);
}
pub fn widen(&mut self) {
self.narrow_to_byte_region(0, self.total_bytes());
}
pub fn register_marker(
&mut self,
marker_ptr: *mut crate::tagged::header::MarkerObj,
marker_id: u64,
pos: usize,
insertion_type: InsertionType,
) {
let clamped = pos.min(self.total_bytes());
let char_pos = if clamped == self.begv_byte {
self.begv
} else if clamped == self.zv_byte {
self.zv
} else {
self.text.emacs_byte_to_char(clamped)
};
self.text.register_marker(
marker_ptr,
self.id,
marker_id,
clamped,
char_pos,
insertion_type,
);
}
pub fn remove_marker_entry(&mut self, marker_id: u64) {
self.text.remove_marker(marker_id);
}
pub fn update_marker_insertion_type(&mut self, marker_id: u64, insertion_type: InsertionType) {
self.text
.update_marker_insertion_type(marker_id, insertion_type);
}
pub fn advance_markers_at(&mut self, pos: usize, byte_len: usize, char_len: usize) {
self.text.advance_markers_at(pos, byte_len, char_len);
}
pub fn set_mark_byte(&mut self, pos: usize) {
let clamped = pos.clamp(self.begv_byte, self.zv_byte);
let char_pos = if clamped == self.begv_byte {
self.begv
} else if clamped == self.zv_byte {
self.zv
} else {
self.text.emacs_byte_to_char(clamped)
};
self.mark = Some(char_pos);
self.mark_byte = Some(clamped);
}
pub fn set_mark(&mut self, pos: usize) {
self.set_mark_byte(pos);
}
pub fn mark_byte(&self) -> Option<usize> {
self.mark_byte
}
pub fn mark_char(&self) -> Option<usize> {
self.mark
}
pub fn mark(&self) -> Option<usize> {
self.mark_byte()
}
pub fn modified_tick(&self) -> i64 {
self.text.modified_tick()
}
pub fn chars_modified_tick(&self) -> i64 {
self.text.chars_modified_tick()
}
pub fn save_modified_tick(&self) -> i64 {
self.text.save_modified_tick()
}
pub fn is_modified(&self) -> bool {
self.save_modified_tick() < self.modified_tick()
}
pub fn modified_state_value(&self) -> Value {
if self.save_modified_tick() < self.modified_tick() {
if self.autosave_modified_tick == self.modified_tick() {
Value::symbol("autosaved")
} else {
Value::T
}
} else {
Value::NIL
}
}
pub fn recent_auto_save_p(&self) -> bool {
self.save_modified_tick() < self.autosave_modified_tick
}
pub fn set_modified(&mut self, flag: bool) {
if flag {
if self.save_modified_tick() >= self.modified_tick() {
self.text.increment_modified_tick(1);
}
} else {
self.text.set_save_modified_tick(self.modified_tick());
}
}
pub fn restore_modified_state(&mut self, flag: Value) -> Value {
if flag.is_nil() {
self.text.set_save_modified_tick(self.modified_tick());
} else {
if self.save_modified_tick() >= self.modified_tick() {
self.text.increment_modified_tick(1);
}
if flag == Value::symbol("autosaved") {
self.autosave_modified_tick = self.modified_tick();
}
}
flag
}
pub fn mark_auto_saved(&mut self) {
self.autosave_modified_tick = self.modified_tick();
}
pub fn set_buffer_local(&mut self, name: &str, value: Value) {
self.set_buffer_local_by_sym_id(intern(name), value);
}
pub fn set_buffer_local_by_sym_id(&mut self, sym_id: SymId, value: Value) {
if let Some(info) = lookup_buffer_slot_by_sym_id(sym_id) {
self.slots[info.offset] = coerce_to_slot(info, value, self.slots[info.offset]);
if info.local_flags_idx >= 0 {
self.set_slot_local_flag(info.offset, true);
}
return;
}
if sym_id == buffer_undo_list_sym() {
self.undo_state.set_list(value);
if value.is_nil() {
self.undo_state.set_recorded_first_change(false);
}
return;
}
set_local_var_alist_entry(&mut self.local_var_alist, Value::from_sym_id(sym_id), value);
}
pub fn set_buffer_local_void(&mut self, name: &str) {
self.set_buffer_local_void_by_sym_id(intern(name));
}
pub fn set_buffer_local_void_by_sym_id(&mut self, sym_id: SymId) {
if let Some(info) = lookup_buffer_slot_by_sym_id(sym_id) {
self.slots[info.offset] = Value::NIL;
return;
}
if sym_id == buffer_undo_list_sym() {
self.undo_state.set_list(Value::NIL);
self.undo_state.set_recorded_first_change(false);
return;
}
remove_local_var_alist_entry(&mut self.local_var_alist, Value::from_sym_id(sym_id));
}
pub fn kill_buffer_local(&mut self, name: &str) -> Option<RuntimeBindingValue> {
self.kill_buffer_local_by_sym_id(intern(name))
}
pub fn kill_buffer_local_by_sym_id(&mut self, sym_id: SymId) -> Option<RuntimeBindingValue> {
if sym_id == buffer_undo_list_sym() {
return None;
}
let key = Value::from_sym_id(sym_id);
let existing = find_local_var_alist_entry(self.local_var_alist, key)?;
remove_local_var_alist_entry(&mut self.local_var_alist, key);
Some(RuntimeBindingValue::Bound(existing))
}
pub fn kill_all_local_variables(
&mut self,
obarray: &mut crate::emacs_core::symbol::Obarray,
kill_permanent: bool,
buffer_defaults: &[crate::emacs_core::value::Value; BUFFER_SLOT_COUNT],
) {
for info in BUFFER_SLOT_INFO {
if info.local_flags_idx >= 0 {
if info.permanent_local && !kill_permanent {
continue;
}
self.set_slot_local_flag(info.offset, false);
self.slots[info.offset] = buffer_defaults[info.offset];
} else if info.reset_on_kill {
self.slots[info.offset] = info.default.to_value();
}
}
{
use crate::emacs_core::value::Value;
let mut new_head = Value::NIL;
let mut new_tail: Option<Value> = None;
let mut alist = self.local_var_alist;
while alist.is_cons() {
let next_pair = alist.cons_cdr();
let entry = alist.cons_car();
if !entry.is_cons() {
alist = next_pair;
continue;
}
let sym_val = entry.cons_car();
let Some(name) = sym_val.as_symbol_name() else {
alist = next_pair;
continue;
};
let mut keep = false;
if kill_permanent {
} else {
let prop = obarray
.get_property(name, "permanent-local")
.filter(|v| !v.is_nil());
if let Some(prop) = prop {
if prop.is_symbol_named("permanent-local-hook") {
let value = entry.cons_cdr();
let preserved =
preserve_partial_permanent_local_hook_value(obarray, value);
entry.set_cdr(preserved);
}
keep = true;
}
}
if keep {
if new_tail.is_none() {
new_head = alist;
} else {
new_tail.unwrap().set_cdr(alist);
}
new_tail = Some(alist);
} else {
let id = crate::emacs_core::intern::intern(name);
if let Some(blv) = obarray.blv_mut(id) {
blv.where_buf = Value::NIL;
blv.found = false;
blv.valcell = blv.defcell;
}
}
alist = next_pair;
}
if let Some(tail) = new_tail {
tail.set_cdr(Value::NIL);
}
self.local_var_alist = new_head;
}
self.keymap = Value::NIL;
}
pub fn get_buffer_local(&self, name: &str) -> Option<Value> {
self.get_buffer_local_by_sym_id(intern(name))
}
pub fn get_buffer_local_by_sym_id(&self, sym_id: SymId) -> Option<Value> {
if let Some(info) = lookup_buffer_slot_by_sym_id(sym_id) {
if info.local_flags_idx >= 0 && !self.slot_local_flag(info.offset) {
return None;
}
return Some(self.slots[info.offset]);
}
if sym_id == buffer_undo_list_sym() {
return Some(self.get_undo_list());
}
find_local_var_alist_entry(self.local_var_alist, Value::from_sym_id(sym_id))
.filter(|v| !v.is_unbound())
}
pub fn find_in_local_var_alist(&self, key: Value) -> Option<Value> {
let mut alist = self.local_var_alist;
while alist.is_cons() {
let entry = alist.cons_car();
if entry.is_cons() && crate::emacs_core::value::eq_value(&entry.cons_car(), &key) {
return Some(entry.cons_cdr());
}
alist = alist.cons_cdr();
}
None
}
pub fn get_buffer_local_binding(&self, name: &str) -> Option<RuntimeBindingValue> {
self.get_buffer_local_binding_by_sym_id(intern(name))
}
pub fn get_buffer_local_binding_by_sym_id(&self, sym_id: SymId) -> Option<RuntimeBindingValue> {
if let Some(info) = lookup_buffer_slot_by_sym_id(sym_id) {
if info.local_flags_idx >= 0 && !self.slot_local_flag(info.offset) {
return None;
}
return Some(RuntimeBindingValue::Bound(self.slots[info.offset]));
}
if sym_id == buffer_undo_list_sym() {
return Some(RuntimeBindingValue::Bound(self.get_undo_list()));
}
find_local_var_alist_entry(self.local_var_alist, Value::from_sym_id(sym_id)).map(|v| {
if v.is_unbound() {
RuntimeBindingValue::Void
} else {
RuntimeBindingValue::Bound(v)
}
})
}
pub fn has_buffer_local(&self, name: &str) -> bool {
self.has_buffer_local_by_sym_id(intern(name))
}
pub fn has_buffer_local_by_sym_id(&self, sym_id: SymId) -> bool {
if let Some(info) = lookup_buffer_slot_by_sym_id(sym_id) {
if info.local_flags_idx >= 0 {
return self.slot_local_flag(info.offset);
}
return true;
}
if sym_id == buffer_undo_list_sym() {
return true;
}
find_local_var_alist_entry(self.local_var_alist, Value::from_sym_id(sym_id)).is_some()
}
pub fn local_map(&self) -> Value {
self.keymap
}
pub fn set_local_map(&mut self, keymap: Value) {
self.keymap = keymap;
}
pub fn buffer_local_value(&self, name: &str) -> Option<Value> {
if let Some(info) = lookup_buffer_slot(name) {
return Some(self.slots[info.offset]);
}
if name == "buffer-undo-list" {
return Some(self.get_undo_list());
}
match self.get_buffer_local_binding(name) {
Some(RuntimeBindingValue::Bound(value)) => Some(value),
Some(RuntimeBindingValue::Void) | None => None,
}
}
pub fn ordered_buffer_local_bindings(&self) -> Vec<(SymId, RuntimeBindingValue)> {
let mut out: Vec<(SymId, RuntimeBindingValue)> = Vec::new();
let mut cursor = self.local_var_alist;
while cursor.is_cons() {
let entry = cursor.cons_car();
cursor = cursor.cons_cdr();
if !entry.is_cons() {
continue;
}
if let Some(sym_id) = entry.cons_car().as_symbol_id() {
let cdr = entry.cons_cdr();
let binding = if cdr.is_unbound() {
RuntimeBindingValue::Void
} else {
RuntimeBindingValue::Bound(cdr)
};
out.push((sym_id, binding));
}
}
for info in BUFFER_SLOT_INFO {
if !info.install_as_forwarder {
continue;
}
if info.local_flags_idx >= 0 && !self.slot_local_flag(info.offset) {
continue;
}
out.push((
intern(info.name),
RuntimeBindingValue::Bound(self.slots[info.offset]),
));
}
out.push((
buffer_undo_list_sym(),
RuntimeBindingValue::Bound(self.get_undo_list()),
));
out
}
pub fn ordered_buffer_local_names(&self) -> Vec<SymId> {
self.ordered_buffer_local_bindings()
.into_iter()
.map(|(sym_id, _)| sym_id)
.collect()
}
pub fn bound_buffer_local_values_mut(&mut self) -> impl Iterator<Item = &mut Value> {
std::iter::empty()
}
}
impl Buffer {
pub fn buffer_local_bound_p(&self, name: &str) -> bool {
matches!(
self.get_buffer_local_binding(name),
Some(RuntimeBindingValue::Bound(_))
)
}
pub fn buffer_local_void_p(&self, name: &str) -> bool {
matches!(
self.get_buffer_local_binding(name),
Some(RuntimeBindingValue::Void)
)
}
}
#[derive(Clone)]
pub struct BufferManager {
buffers: HashMap<BufferId, Buffer>,
buffer_order: Vec<BufferId>,
current: Option<BufferId>,
next_id: u64,
next_marker_id: u64,
labeled_restrictions: HashMap<BufferId, Vec<LabeledRestriction>>,
dead_buffer_last_names: HashMap<BufferId, Value>,
pub buffer_defaults: [crate::emacs_core::value::Value; BUFFER_SLOT_COUNT],
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct UndoExecutionResult {
pub had_any_records: bool,
pub had_boundary: bool,
pub applied_any: bool,
pub skipped_apply: bool,
}
impl BufferManager {
pub fn new() -> Self {
let mut buffer_defaults = [crate::emacs_core::value::Value::NIL; BUFFER_SLOT_COUNT];
for info in BUFFER_SLOT_INFO {
buffer_defaults[info.offset] = info.default.to_value();
}
let mut mgr = Self {
buffers: HashMap::new(),
buffer_order: Vec::new(),
current: None,
next_id: 1,
next_marker_id: 1,
labeled_restrictions: HashMap::new(),
dead_buffer_last_names: HashMap::new(),
buffer_defaults,
};
let scratch = mgr.create_buffer("*scratch*");
mgr.current = Some(scratch);
mgr.note_buffer_order_head(scratch);
mgr
}
pub fn create_buffer(&mut self, name: &str) -> BufferId {
self.create_buffer_with_hook_inhibition(name, false)
}
pub fn create_buffer_with_hook_inhibition(
&mut self,
name: &str,
inhibit_buffer_hooks: bool,
) -> BufferId {
let id = BufferId(self.next_id);
self.next_id += 1;
let mut buf = Buffer::new(id, Value::string(name));
for info in BUFFER_SLOT_INFO {
if info.local_flags_idx >= 0 {
buf.slots[info.offset] = self.buffer_defaults[info.offset];
}
}
buf.inhibit_buffer_hooks = inhibit_buffer_hooks;
if let Some(default_directory) = self
.current
.and_then(|current| self.buffers.get(¤t))
.and_then(|current| current.buffer_local_value("default-directory"))
{
buf.set_buffer_local("default-directory", default_directory);
}
if name.starts_with(' ') {
buf.set_buffer_local("buffer-undo-list", crate::emacs_core::value::Value::T);
}
self.buffers.insert(id, buf);
self.buffer_order.push(id);
id
}
pub fn create_indirect_buffer(
&mut self,
base_id: BufferId,
name: &str,
clone: bool,
) -> Option<BufferId> {
self.create_indirect_buffer_with_hook_inhibition(base_id, name, clone, false)
}
pub fn create_indirect_buffer_with_hook_inhibition(
&mut self,
base_id: BufferId,
name: &str,
clone: bool,
inhibit_buffer_hooks: bool,
) -> Option<BufferId> {
if name.is_empty() || self.find_buffer_by_name(name).is_some() {
return None;
}
let root_id = self.shared_text_root_id(base_id)?;
let root = self.buffers.get(&root_id)?.clone();
let shared_text = self.buffers.get(&root_id)?.text.shared_clone();
let id = BufferId(self.next_id);
self.next_id += 1;
let mut indirect = if clone {
let mut cloned = root.clone();
cloned.id = id;
cloned.set_name_value(Value::string(name));
cloned
} else {
let mut fresh = Buffer::new(id, Value::string(name));
if let Some(default_directory) = self
.current
.and_then(|current| self.buffers.get(¤t))
.and_then(|current| current.buffer_local_value("default-directory"))
{
fresh.set_buffer_local("default-directory", default_directory);
}
fresh
};
indirect.base_buffer = Some(root_id);
indirect.inhibit_buffer_hooks = inhibit_buffer_hooks;
indirect.text = shared_text;
indirect.undo_state = root.undo_state.clone();
indirect.narrow_to_byte_region(root.begv_byte, root.zv_byte);
indirect.goto_byte(root.pt_byte);
indirect.set_multibyte_value(root.get_multibyte());
indirect.autosave_modified_tick = root.autosave_modified_tick;
indirect.slots[BUFFER_SLOT_FILE_NAME] = Value::NIL;
if !clone {
indirect.overlays = OverlayList::new();
indirect.mark = None;
indirect.mark_byte = None;
}
self.buffers.insert(id, indirect);
self.buffer_order.push(id);
let _ = self.ensure_buffer_state_markers(root_id);
let _ = self.ensure_buffer_state_markers(id);
Some(id)
}
fn note_buffer_order_head(&mut self, id: BufferId) {
self.buffer_order.retain(|existing| *existing != id);
self.buffer_order.insert(0, id);
}
pub fn note_buffer_order_tail(&mut self, id: BufferId) -> bool {
if !self.buffers.contains_key(&id) {
return false;
}
self.buffer_order.retain(|existing| *existing != id);
self.buffer_order.push(id);
true
}
pub fn note_buffer_display(&mut self, id: BufferId) {
if self.buffers.contains_key(&id) {
self.note_buffer_order_head(id);
}
}
pub fn get(&self, id: BufferId) -> Option<&Buffer> {
self.buffers.get(&id)
}
pub fn get_mut(&mut self, id: BufferId) -> Option<&mut Buffer> {
self.buffers.get_mut(&id)
}
pub unsafe fn collect_marker_chain_head_slots(
&self,
) -> Vec<*mut *mut crate::tagged::header::MarkerObj> {
self.buffers
.values()
.map(|buf| unsafe { buf.text.markers_head_slot_raw() })
.collect()
}
pub fn current_buffer(&self) -> Option<&Buffer> {
self.current.and_then(|id| self.buffers.get(&id))
}
pub fn current_buffer_mut(&mut self) -> Option<&mut Buffer> {
self.current.and_then(|id| self.buffers.get_mut(&id))
}
pub fn current_buffer_id(&self) -> Option<BufferId> {
self.current
}
pub fn buffer_hooks_inhibited(&self, id: BufferId) -> bool {
self.buffers
.get(&id)
.is_some_and(|buffer| buffer.inhibit_buffer_hooks)
}
fn buffer_has_state_markers(&self, id: BufferId) -> bool {
self.buffers
.get(&id)
.and_then(|buffer| buffer.state_markers)
.is_some()
}
fn ensure_buffer_state_markers(&mut self, buffer_id: BufferId) -> Option<()> {
if self.buffer_has_state_markers(buffer_id) {
return Some(());
}
let (pt, begv, zv) = {
let buffer = self.buffers.get(&buffer_id)?;
(buffer.pt_byte, buffer.begv_byte, buffer.zv_byte)
};
let (pt_marker, pt_marker_ptr) = self.create_marker(buffer_id, pt, InsertionType::Before);
let (begv_marker, begv_marker_ptr) =
self.create_marker(buffer_id, begv, InsertionType::Before);
let (zv_marker, zv_marker_ptr) = self.create_marker(buffer_id, zv, InsertionType::After);
self.buffers.get_mut(&buffer_id)?.state_markers = Some(BufferStateMarkers {
pt_marker,
begv_marker,
zv_marker,
pt_marker_ptr,
begv_marker_ptr,
zv_marker_ptr,
});
Some(())
}
fn record_buffer_state_markers(&mut self, buffer_id: BufferId) -> Option<()> {
let markers = self.buffers.get(&buffer_id)?.state_markers?;
let (pt, begv, zv) = {
let buffer = self.buffers.get(&buffer_id)?;
(buffer.pt_byte, buffer.begv_byte, buffer.zv_byte)
};
if let Some(buf) = self.buffers.get(&buffer_id) {
buf.text.chain_unlink(markers.pt_marker_ptr);
buf.text.chain_unlink(markers.begv_marker_ptr);
buf.text.chain_unlink(markers.zv_marker_ptr);
}
self.register_marker_id(
markers.pt_marker_ptr,
buffer_id,
markers.pt_marker,
pt,
InsertionType::Before,
)?;
self.register_marker_id(
markers.begv_marker_ptr,
buffer_id,
markers.begv_marker,
begv,
InsertionType::Before,
)?;
self.register_marker_id(
markers.zv_marker_ptr,
buffer_id,
markers.zv_marker,
zv,
InsertionType::After,
)?;
Some(())
}
fn fetch_buffer_state_markers(&mut self, buffer_id: BufferId) -> Option<()> {
let markers = self.buffers.get(&buffer_id)?.state_markers?;
let pt = self.marker_position(buffer_id, markers.pt_marker)?;
let pt_char = self.marker_char_position(buffer_id, markers.pt_marker)?;
let begv = self.marker_position(buffer_id, markers.begv_marker)?;
let begv_char = self.marker_char_position(buffer_id, markers.begv_marker)?;
let zv = self.marker_position(buffer_id, markers.zv_marker)?;
let zv_char = self.marker_char_position(buffer_id, markers.zv_marker)?;
let buffer = self.buffers.get_mut(&buffer_id)?;
buffer.pt = pt_char;
buffer.pt_byte = pt;
buffer.begv = begv_char;
buffer.begv_byte = begv;
buffer.zv = zv_char;
buffer.zv_byte = zv;
Some(())
}
fn switch_current_with_recording(&mut self, id: BufferId, record_order: bool) -> bool {
if !self.buffers.contains_key(&id) {
return false;
}
if self.current == Some(id) {
return true;
}
let old_id = self.current;
self.current = Some(id);
if record_order {
self.note_buffer_order_head(id);
}
if let Some(old_id) = old_id {
let _ = self.record_buffer_state_markers(old_id);
}
let _ = self.fetch_buffer_state_markers(id);
true
}
pub fn switch_current(&mut self, id: BufferId) -> bool {
self.switch_current_with_recording(id, true)
}
pub fn switch_current_unrecorded(&mut self, id: BufferId) -> bool {
self.switch_current_with_recording(id, false)
}
pub fn set_current(&mut self, id: BufferId) {
let _ = self.switch_current(id);
}
pub fn find_buffer_by_name(&self, name: &str) -> Option<BufferId> {
self.buffers
.values()
.find(|b| b.has_name(name))
.map(|b| b.id)
}
pub fn find_dead_buffer_by_name(&self, name: &str) -> Option<BufferId> {
self.dead_buffer_last_names
.iter()
.find_map(|(id, last_name)| {
(last_name.as_runtime_string_owned().as_deref() == Some(name)).then_some(*id)
})
}
pub fn kill_buffer(&mut self, id: BufferId) -> bool {
self.kill_buffer_collect(id).is_some()
}
pub fn kill_buffer_collect(&mut self, id: BufferId) -> Option<Vec<BufferId>> {
let killed_ids = self.collect_killed_buffer_ids(id)?;
let killed_set: HashSet<BufferId> = killed_ids.iter().copied().collect();
for killed_id in &killed_ids {
self.replace_labeled_restrictions(*killed_id, None);
}
self.buffers
.get(&id)?
.text
.remove_markers_for_buffers(&killed_set);
for killed_id in &killed_ids {
let buf = self.buffers.remove(killed_id)?;
self.dead_buffer_last_names
.insert(*killed_id, buf.name_value());
}
self.buffer_order
.retain(|buffer_id| !killed_set.contains(buffer_id));
if self
.current
.is_some_and(|current| killed_set.contains(¤t))
{
self.current = None;
}
Some(killed_ids)
}
pub fn dead_buffer_last_name_value(&self, id: BufferId) -> Option<Value> {
self.dead_buffer_last_names.get(&id).copied()
}
pub fn dead_buffer_last_name_owned(&self, id: BufferId) -> Option<String> {
self.dead_buffer_last_name_value(id)
.and_then(Value::as_runtime_string_owned)
}
pub fn buffer_list(&self) -> Vec<BufferId> {
let mut ids = Vec::with_capacity(self.buffers.len());
for id in &self.buffer_order {
if self.buffers.contains_key(id) {
ids.push(*id);
}
}
if ids.len() < self.buffers.len() {
let mut missing: Vec<BufferId> = self
.buffers
.keys()
.copied()
.filter(|id| !ids.contains(id))
.collect();
missing.sort_by_key(|id| id.0);
ids.extend(missing);
}
ids
}
fn shared_text_root_id(&self, id: BufferId) -> Option<BufferId> {
let buf = self.buffers.get(&id)?;
Some(buf.base_buffer.unwrap_or(buf.id))
}
pub(crate) fn collect_killed_buffer_ids(&self, id: BufferId) -> Option<Vec<BufferId>> {
let buf = self.buffers.get(&id)?;
let mut killed_ids = vec![id];
if buf.base_buffer.is_none() {
let mut indirects = self
.buffers
.values()
.filter_map(|buffer| (buffer.base_buffer == Some(id)).then_some(buffer.id))
.collect::<Vec<_>>();
indirects.sort_by_key(|buffer_id| buffer_id.0);
killed_ids.extend(indirects);
}
Some(killed_ids)
}
fn full_buffer_bounds(&self, id: BufferId) -> Option<(usize, usize)> {
let buf = self.buffers.get(&id)?;
Some((0, buf.total_bytes()))
}
fn labeled_restriction_at(&self, id: BufferId, outermost: bool) -> Option<&LabeledRestriction> {
let restrictions = self.labeled_restrictions.get(&id)?;
if outermost {
restrictions.first()
} else {
restrictions.last()
}
}
fn labeled_restriction_bounds(&self, id: BufferId, outermost: bool) -> Option<(usize, usize)> {
let restriction = self.labeled_restriction_at(id, outermost)?;
let beg = self.marker_position(id, restriction.beg_marker)?;
let end = self.marker_position(id, restriction.end_marker)?;
Some((beg, end))
}
pub fn current_labeled_restriction_bounds(&self, id: BufferId) -> Option<(usize, usize)> {
self.labeled_restriction_bounds(id, false)
}
pub fn current_labeled_restriction_char_bounds(&self, id: BufferId) -> Option<(usize, usize)> {
let restriction = self.labeled_restriction_at(id, false)?;
let beg = self.marker_char_position(id, restriction.beg_marker)?;
let end = self.marker_char_position(id, restriction.end_marker)?;
Some((beg, end))
}
pub fn current_labeled_restriction_matches_label(&self, id: BufferId, label: &Value) -> bool {
let Some(restriction) = self.labeled_restriction_at(id, false) else {
return false;
};
match restriction.label {
LabeledRestrictionLabel::User(current) => {
crate::emacs_core::value::eq_value(¤t, label)
}
LabeledRestrictionLabel::Outermost => false,
}
}
fn clone_marker_in_buffer(&mut self, buffer_id: BufferId, marker_id: u64) -> Option<u64> {
let (pos, insertion_type) = {
let buf = self.buffers.get(&buffer_id)?;
let (bytepos, _charpos, ins_type) = buf.text.marker_chain_lookup(marker_id)?;
(bytepos, ins_type)
};
let (marker_id, _marker_ptr) = self.create_marker(buffer_id, pos, insertion_type);
Some(marker_id)
}
fn clone_labeled_restrictions(
&mut self,
buffer_id: BufferId,
) -> Option<Option<Vec<LabeledRestriction>>> {
let restrictions = self.labeled_restrictions.get(&buffer_id)?.clone();
let mut cloned = Vec::with_capacity(restrictions.len());
for restriction in restrictions {
let beg_marker = self.clone_marker_in_buffer(buffer_id, restriction.beg_marker)?;
let end_marker = self.clone_marker_in_buffer(buffer_id, restriction.end_marker)?;
cloned.push(LabeledRestriction {
label: restriction.label,
beg_marker,
end_marker,
});
}
Some(Some(cloned))
}
fn replace_labeled_restrictions(
&mut self,
buffer_id: BufferId,
restrictions: Option<Vec<LabeledRestriction>>,
) {
let mut live_marker_ids = std::collections::HashSet::new();
if let Some(ref restrictions) = restrictions {
for restriction in restrictions {
live_marker_ids.insert(restriction.beg_marker);
live_marker_ids.insert(restriction.end_marker);
}
}
if let Some(old) = self.labeled_restrictions.remove(&buffer_id) {
for restriction in old {
if !live_marker_ids.contains(&restriction.beg_marker) {
self.remove_marker(restriction.beg_marker);
}
if !live_marker_ids.contains(&restriction.end_marker) {
self.remove_marker(restriction.end_marker);
}
}
}
if self.buffers.contains_key(&buffer_id) {
if let Some(restrictions) = restrictions.filter(|restrictions| !restrictions.is_empty())
{
self.labeled_restrictions.insert(buffer_id, restrictions);
}
}
}
pub fn clear_buffer_labeled_restrictions(&mut self, buffer_id: BufferId) -> Option<()> {
self.buffers.get(&buffer_id)?;
self.replace_labeled_restrictions(buffer_id, None);
Some(())
}
fn push_labeled_restriction_for_current_bounds(
&mut self,
buffer_id: BufferId,
label: LabeledRestrictionLabel,
) -> Option<()> {
let (begv, zv) = {
let buf = self.buffers.get(&buffer_id)?;
(buf.begv_byte, buf.zv_byte)
};
let (beg_marker, _) = self.create_marker(buffer_id, begv, InsertionType::Before);
let (end_marker, _) = self.create_marker(buffer_id, zv, InsertionType::After);
self.labeled_restrictions
.entry(buffer_id)
.or_default()
.push(LabeledRestriction {
label,
beg_marker,
end_marker,
});
Some(())
}
fn pop_labeled_restriction(&mut self, buffer_id: BufferId) -> Option<LabeledRestriction> {
let restrictions = self.labeled_restrictions.get_mut(&buffer_id)?;
let restriction = restrictions.pop()?;
let remove_entry = restrictions.is_empty();
if remove_entry {
self.labeled_restrictions.remove(&buffer_id);
}
self.remove_marker(restriction.beg_marker);
self.remove_marker(restriction.end_marker);
Some(restriction)
}
fn widen_buffer_fully(&mut self, id: BufferId) -> Option<()> {
let (begv, zv) = self.full_buffer_bounds(id)?;
self.restore_buffer_restriction(id, begv, zv)
}
fn buffers_sharing_root_ids(&self, root_id: BufferId) -> Vec<BufferId> {
self.buffers
.values()
.filter_map(|buf| (buf.base_buffer.unwrap_or(buf.id) == root_id).then_some(buf.id))
.collect()
}
pub(crate) fn shared_text_buffer_ids(&self, root_id: BufferId) -> Vec<BufferId> {
self.buffers_sharing_root_ids(root_id)
}
pub(crate) fn modified_state_root_id(&self, id: BufferId) -> Option<BufferId> {
self.shared_text_root_id(id)
}
pub fn goto_buffer_byte(&mut self, id: BufferId, pos: usize) -> Option<usize> {
{
let buf = self.buffers.get_mut(&id)?;
buf.goto_byte(pos);
}
let point = self.buffers.get(&id)?.point_byte();
let _ = self.record_buffer_state_markers(id);
Some(point)
}
pub fn delete_all_buffer_overlays(&mut self, id: BufferId) -> Option<()> {
let buf = self.buffers.get_mut(&id)?;
let ids = buf
.overlays
.overlays_in(buf.point_min_byte(), buf.point_max_byte());
for ov_id in ids {
buf.overlays.delete_overlay(ov_id);
}
Some(())
}
pub fn delete_buffer_overlay(&mut self, id: BufferId, overlay_id: Value) -> Option<()> {
self.buffers
.get_mut(&id)?
.overlays
.delete_overlay(overlay_id);
Some(())
}
pub fn put_buffer_overlay_property(
&mut self,
id: BufferId,
overlay_id: Value,
name: Value,
value: Value,
) -> Option<()> {
self.buffers
.get_mut(&id)?
.overlays
.overlay_put(overlay_id, name, value)
.ok()?;
Some(())
}
pub fn narrow_buffer_to_region(
&mut self,
id: BufferId,
start: usize,
end: usize,
) -> Option<()> {
self.buffers.get_mut(&id)?.narrow_to_byte_region(start, end);
let _ = self.record_buffer_state_markers(id);
Some(())
}
pub fn widen_buffer(&mut self, id: BufferId) -> Option<()> {
self.buffers.get(&id)?;
let Some(restriction) = self.labeled_restriction_at(id, false).copied() else {
return self.widen_buffer_fully(id);
};
let Some((begv, zv)) = self.labeled_restriction_bounds(id, false) else {
self.replace_labeled_restrictions(id, None);
return self.widen_buffer_fully(id);
};
self.restore_buffer_restriction(id, begv, zv)?;
if matches!(restriction.label, LabeledRestrictionLabel::Outermost) {
let _ = self.pop_labeled_restriction(id);
}
Some(())
}
pub fn replace_buffer_contents(&mut self, id: BufferId, text: &str) -> Option<()> {
let len = self.buffers.get(&id)?.total_bytes();
if len > 0 {
self.delete_buffer_region(id, 0, len)?;
}
{
let buf = self.buffers.get_mut(&id)?;
buf.widen();
buf.goto_byte(0);
}
if !text.is_empty() {
self.insert_into_buffer(id, text)?;
self.goto_buffer_byte(id, 0)?;
}
Some(())
}
pub fn replace_buffer_contents_lisp_string(
&mut self,
id: BufferId,
text: &crate::heap_types::LispString,
) -> Option<()> {
debug_assert_eq!(
self.buffers.get(&id)?.get_multibyte(),
text.is_multibyte(),
"replace_buffer_contents_lisp_string expects text already converted to target buffer representation",
);
let len = self.buffers.get(&id)?.total_bytes();
if len > 0 {
self.delete_buffer_region(id, 0, len)?;
}
{
let buf = self.buffers.get_mut(&id)?;
buf.widen();
buf.goto_byte(0);
}
if !text.is_empty() {
self.insert_lisp_string_into_buffer(id, text)?;
self.goto_buffer_byte(id, 0)?;
}
Some(())
}
pub fn clear_buffer_local_properties(
&mut self,
id: BufferId,
obarray: &mut crate::emacs_core::symbol::Obarray,
kill_permanent: bool,
) -> Option<()> {
let defaults_snapshot = self.buffer_defaults;
let buf = self.buffers.get_mut(&id)?;
buf.kill_all_local_variables(obarray, kill_permanent, &defaults_snapshot);
Some(())
}
pub fn put_buffer_text_property(
&mut self,
id: BufferId,
start: usize,
end: usize,
name: Value,
value: Value,
) -> Option<bool> {
let buf = self.buffers.get_mut(&id)?;
if !buf.undo_state.in_progress() && !undo::undo_list_is_disabled(&buf.get_undo_list()) {
let old_val = buf
.text
.text_props_get_property(start, name)
.unwrap_or(Value::NIL);
let mut ul = buf.get_undo_list();
undo::undo_list_record_property_change(&mut ul, name, old_val, start, end);
buf.set_undo_list(ul);
}
Some(buf.text.text_props_put_property(start, end, name, value))
}
pub fn append_buffer_text_properties(
&mut self,
id: BufferId,
table: &TextPropertyTable,
byte_offset: usize,
) -> Option<()> {
self.buffers
.get_mut(&id)?
.text
.text_props_append_shifted(table, byte_offset);
Some(())
}
pub fn remove_buffer_text_property(
&mut self,
id: BufferId,
start: usize,
end: usize,
name: Value,
) -> Option<bool> {
let buf = self.buffers.get_mut(&id)?;
if !buf.undo_state.in_progress() && !undo::undo_list_is_disabled(&buf.get_undo_list()) {
let old_val = buf
.text
.text_props_get_property(start, name)
.unwrap_or(Value::NIL);
if !old_val.is_nil() {
let mut ul = buf.get_undo_list();
undo::undo_list_record_property_change(&mut ul, name, old_val, start, end);
buf.set_undo_list(ul);
}
}
Some(buf.text.text_props_remove_property(start, end, name))
}
pub fn clear_buffer_text_properties(
&mut self,
id: BufferId,
start: usize,
end: usize,
) -> Option<()> {
self.buffers
.get_mut(&id)?
.text
.text_props_remove_all(start, end);
Some(())
}
pub fn set_buffer_multibyte_flag(&mut self, id: BufferId, flag: bool) -> Option<()> {
let buf = self.buffers.get_mut(&id)?;
buf.set_multibyte_value(flag);
buf.set_buffer_local(
"enable-multibyte-characters",
if flag {
crate::emacs_core::value::Value::T
} else {
crate::emacs_core::value::Value::NIL
},
);
Some(())
}
pub fn set_buffer_modified_flag(&mut self, id: BufferId, flag: bool) -> Option<()> {
let root_id = self.modified_state_root_id(id)?;
self.buffers.get_mut(&root_id)?.set_modified(flag);
Some(())
}
pub fn restore_buffer_modified_state(&mut self, id: BufferId, flag: Value) -> Option<Value> {
let root_id = self.modified_state_root_id(id)?;
let out = self.buffers.get_mut(&root_id)?.restore_modified_state(flag);
Some(out)
}
pub fn set_buffer_auto_saved(&mut self, id: BufferId) -> Option<()> {
self.buffers.get_mut(&id)?.mark_auto_saved();
Some(())
}
pub fn set_buffer_modified_tick(&mut self, id: BufferId, tick: i64) -> Option<()> {
let root_id = self.modified_state_root_id(id)?;
let buf = self.buffers.get_mut(&root_id)?;
buf.text.set_modified_tick(tick);
Some(())
}
pub fn set_buffer_file_name(&mut self, id: BufferId, file_name: Value) -> Option<()> {
debug_assert!(file_name.is_nil() || file_name.is_string());
let buf = self.buffers.get_mut(&id)?;
buf.set_file_name_value(file_name);
Some(())
}
pub fn set_buffer_file_truename(&mut self, id: BufferId, file_truename: Value) -> Option<()> {
debug_assert!(file_truename.is_nil() || file_truename.is_string());
let buf = self.buffers.get_mut(&id)?;
buf.slots[BUFFER_SLOT_FILE_TRUENAME] = file_truename;
Some(())
}
pub fn set_buffer_name(&mut self, id: BufferId, name: Value) -> Option<()> {
self.buffers.get_mut(&id)?.set_name_value(name);
Some(())
}
pub fn set_buffer_mark(&mut self, id: BufferId, pos: usize) -> Option<()> {
self.buffers.get_mut(&id)?.set_mark_byte(pos);
Some(())
}
pub fn clear_buffer_mark(&mut self, id: BufferId) -> Option<()> {
let buf = self.buffers.get_mut(&id)?;
buf.mark = None;
buf.mark_byte = None;
Some(())
}
pub fn set_buffer_local_property(
&mut self,
id: BufferId,
name: &str,
value: Value,
) -> Option<()> {
self.buffers.get_mut(&id)?.set_buffer_local(name, value);
Some(())
}
pub fn set_buffer_local_property_by_sym_id(
&mut self,
id: BufferId,
sym_id: SymId,
value: Value,
) -> Option<()> {
self.buffers
.get_mut(&id)?
.set_buffer_local_by_sym_id(sym_id, value);
Some(())
}
pub fn buffer_local_map(&self, id: BufferId) -> Option<Value> {
Some(self.buffers.get(&id)?.local_map())
}
pub fn current_local_map(&self) -> Value {
self.current
.and_then(|id| self.buffer_local_map(id))
.unwrap_or(Value::NIL)
}
pub fn set_buffer_local_map(&mut self, id: BufferId, keymap: Value) -> Option<()> {
self.buffers.get_mut(&id)?.set_local_map(keymap);
Some(())
}
pub fn set_current_local_map(&mut self, keymap: Value) -> Option<()> {
let id = self.current?;
self.set_buffer_local_map(id, keymap)
}
pub fn set_buffer_local_void_property(&mut self, id: BufferId, name: &str) -> Option<()> {
self.buffers.get_mut(&id)?.set_buffer_local_void(name);
Some(())
}
pub fn set_buffer_local_void_property_by_sym_id(
&mut self,
id: BufferId,
sym_id: SymId,
) -> Option<()> {
self.buffers
.get_mut(&id)?
.set_buffer_local_void_by_sym_id(sym_id);
Some(())
}
pub fn remove_buffer_local_property(
&mut self,
id: BufferId,
name: &str,
) -> Option<Option<RuntimeBindingValue>> {
let buf = self.buffers.get_mut(&id)?;
Some(buf.kill_buffer_local(name))
}
pub fn add_undo_boundary(&mut self, id: BufferId) -> Option<()> {
let buf = self.buffers.get_mut(&id)?;
let mut ul = buf.get_undo_list();
undo::undo_list_boundary(&mut ul);
ul = undo::truncate_undo_list(ul, 160_000, 240_000);
buf.set_undo_list(ul);
Some(())
}
pub fn restore_buffer_restriction(
&mut self,
id: BufferId,
begv: usize,
zv: usize,
) -> Option<()> {
self.buffers.get_mut(&id)?.narrow_to_byte_region(begv, zv);
let _ = self.record_buffer_state_markers(id);
Some(())
}
pub fn save_current_restriction_state(&mut self) -> Option<SavedRestrictionState> {
let buffer_id = self.current_buffer_id()?;
let (begv, zv, len) = {
let buffer = self.get(buffer_id)?;
(buffer.begv_byte, buffer.zv_byte, buffer.total_bytes())
};
let restriction = if begv == 0 && zv == len {
SavedRestrictionKind::None
} else {
let (beg_marker, _) = self.create_marker(buffer_id, begv, InsertionType::Before);
let (end_marker, _) = self.create_marker(buffer_id, zv, InsertionType::After);
SavedRestrictionKind::Markers {
beg_marker,
end_marker,
}
};
let labeled_restrictions = self.clone_labeled_restrictions(buffer_id).unwrap_or(None);
Some(SavedRestrictionState {
buffer_id,
restriction,
labeled_restrictions,
})
}
#[tracing::instrument(level = "trace", skip(self))]
pub fn reset_outermost_restrictions(&mut self) -> OutermostRestrictionResetState {
let mut affected_buffers: Vec<BufferId> =
self.labeled_restrictions.keys().copied().collect();
affected_buffers.sort_by_key(|buffer_id| buffer_id.0);
let mut retained_buffers = Vec::with_capacity(affected_buffers.len());
for buffer_id in affected_buffers {
let Some((begv, zv)) = self.labeled_restriction_bounds(buffer_id, true) else {
self.replace_labeled_restrictions(buffer_id, None);
continue;
};
if self
.restore_buffer_restriction(buffer_id, begv, zv)
.is_some()
{
retained_buffers.push(buffer_id);
} else {
self.replace_labeled_restrictions(buffer_id, None);
}
}
OutermostRestrictionResetState {
affected_buffers: retained_buffers,
}
}
#[tracing::instrument(level = "trace", skip(self, state))]
pub fn restore_outermost_restrictions(&mut self, state: OutermostRestrictionResetState) {
for buffer_id in state.affected_buffers {
if let Some((begv, zv)) = self.current_labeled_restriction_bounds(buffer_id) {
let _ = self.restore_buffer_restriction(buffer_id, begv, zv);
} else {
self.replace_labeled_restrictions(buffer_id, None);
}
}
}
pub fn restore_saved_restriction_state(&mut self, saved: SavedRestrictionState) {
let buffer_id = saved.buffer_id;
if self.buffers.get(&buffer_id).is_none() {
self.replace_labeled_restrictions(buffer_id, None);
return;
}
self.replace_labeled_restrictions(buffer_id, saved.labeled_restrictions);
match saved.restriction {
SavedRestrictionKind::None => {
let _ = self.widen_buffer_fully(buffer_id);
}
SavedRestrictionKind::Markers {
beg_marker,
end_marker,
} => {
let beg = self.marker_position(buffer_id, beg_marker);
let end = self.marker_position(buffer_id, end_marker);
if let (Some(begv), Some(zv), Some(len)) = (
beg,
end,
self.buffers
.get(&buffer_id)
.map(|buffer| buffer.total_bytes()),
) {
let mut restored_begv = begv.min(len);
let mut restored_zv = zv.min(len);
if restored_begv > restored_zv {
std::mem::swap(&mut restored_begv, &mut restored_zv);
}
let _ = self.restore_buffer_restriction(buffer_id, restored_begv, restored_zv);
}
self.remove_marker(beg_marker);
self.remove_marker(end_marker);
}
}
}
pub fn internal_labeled_narrow_to_region(
&mut self,
buffer_id: BufferId,
start: usize,
end: usize,
label: Value,
) -> Option<()> {
self.buffers.get(&buffer_id)?;
if self.labeled_restriction_at(buffer_id, false).is_none() {
self.push_labeled_restriction_for_current_bounds(
buffer_id,
LabeledRestrictionLabel::Outermost,
)?;
}
self.restore_buffer_restriction(buffer_id, start, end)?;
self.push_labeled_restriction_for_current_bounds(
buffer_id,
LabeledRestrictionLabel::User(label),
)?;
Some(())
}
pub fn internal_labeled_widen(&mut self, buffer_id: BufferId, label: &Value) -> Option<()> {
self.buffers.get(&buffer_id)?;
if self.current_labeled_restriction_matches_label(buffer_id, label) {
let _ = self.pop_labeled_restriction(buffer_id);
}
self.widen_buffer(buffer_id)
}
pub fn configure_buffer_undo_list(&mut self, id: BufferId, value: Value) -> Option<()> {
{
let buf = self.buffers.get_mut(&id)?;
match value.kind() {
ValueKind::T => {
buf.set_buffer_local("buffer-undo-list", Value::T);
}
ValueKind::Nil => {
buf.set_buffer_local("buffer-undo-list", Value::NIL);
buf.undo_state.set_recorded_first_change(false);
}
other => {
buf.set_buffer_local("buffer-undo-list", value);
}
}
}
Some(())
}
pub fn undo_buffer(&mut self, id: BufferId, mut count: i64) -> Option<UndoExecutionResult> {
let (had_any_records, had_boundary, previous_undoing, groups) = {
let buffer = self.buffers.get_mut(&id)?;
let ul = buffer.get_undo_list();
let had_any_records = !undo::undo_list_is_empty(&ul);
let had_boundary = undo::undo_list_contains_boundary(&ul);
let had_trailing_boundary = undo::undo_list_has_trailing_boundary(&ul);
if count <= 0 && had_boundary {
return Some(UndoExecutionResult {
had_any_records,
had_boundary,
applied_any: false,
skipped_apply: true,
});
}
if count <= 0 {
count = 1;
}
let previous_undoing = buffer.undo_state.in_progress();
buffer.undo_state.set_in_progress(true);
let groups_to_undo = if had_trailing_boundary {
count as usize
} else {
(count as usize).saturating_add(1)
};
let mut current_ul = ul;
let mut groups = Vec::new();
for _ in 0..groups_to_undo {
let group = undo::undo_list_pop_group(&mut current_ul);
if group.is_empty() {
break;
}
groups.push(group);
}
buffer.set_undo_list(current_ul);
(had_any_records, had_boundary, previous_undoing, groups)
};
let mut applied_any = false;
for group in groups {
applied_any = true;
for entry in group {
if let Some(pt1) = entry.as_fixnum() {
let pos = (pt1 - 1).max(0) as usize;
let clamped = self
.buffers
.get(&id)
.map(|buffer| pos.min(buffer.total_bytes()))?;
self.goto_buffer_byte(id, clamped)?;
} else if entry.is_cons() {
let car = entry.cons_car();
let cdr = entry.cons_cdr();
match (car.kind(), cdr.kind()) {
(ValueKind::Fixnum(beg1), ValueKind::Fixnum(end1)) => {
let beg = (beg1 - 1).max(0) as usize;
let end = (end1 - 1).max(0) as usize;
let clamped_end = self
.buffers
.get(&id)
.map(|buffer| end.min(buffer.total_bytes()))?;
self.delete_buffer_region(id, beg.min(clamped_end), clamped_end)?;
}
(ValueKind::String, ValueKind::Fixnum(pos1)) => {
let text = car
.as_runtime_string_owned()
.expect("ValueKind::String must carry LispString payload");
let pos = (pos1.abs() - 1).max(0) as usize;
let clamped = self
.buffers
.get(&id)
.map(|buffer| pos.min(buffer.total_bytes()))?;
self.goto_buffer_byte(id, clamped)?;
self.insert_into_buffer(id, &text)?;
}
(ValueKind::T, ValueKind::Fixnum(_)) => {
}
_ => {
}
}
}
}
}
self.buffers
.get_mut(&id)?
.undo_state
.set_in_progress(previous_undoing);
Some(UndoExecutionResult {
had_any_records,
had_boundary,
applied_any,
skipped_apply: false,
})
}
pub fn generate_new_buffer_name(&self, base: &str) -> String {
self.generate_new_buffer_name_ignoring(base, None)
}
pub fn generate_new_buffer_name_ignoring(&self, base: &str, ignore: Option<&str>) -> String {
if ignore == Some(base) || self.find_buffer_by_name(base).is_none() {
return base.to_string();
}
let mut n = 2u64;
loop {
let candidate = format!("{}<{}>", base, n);
if ignore == Some(candidate.as_str()) || self.find_buffer_by_name(&candidate).is_none()
{
return candidate;
}
n += 1;
}
}
pub fn allocate_marker_id(&mut self) -> u64 {
let id = self.next_marker_id;
self.next_marker_id += 1;
id
}
pub fn create_marker(
&mut self,
buffer_id: BufferId,
pos: usize,
insertion_type: InsertionType,
) -> (u64, *mut crate::tagged::header::MarkerObj) {
let marker_id = self.next_marker_id;
self.next_marker_id += 1;
let marker_value =
crate::emacs_core::value::Value::make_marker(crate::heap_types::MarkerData {
buffer: Some(buffer_id),
insertion_type: insertion_type == InsertionType::After,
marker_id: Some(marker_id),
bytepos: 0,
charpos: 0,
next_marker: std::ptr::null_mut(),
});
let marker_ptr = marker_value
.as_veclike_ptr()
.expect("freshly allocated marker should have a veclike ptr")
as *mut crate::tagged::header::MarkerObj;
let _ = self.register_marker_id(marker_ptr, buffer_id, marker_id, pos, insertion_type);
(marker_id, marker_ptr)
}
pub fn register_marker_id(
&mut self,
marker_ptr: *mut crate::tagged::header::MarkerObj,
buffer_id: BufferId,
marker_id: u64,
pos: usize,
insertion_type: InsertionType,
) -> Option<()> {
let buf = self.buffers.get_mut(&buffer_id)?;
buf.register_marker(marker_ptr, marker_id, pos, insertion_type);
Some(())
}
pub fn marker_position(&self, buffer_id: BufferId, marker_id: u64) -> Option<usize> {
self.buffers
.get(&buffer_id)
.and_then(|buf| buf.text.marker_chain_lookup(marker_id))
.map(|(bytepos, _charpos, _ins)| bytepos)
}
pub fn marker_char_position(&self, buffer_id: BufferId, marker_id: u64) -> Option<usize> {
self.buffers
.get(&buffer_id)
.and_then(|buf| buf.text.marker_chain_lookup(marker_id))
.map(|(_bytepos, charpos, _ins)| charpos)
}
pub fn set_buffer_default_slot(&mut self, info: &BufferSlotInfo, value: Value) {
debug_assert!(info.offset < BUFFER_SLOT_COUNT);
self.buffer_defaults[info.offset] = value;
if info.local_flags_idx >= 0 {
for buf in self.buffers.values_mut() {
if !buf.slot_local_flag(info.offset) {
buf.slots[info.offset] = value;
}
}
}
}
pub fn remove_marker(&mut self, marker_id: u64) {
for buf in self.buffers.values_mut() {
buf.remove_marker_entry(marker_id);
}
}
pub fn update_marker_insertion_type(&mut self, marker_id: u64, ins_type: InsertionType) {
for buf in self.buffers.values_mut() {
if buf.text.has_marker(marker_id) {
buf.update_marker_insertion_type(marker_id, ins_type);
return;
}
}
}
pub(crate) fn dump_buffers(&self) -> &HashMap<BufferId, Buffer> {
&self.buffers
}
pub(crate) fn dump_current(&self) -> Option<BufferId> {
self.current
}
pub(crate) fn dump_next_id(&self) -> u64 {
self.next_id
}
pub(crate) fn dump_next_marker_id(&self) -> u64 {
self.next_marker_id
}
pub(crate) fn from_dump(
mut buffers: HashMap<BufferId, Buffer>,
current: Option<BufferId>,
next_id: u64,
next_marker_id: u64,
dumped_buffer_defaults: Option<&[crate::emacs_core::value::Value]>,
) -> Self {
let indirect_buffers: Vec<(BufferId, BufferId)> = buffers
.iter()
.filter_map(|(id, buffer)| buffer.base_buffer.map(|base_id| (*id, base_id)))
.collect();
for (buffer_id, base_id) in indirect_buffers {
let (shared_text, shared_undo) = match buffers.get(&base_id) {
Some(root) => (root.text.shared_clone(), root.undo_state.clone()),
None => continue,
};
let Some(buffer) = buffers.get_mut(&buffer_id) else {
continue;
};
buffer.text = shared_text;
buffer.undo_state = shared_undo;
}
let mut buffer_defaults = [crate::emacs_core::value::Value::NIL; BUFFER_SLOT_COUNT];
for info in BUFFER_SLOT_INFO {
buffer_defaults[info.offset] = info.default.to_value();
}
if let Some(dumped) = dumped_buffer_defaults {
for (idx, value) in dumped.iter().enumerate() {
if idx >= BUFFER_SLOT_COUNT {
break;
}
buffer_defaults[idx] = *value;
}
}
let mut manager = Self {
buffers,
buffer_order: Vec::new(),
current,
next_id,
next_marker_id,
labeled_restrictions: HashMap::new(),
dead_buffer_last_names: HashMap::new(),
buffer_defaults,
};
manager.buffer_order = manager.buffers.keys().copied().collect();
manager.buffer_order.sort_by_key(|id| id.0);
if let Some(current) = manager.current
&& manager.buffers.contains_key(¤t)
{
manager.note_buffer_order_head(current);
}
manager
}
}
impl Default for BufferManager {
fn default() -> Self {
Self::new()
}
}
impl GcTrace for BufferManager {
fn trace_roots(&self, roots: &mut Vec<Value>) {
for buffer in self.buffers.values() {
roots.push(buffer.name);
buffer.text.trace_text_prop_roots(roots);
buffer.undo_state.trace_roots(roots);
buffer.overlays.trace_roots(roots);
for slot in &buffer.slots {
roots.push(*slot);
}
roots.push(buffer.local_var_alist);
roots.push(buffer.keymap);
if let Some(sm) = buffer.state_markers {
unsafe {
if !sm.pt_marker_ptr.is_null() {
roots.push(Value::from_veclike_ptr(
sm.pt_marker_ptr as *const crate::tagged::header::VecLikeHeader,
));
}
if !sm.begv_marker_ptr.is_null() {
roots.push(Value::from_veclike_ptr(
sm.begv_marker_ptr as *const crate::tagged::header::VecLikeHeader,
));
}
if !sm.zv_marker_ptr.is_null() {
roots.push(Value::from_veclike_ptr(
sm.zv_marker_ptr as *const crate::tagged::header::VecLikeHeader,
));
}
}
}
}
for last_name in self.dead_buffer_last_names.values() {
roots.push(*last_name);
}
for slot in &self.buffer_defaults {
roots.push(*slot);
}
for restrictions in self.labeled_restrictions.values() {
for restriction in restrictions {
if let LabeledRestrictionLabel::User(label) = restriction.label {
roots.push(label);
}
}
}
}
}
#[cfg(test)]
#[path = "buffer_test.rs"]
mod tests;