pub mod tree;
pub mod view;
use std::collections::HashMap;
use crate::editor::buffer::BufferId;
use crate::editor::cursor::{Cursor, Motion, Position};
use crate::editor::document::Document;
pub use tree::{Axis, Side, WindowId, Windows};
pub use view::View;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Area {
pub x: u16,
pub y: u16,
pub width: u16,
pub height: u16,
}
impl Area {
#[must_use]
pub const fn new(x: u16, y: u16, width: u16, height: u16) -> Self {
Self {
x,
y,
width,
height,
}
}
#[must_use]
pub const fn right(&self) -> u16 {
self.x + self.width
}
#[must_use]
pub const fn bottom(&self) -> u16 {
self.y + self.height
}
#[must_use]
pub const fn centre(&self) -> (u16, u16) {
(self.x + self.width / 2, self.y + self.height / 2)
}
#[must_use]
pub const fn contains(&self, x: u16, y: u16) -> bool {
x >= self.x && x < self.right() && y >= self.y && y < self.bottom()
}
}
#[derive(Debug, Clone)]
struct Anchor {
view: View,
cursors: Vec<Cursor>,
primary: usize,
}
#[derive(Debug, Clone)]
pub struct Window {
pub buffer: BufferId,
pub view: View,
cursors: Vec<Cursor>,
primary: usize,
pub area: Area,
anchors: HashMap<BufferId, Anchor>,
}
impl Window {
#[must_use]
pub fn new(buffer: BufferId) -> Self {
Self {
buffer,
view: View::default(),
cursors: vec![Cursor::at(Position::ZERO)],
primary: 0,
area: Area::new(0, 0, 1, 1),
anchors: HashMap::new(),
}
}
pub fn show(&mut self, buffer: BufferId) {
if self.buffer == buffer {
return;
}
self.anchors.insert(
self.buffer,
Anchor {
view: self.view,
cursors: self.cursors.clone(),
primary: self.primary,
},
);
self.load(buffer);
}
fn load(&mut self, buffer: BufferId) {
self.buffer = buffer;
match self.anchors.remove(&buffer) {
Some(anchor) => {
self.view = anchor.view;
self.cursors = anchor.cursors;
self.primary = anchor.primary;
}
None => {
self.view = View::default();
self.cursors = vec![Cursor::at(Position::ZERO)];
self.primary = 0;
}
}
}
pub fn reset(&mut self, buffer: BufferId) {
self.anchors.remove(&buffer);
self.buffer = buffer;
self.view = View::default();
self.cursors = vec![Cursor::at(Position::ZERO)];
self.primary = 0;
}
pub fn buffer_removed(&mut self, removed: BufferId, fallback: BufferId) {
let shift = |id: BufferId| if id > removed { id - 1 } else { id };
self.anchors = std::mem::take(&mut self.anchors)
.into_iter()
.filter(|(id, _)| *id != removed)
.map(|(id, anchor)| (shift(id), anchor))
.collect();
if self.buffer == removed {
self.load(fallback);
} else {
self.buffer = shift(self.buffer);
}
}
#[must_use]
pub fn cursor(&self) -> Cursor {
self.cursors[self.primary]
}
pub fn cursor_mut(&mut self) -> &mut Cursor {
&mut self.cursors[self.primary]
}
#[must_use]
pub fn cursors(&self) -> &[Cursor] {
&self.cursors
}
pub fn cursors_mut(&mut self) -> &mut [Cursor] {
&mut self.cursors
}
pub fn move_cursors(
&mut self,
motion: Motion,
document: &Document,
extend: bool,
allow_eol: bool,
) {
for cursor in &mut self.cursors {
cursor.apply(motion, document, extend, allow_eol);
}
self.resort();
}
pub fn collapse_selections(&mut self) {
for cursor in &mut self.cursors {
cursor.collapse();
}
}
pub fn anchor_selections(&mut self) {
for cursor in &mut self.cursors {
cursor.anchor_here();
}
}
pub fn add_cursor(&mut self, cursor: Cursor) {
if self.cursors.iter().any(|c| c.head == cursor.head) {
return;
}
self.cursors.push(cursor);
self.resort();
}
pub fn clear_secondary_cursors(&mut self) {
let primary = self.cursors[self.primary];
self.cursors.clear();
self.cursors.push(primary);
self.primary = 0;
}
#[must_use]
pub fn edit_order(&self) -> Vec<usize> {
(0..self.cursors.len()).rev().collect()
}
pub fn clamp_cursors(&mut self, document: &Document, allow_eol: bool) {
for cursor in &mut self.cursors {
cursor.head = document.clamp(cursor.head, allow_eol);
cursor.anchor = document.clamp(cursor.anchor, allow_eol);
}
self.resort();
}
pub fn resort(&mut self) {
if self.cursors.len() == 1 {
self.primary = 0;
return;
}
let primary_head = self.cursors[self.primary].head;
self.cursors.sort_by_key(|c| c.head);
self.cursors.dedup_by_key(|c| c.head);
self.primary = self
.cursors
.iter()
.position(|c| c.head == primary_head)
.unwrap_or(0);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn window() -> Window {
Window::new(0)
}
#[test]
fn a_new_window_has_exactly_one_cursor() {
let win = window();
assert_eq!(win.cursors().len(), 1);
assert_eq!(win.cursor().head, Position::ZERO);
}
#[test]
fn cursors_stay_in_document_order() {
let mut win = window();
win.add_cursor(Cursor::at(Position::new(2, 1)));
win.add_cursor(Cursor::at(Position::new(1, 1)));
let heads: Vec<_> = win.cursors().iter().map(|c| c.head).collect();
assert_eq!(
heads,
vec![Position::ZERO, Position::new(1, 1), Position::new(2, 1)]
);
assert_eq!(win.cursor().head, Position::ZERO);
}
#[test]
fn duplicate_cursors_are_rejected() {
let mut win = window();
win.add_cursor(Cursor::at(Position::ZERO));
assert_eq!(win.cursors().len(), 1);
}
#[test]
fn collapsing_cursors_keeps_the_primary_one() {
let mut win = window();
win.add_cursor(Cursor::at(Position::new(1, 2)));
win.clear_secondary_cursors();
assert_eq!(win.cursors().len(), 1);
assert_eq!(win.cursor().head, Position::ZERO);
}
#[test]
fn merged_cursors_do_not_leave_a_dangling_primary() {
let document = Document::from_text("abc", None);
let mut win = window();
win.add_cursor(Cursor::at(Position::new(0, 1)));
win.move_cursors(Motion::LineEnd, &document, false, false);
assert_eq!(win.cursors().len(), 1);
assert_eq!(win.cursor().head, Position::new(0, 2));
}
#[test]
fn edit_order_runs_backwards_through_the_document() {
let mut win = window();
win.add_cursor(Cursor::at(Position::new(1, 0)));
win.add_cursor(Cursor::at(Position::new(2, 0)));
assert_eq!(win.edit_order(), vec![2, 1, 0]);
}
#[test]
fn clamping_pulls_cursors_back_inside_a_shrunken_document() {
let mut win = window();
win.add_cursor(Cursor::at(Position::new(9, 9)));
win.clamp_cursors(&Document::from_text("ab", None), false);
let heads: Vec<_> = win.cursors().iter().map(|c| c.head).collect();
assert_eq!(heads, vec![Position::ZERO, Position::new(0, 1)]);
}
#[test]
fn switching_buffers_and_back_returns_to_the_same_place() {
let mut win = window();
win.cursor_mut().move_to(Position::new(4, 2), false);
win.view.top_line = 3;
win.show(1);
assert_eq!(win.cursor().head, Position::ZERO);
assert_eq!(win.view.top_line, 0);
win.show(0);
assert_eq!(win.cursor().head, Position::new(4, 2));
assert_eq!(win.view.top_line, 3);
}
#[test]
fn showing_the_buffer_already_on_screen_does_nothing() {
let mut win = window();
win.cursor_mut().move_to(Position::new(2, 2), false);
win.show(0);
assert_eq!(win.cursor().head, Position::new(2, 2));
}
#[test]
fn closing_a_buffer_shifts_the_ids_above_it_down() {
let mut win = Window::new(2);
win.buffer_removed(0, 0);
assert_eq!(win.buffer, 1);
}
#[test]
fn closing_the_shown_buffer_falls_back_to_another() {
let mut win = Window::new(1);
win.cursor_mut().move_to(Position::new(3, 0), false);
win.buffer_removed(1, 0);
assert_eq!(win.buffer, 0);
assert_eq!(win.cursor().head, Position::ZERO);
}
#[test]
fn a_remembered_position_survives_an_unrelated_buffer_closing() {
let mut win = Window::new(0);
win.cursor_mut().move_to(Position::new(5, 1), false);
win.show(2);
win.buffer_removed(1, 0);
assert_eq!(win.buffer, 1);
win.show(0);
assert_eq!(win.cursor().head, Position::new(5, 1));
}
}