use crate::model::event::SplitId;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SyncAnchor {
pub left_line: usize,
pub right_line: usize,
}
pub type ScrollSyncGroupId = u32;
#[derive(Debug, Clone)]
pub struct ScrollSyncGroup {
pub id: ScrollSyncGroupId,
pub left_split: SplitId,
pub right_split: SplitId,
pub scroll_line: usize,
pub anchors: Vec<SyncAnchor>,
}
impl ScrollSyncGroup {
pub fn new(id: ScrollSyncGroupId, left_split: SplitId, right_split: SplitId) -> Self {
Self {
id,
left_split,
right_split,
scroll_line: 0,
anchors: vec![SyncAnchor {
left_line: 0,
right_line: 0,
}],
}
}
pub fn set_anchors(&mut self, anchors: Vec<SyncAnchor>) {
self.anchors = anchors;
if self.anchors.is_empty() {
self.anchors.push(SyncAnchor {
left_line: 0,
right_line: 0,
});
}
}
pub fn contains_split(&self, split_id: SplitId) -> bool {
self.left_split == split_id || self.right_split == split_id
}
pub fn is_left_split(&self, split_id: SplitId) -> bool {
self.left_split == split_id
}
pub fn left_to_right_line(&self, left_line: usize) -> usize {
let anchor = self
.anchors
.iter()
.rfind(|a| a.left_line <= left_line)
.unwrap_or(&self.anchors[0]);
let offset = left_line.saturating_sub(anchor.left_line);
anchor.right_line.saturating_add(offset)
}
pub fn right_to_left_line(&self, right_line: usize) -> usize {
let anchor = self
.anchors
.iter()
.rfind(|a| a.right_line <= right_line)
.unwrap_or(&self.anchors[0]);
let offset = right_line.saturating_sub(anchor.right_line);
anchor.left_line.saturating_add(offset)
}
pub fn apply_scroll_delta(&mut self, split_id: SplitId, delta_lines: isize) -> bool {
if !self.contains_split(split_id) {
return false;
}
let new_scroll = if delta_lines >= 0 {
self.scroll_line.saturating_add(delta_lines as usize)
} else {
self.scroll_line.saturating_sub(delta_lines.unsigned_abs())
};
self.scroll_line = new_scroll;
true
}
pub fn set_scroll_line(&mut self, line: usize) {
self.scroll_line = line;
}
pub fn left_scroll_line(&self) -> usize {
self.scroll_line
}
pub fn right_scroll_line(&self) -> usize {
self.left_to_right_line(self.scroll_line)
}
pub fn scroll_line_for_split(&self, split_id: SplitId) -> usize {
if split_id == self.left_split {
self.left_scroll_line()
} else {
self.right_scroll_line()
}
}
}
#[derive(Debug, Default)]
pub struct ScrollSyncManager {
groups: Vec<ScrollSyncGroup>,
next_id: ScrollSyncGroupId,
}
impl ScrollSyncManager {
pub fn new() -> Self {
Self {
groups: Vec::new(),
next_id: 1,
}
}
pub fn create_group(&mut self, left_split: SplitId, right_split: SplitId) -> ScrollSyncGroupId {
let id = self.next_id;
self.next_id += 1;
let group = ScrollSyncGroup::new(id, left_split, right_split);
self.groups.push(group);
id
}
pub fn create_group_with_id(
&mut self,
id: ScrollSyncGroupId,
left_split: SplitId,
right_split: SplitId,
) -> bool {
if self.groups.iter().any(|g| g.id == id) {
return false;
}
let group = ScrollSyncGroup::new(id, left_split, right_split);
self.groups.push(group);
true
}
pub fn remove_group(&mut self, id: ScrollSyncGroupId) -> bool {
if let Some(pos) = self.groups.iter().position(|g| g.id == id) {
self.groups.remove(pos);
true
} else {
false
}
}
pub fn remove_groups_for_split(&mut self, split_id: SplitId) {
self.groups.retain(|g| !g.contains_split(split_id));
}
pub fn get_group_mut(&mut self, id: ScrollSyncGroupId) -> Option<&mut ScrollSyncGroup> {
self.groups.iter_mut().find(|g| g.id == id)
}
pub fn get_group(&self, id: ScrollSyncGroupId) -> Option<&ScrollSyncGroup> {
self.groups.iter().find(|g| g.id == id)
}
pub fn find_group_for_split(&self, split_id: SplitId) -> Option<&ScrollSyncGroup> {
self.groups.iter().find(|g| g.contains_split(split_id))
}
pub fn find_group_for_split_mut(&mut self, split_id: SplitId) -> Option<&mut ScrollSyncGroup> {
self.groups.iter_mut().find(|g| g.contains_split(split_id))
}
pub fn is_split_synced(&self, split_id: SplitId) -> bool {
self.groups.iter().any(|g| g.contains_split(split_id))
}
pub fn groups(&self) -> &[ScrollSyncGroup] {
&self.groups
}
pub fn apply_scroll_delta(&mut self, split_id: SplitId, delta_lines: isize) -> bool {
if let Some(group) = self.find_group_for_split_mut(split_id) {
group.apply_scroll_delta(split_id, delta_lines);
true
} else {
false
}
}
pub fn set_anchors(&mut self, group_id: ScrollSyncGroupId, anchors: Vec<SyncAnchor>) {
if let Some(group) = self.get_group_mut(group_id) {
group.set_anchors(anchors);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_left_to_right_line_simple() {
let mut group = ScrollSyncGroup::new(1, SplitId(1), SplitId(2));
group.set_anchors(vec![
SyncAnchor {
left_line: 0,
right_line: 0,
},
SyncAnchor {
left_line: 10,
right_line: 10,
},
SyncAnchor {
left_line: 20,
right_line: 25,
}, ]);
assert_eq!(group.left_to_right_line(0), 0);
assert_eq!(group.left_to_right_line(5), 5);
assert_eq!(group.left_to_right_line(10), 10);
assert_eq!(group.left_to_right_line(15), 15);
assert_eq!(group.left_to_right_line(20), 25);
assert_eq!(group.left_to_right_line(25), 30);
}
#[test]
fn test_right_to_left_line() {
let mut group = ScrollSyncGroup::new(1, SplitId(1), SplitId(2));
group.set_anchors(vec![
SyncAnchor {
left_line: 0,
right_line: 0,
},
SyncAnchor {
left_line: 10,
right_line: 15,
}, ]);
assert_eq!(group.right_to_left_line(0), 0);
assert_eq!(group.right_to_left_line(5), 5);
assert_eq!(group.right_to_left_line(15), 10);
assert_eq!(group.right_to_left_line(20), 15);
}
#[test]
fn test_scroll_delta() {
let mut group = ScrollSyncGroup::new(1, SplitId(1), SplitId(2));
group.set_anchors(vec![
SyncAnchor {
left_line: 0,
right_line: 0,
},
SyncAnchor {
left_line: 50,
right_line: 60,
},
]);
assert_eq!(group.left_scroll_line(), 0);
assert_eq!(group.right_scroll_line(), 0);
group.apply_scroll_delta(SplitId(1), 10);
assert_eq!(group.left_scroll_line(), 10);
assert_eq!(group.right_scroll_line(), 10);
group.set_scroll_line(55);
assert_eq!(group.left_scroll_line(), 55);
assert_eq!(group.right_scroll_line(), 65); }
}